UNPKG

2.95 MBJavaScriptView Raw
1/**
2* plotly.js (cartesian) v1.54.3
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 var opts = nestedProperty(attributes, attribute).get();
37445 var propIn = nestedProperty(containerIn, attribute);
37446 var propOut = nestedProperty(containerOut, attribute);
37447 var v = propIn.get();
37448
37449 var template = containerOut._template;
37450 if(v === undefined && template) {
37451 v = nestedProperty(template, attribute).get();
37452 // already used the template value, so short-circuit the second check
37453 template = 0;
37454 }
37455
37456 if(dflt === undefined) dflt = opts.dflt;
37457
37458 /**
37459 * arrayOk: value MAY be an array, then we do no value checking
37460 * at this point, because it can be more complicated than the
37461 * individual form (eg. some array vals can be numbers, even if the
37462 * single values must be color strings)
37463 */
37464 if(opts.arrayOk && isArrayOrTypedArray(v)) {
37465 propOut.set(v);
37466 return v;
37467 }
37468
37469 var coerceFunction = exports.valObjectMeta[opts.valType].coerceFunction;
37470 coerceFunction(v, propOut, dflt, opts);
37471
37472 var out = propOut.get();
37473 // in case v was provided but invalid, try the template again so it still
37474 // overrides the regular default
37475 if(template && out === dflt && !validate(v, opts)) {
37476 v = nestedProperty(template, attribute).get();
37477 coerceFunction(v, propOut, dflt, opts);
37478 out = propOut.get();
37479 }
37480 return out;
37481};
37482
37483/**
37484 * Variation on coerce
37485 *
37486 * Uses coerce to get attribute value if user input is valid,
37487 * returns attribute default if user input it not valid or
37488 * returns false if there is no user input.
37489 */
37490exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dflt) {
37491 var propIn = nestedProperty(containerIn, attribute);
37492 var propOut = exports.coerce(containerIn, containerOut, attributes, attribute, dflt);
37493 var valIn = propIn.get();
37494
37495 return (valIn !== undefined && valIn !== null) ? propOut : false;
37496};
37497
37498/*
37499 * Shortcut to coerce the three font attributes
37500 *
37501 * 'coerce' is a lib.coerce wrapper with implied first three arguments
37502 */
37503exports.coerceFont = function(coerce, attr, dfltObj) {
37504 var out = {};
37505
37506 dfltObj = dfltObj || {};
37507
37508 out.family = coerce(attr + '.family', dfltObj.family);
37509 out.size = coerce(attr + '.size', dfltObj.size);
37510 out.color = coerce(attr + '.color', dfltObj.color);
37511
37512 return out;
37513};
37514
37515/** Coerce shortcut for 'hoverinfo'
37516 * handling 1-vs-multi-trace dflt logic
37517 *
37518 * @param {object} traceIn : user trace object
37519 * @param {object} traceOut : full trace object (requires _module ref)
37520 * @param {object} layoutOut : full layout object (require _dataLength ref)
37521 * @return {any} : the coerced value
37522 */
37523exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) {
37524 var moduleAttrs = traceOut._module.attributes;
37525 var attrs = moduleAttrs.hoverinfo ? moduleAttrs : baseTraceAttrs;
37526
37527 var valObj = attrs.hoverinfo;
37528 var dflt;
37529
37530 if(layoutOut._dataLength === 1) {
37531 var flags = valObj.dflt === 'all' ?
37532 valObj.flags.slice() :
37533 valObj.dflt.split('+');
37534
37535 flags.splice(flags.indexOf('name'), 1);
37536 dflt = flags.join('+');
37537 }
37538
37539 return exports.coerce(traceIn, traceOut, attrs, 'hoverinfo', dflt);
37540};
37541
37542/** Coerce shortcut for [un]selected.marker.opacity,
37543 * which has special default logic, to ensure that it corresponds to the
37544 * default selection behavior while allowing to be overtaken by any other
37545 * [un]selected attribute.
37546 *
37547 * N.B. This must be called *after* coercing all the other [un]selected attrs,
37548 * to give the intended result.
37549 *
37550 * @param {object} traceOut : fullData item
37551 * @param {function} coerce : lib.coerce wrapper with implied first three arguments
37552 */
37553exports.coerceSelectionMarkerOpacity = function(traceOut, coerce) {
37554 if(!traceOut.marker) return;
37555
37556 var mo = traceOut.marker.opacity;
37557 // you can still have a `marker` container with no markers if there's text
37558 if(mo === undefined) return;
37559
37560 var smoDflt;
37561 var usmoDflt;
37562
37563 // Don't give [un]selected.marker.opacity a default value if
37564 // marker.opacity is an array: handle this during style step.
37565 //
37566 // Only give [un]selected.marker.opacity a default value if you don't
37567 // set any other [un]selected attributes.
37568 if(!isArrayOrTypedArray(mo) && !traceOut.selected && !traceOut.unselected) {
37569 smoDflt = mo;
37570 usmoDflt = DESELECTDIM * mo;
37571 }
37572
37573 coerce('selected.marker.opacity', smoDflt);
37574 coerce('unselected.marker.opacity', usmoDflt);
37575};
37576
37577function validate(value, opts) {
37578 var valObjectDef = exports.valObjectMeta[opts.valType];
37579
37580 if(opts.arrayOk && isArrayOrTypedArray(value)) return true;
37581
37582 if(valObjectDef.validateFunction) {
37583 return valObjectDef.validateFunction(value, opts);
37584 }
37585
37586 var failed = {};
37587 var out = failed;
37588 var propMock = { set: function(v) { out = v; } };
37589
37590 // 'failed' just something mutable that won't be === anything else
37591
37592 valObjectDef.coerceFunction(value, propMock, failed, opts);
37593 return out !== failed;
37594}
37595exports.validate = validate;
37596
37597},{"../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){
37598/**
37599* Copyright 2012-2020, Plotly, Inc.
37600* All rights reserved.
37601*
37602* This source code is licensed under the MIT license found in the
37603* LICENSE file in the root directory of this source tree.
37604*/
37605
37606
37607'use strict';
37608
37609var d3 = _dereq_('d3');
37610var isNumeric = _dereq_('fast-isnumeric');
37611
37612var Loggers = _dereq_('./loggers');
37613var mod = _dereq_('./mod').mod;
37614
37615var constants = _dereq_('../constants/numerical');
37616var BADNUM = constants.BADNUM;
37617var ONEDAY = constants.ONEDAY;
37618var ONEHOUR = constants.ONEHOUR;
37619var ONEMIN = constants.ONEMIN;
37620var ONESEC = constants.ONESEC;
37621var EPOCHJD = constants.EPOCHJD;
37622
37623var Registry = _dereq_('../registry');
37624
37625var utcFormat = d3.time.format.utc;
37626
37627var 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;
37628// special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months
37629var 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;
37630
37631// for 2-digit years, the first year we map them onto
37632var YFIRST = new Date().getFullYear() - 70;
37633
37634function isWorldCalendar(calendar) {
37635 return (
37636 calendar &&
37637 Registry.componentsRegistry.calendars &&
37638 typeof calendar === 'string' && calendar !== 'gregorian'
37639 );
37640}
37641
37642/*
37643 * dateTick0: get the canonical tick for this calendar
37644 *
37645 * bool sunday is for week ticks, shift it to a Sunday.
37646 */
37647exports.dateTick0 = function(calendar, sunday) {
37648 if(isWorldCalendar(calendar)) {
37649 return sunday ?
37650 Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] :
37651 Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar];
37652 } else {
37653 return sunday ? '2000-01-02' : '2000-01-01';
37654 }
37655};
37656
37657/*
37658 * dfltRange: for each calendar, give a valid default range
37659 */
37660exports.dfltRange = function(calendar) {
37661 if(isWorldCalendar(calendar)) {
37662 return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar];
37663 } else {
37664 return ['2000-01-01', '2001-01-01'];
37665 }
37666};
37667
37668// is an object a javascript date?
37669exports.isJSDate = function(v) {
37670 return typeof v === 'object' && v !== null && typeof v.getTime === 'function';
37671};
37672
37673// The absolute limits of our date-time system
37674// This is a little weird: we use MIN_MS and MAX_MS in dateTime2ms
37675// but we use dateTime2ms to calculate them (after defining it!)
37676var MIN_MS, MAX_MS;
37677
37678/**
37679 * dateTime2ms - turn a date object or string s into milliseconds
37680 * (relative to 1970-01-01, per javascript standard)
37681 * optional calendar (string) to use a non-gregorian calendar
37682 *
37683 * Returns BADNUM if it doesn't find a date
37684 *
37685 * strings should have the form:
37686 *
37687 * -?YYYY-mm-dd<sep>HH:MM:SS.sss<tzInfo>?
37688 *
37689 * <sep>: space (our normal standard) or T or t (ISO-8601)
37690 * <tzInfo>: Z, z, or [+\-]HH:?MM and we THROW IT AWAY
37691 * this format comes from https://tools.ietf.org/html/rfc3339#section-5.6
37692 * but we allow it even with a space as the separator
37693 *
37694 * May truncate after any full field, and sss can be any length
37695 * even >3 digits, though javascript dates truncate to milliseconds,
37696 * we keep as much as javascript numeric precision can hold, but we only
37697 * report back up to 100 microsecond precision, because most dates support
37698 * this precision (close to 1970 support more, very far away support less)
37699 *
37700 * Expanded to support negative years to -9999 but you must always
37701 * give 4 digits, except for 2-digit positive years which we assume are
37702 * near the present time.
37703 * Note that we follow ISO 8601:2004: there *is* a year 0, which
37704 * is 1BC/BCE, and -1===2BC etc.
37705 *
37706 * World calendars: not all of these *have* agreed extensions to this full range,
37707 * if you have another calendar system but want a date range outside its validity,
37708 * you can use a gregorian date string prefixed with 'G' or 'g'.
37709 *
37710 * Where to cut off 2-digit years between 1900s and 2000s?
37711 * from http://support.microsoft.com/kb/244664:
37712 * 1930-2029 (the most retro of all...)
37713 * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
37714 * 1950-2049
37715 * by Java, from http://stackoverflow.com/questions/2024273/:
37716 * now-80 - now+19
37717 * or FileMaker Pro, from
37718 * http://www.filemaker.com/12help/html/add_view_data.4.21.html:
37719 * now-70 - now+29
37720 * but python strptime etc, via
37721 * http://docs.python.org/py3k/library/time.html:
37722 * 1969-2068 (super forward-looking, but static, not sliding!)
37723 *
37724 * lets go with now-70 to now+29, and if anyone runs into this problem
37725 * they can learn the hard way not to use 2-digit years, as no choice we
37726 * make now will cover all possibilities. mostly this will all be taken
37727 * care of in initial parsing, should only be an issue for hand-entered data
37728 * currently (2016) this range is:
37729 * 1946-2045
37730 */
37731exports.dateTime2ms = function(s, calendar) {
37732 // first check if s is a date object
37733 if(exports.isJSDate(s)) {
37734 // Convert to the UTC milliseconds that give the same
37735 // hours as this date has in the local timezone
37736 var tzOffset = s.getTimezoneOffset() * ONEMIN;
37737 var offsetTweak = (s.getUTCMinutes() - s.getMinutes()) * ONEMIN +
37738 (s.getUTCSeconds() - s.getSeconds()) * ONESEC +
37739 (s.getUTCMilliseconds() - s.getMilliseconds());
37740
37741 if(offsetTweak) {
37742 var comb = 3 * ONEMIN;
37743 tzOffset = tzOffset - comb / 2 + mod(offsetTweak - tzOffset + comb / 2, comb);
37744 }
37745 s = Number(s) - tzOffset;
37746 if(s >= MIN_MS && s <= MAX_MS) return s;
37747 return BADNUM;
37748 }
37749 // otherwise only accept strings and numbers
37750 if(typeof s !== 'string' && typeof s !== 'number') return BADNUM;
37751
37752 s = String(s);
37753
37754 var isWorld = isWorldCalendar(calendar);
37755
37756 // to handle out-of-range dates in international calendars, accept
37757 // 'G' as a prefix to force the built-in gregorian calendar.
37758 var s0 = s.charAt(0);
37759 if(isWorld && (s0 === 'G' || s0 === 'g')) {
37760 s = s.substr(1);
37761 calendar = '';
37762 }
37763
37764 var isChinese = isWorld && calendar.substr(0, 7) === 'chinese';
37765
37766 var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
37767 if(!match) return BADNUM;
37768 var y = match[1];
37769 var m = match[3] || '1';
37770 var d = Number(match[5] || 1);
37771 var H = Number(match[7] || 0);
37772 var M = Number(match[9] || 0);
37773 var S = Number(match[11] || 0);
37774
37775 if(isWorld) {
37776 // disallow 2-digit years for world calendars
37777 if(y.length === 2) return BADNUM;
37778 y = Number(y);
37779
37780 var cDate;
37781 try {
37782 var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
37783 if(isChinese) {
37784 var isIntercalary = m.charAt(m.length - 1) === 'i';
37785 m = parseInt(m, 10);
37786 cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d);
37787 } else {
37788 cDate = calInstance.newDate(y, Number(m), d);
37789 }
37790 } catch(e) { return BADNUM; } // Invalid ... date
37791
37792 if(!cDate) return BADNUM;
37793
37794 return ((cDate.toJD() - EPOCHJD) * ONEDAY) +
37795 (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC);
37796 }
37797
37798 if(y.length === 2) {
37799 y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST;
37800 } else y = Number(y);
37801
37802 // new Date uses months from 0; subtract 1 here just so we
37803 // don't have to do it again during the validity test below
37804 m -= 1;
37805
37806 // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
37807 // to support years 0-99 we need to use setFullYear explicitly
37808 // Note that 2000 is a leap year.
37809 var date = new Date(Date.UTC(2000, m, d, H, M));
37810 date.setUTCFullYear(y);
37811
37812 if(date.getUTCMonth() !== m) return BADNUM;
37813 if(date.getUTCDate() !== d) return BADNUM;
37814
37815 return date.getTime() + S * ONESEC;
37816};
37817
37818MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
37819MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
37820
37821// is string s a date? (see above)
37822exports.isDateTime = function(s, calendar) {
37823 return (exports.dateTime2ms(s, calendar) !== BADNUM);
37824};
37825
37826// pad a number with zeroes, to given # of digits before the decimal point
37827function lpad(val, digits) {
37828 return String(val + Math.pow(10, digits)).substr(1);
37829}
37830
37831/**
37832 * Turn ms into string of the form YYYY-mm-dd HH:MM:SS.ssss
37833 * Crop any trailing zeros in time, except never stop right after hours
37834 * (we could choose to crop '-01' from date too but for now we always
37835 * show the whole date)
37836 * Optional range r is the data range that applies, also in ms.
37837 * If rng is big, the later parts of time will be omitted
37838 */
37839var NINETYDAYS = 90 * ONEDAY;
37840var THREEHOURS = 3 * ONEHOUR;
37841var FIVEMIN = 5 * ONEMIN;
37842exports.ms2DateTime = function(ms, r, calendar) {
37843 if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM;
37844
37845 if(!r) r = 0;
37846
37847 var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10);
37848 var msRounded = Math.round(ms - msecTenths / 10);
37849 var dateStr, h, m, s, msec10, d;
37850
37851 if(isWorldCalendar(calendar)) {
37852 var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD;
37853 var timeMs = Math.floor(mod(ms, ONEDAY));
37854 try {
37855 dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar)
37856 .fromJD(dateJD).formatDate('yyyy-mm-dd');
37857 } catch(e) {
37858 // invalid date in this calendar - fall back to Gyyyy-mm-dd
37859 dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
37860 }
37861
37862 // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
37863 // other things for a few calendars, so we can't trust it. Just pad
37864 // it manually (after the '-' if there is one)
37865 if(dateStr.charAt(0) === '-') {
37866 while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1);
37867 } else {
37868 while(dateStr.length < 10) dateStr = '0' + dateStr;
37869 }
37870
37871 // TODO: if this is faster, we could use this block for extracting
37872 // the time components of regular gregorian too
37873 h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0;
37874 m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0;
37875 s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0;
37876 msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0;
37877 } else {
37878 d = new Date(msRounded);
37879
37880 dateStr = utcFormat('%Y-%m-%d')(d);
37881
37882 // <90 days: add hours and minutes - never *only* add hours
37883 h = (r < NINETYDAYS) ? d.getUTCHours() : 0;
37884 m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0;
37885 // <3 hours: add seconds
37886 s = (r < THREEHOURS) ? d.getUTCSeconds() : 0;
37887 // <5 minutes: add ms (plus one extra digit, this is msec*10)
37888 msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0;
37889 }
37890
37891 return includeTime(dateStr, h, m, s, msec10);
37892};
37893
37894// For converting old-style milliseconds to date strings,
37895// we use the local timezone rather than UTC like we use
37896// everywhere else, both for backward compatibility and
37897// because that's how people mostly use javasript date objects.
37898// Clip one extra day off our date range though so we can't get
37899// thrown beyond the range by the timezone shift.
37900exports.ms2DateTimeLocal = function(ms) {
37901 if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM;
37902
37903 var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10);
37904 var d = new Date(Math.round(ms - msecTenths / 10));
37905 var dateStr = d3.time.format('%Y-%m-%d')(d);
37906 var h = d.getHours();
37907 var m = d.getMinutes();
37908 var s = d.getSeconds();
37909 var msec10 = d.getUTCMilliseconds() * 10 + msecTenths;
37910
37911 return includeTime(dateStr, h, m, s, msec10);
37912};
37913
37914function includeTime(dateStr, h, m, s, msec10) {
37915 // include each part that has nonzero data in or after it
37916 if(h || m || s || msec10) {
37917 dateStr += ' ' + lpad(h, 2) + ':' + lpad(m, 2);
37918 if(s || msec10) {
37919 dateStr += ':' + lpad(s, 2);
37920 if(msec10) {
37921 var digits = 4;
37922 while(msec10 % 10 === 0) {
37923 digits -= 1;
37924 msec10 /= 10;
37925 }
37926 dateStr += '.' + lpad(msec10, digits);
37927 }
37928 }
37929 }
37930 return dateStr;
37931}
37932
37933// normalize date format to date string, in case it starts as
37934// a Date object or milliseconds
37935// optional dflt is the return value if cleaning fails
37936exports.cleanDate = function(v, dflt, calendar) {
37937 // let us use cleanDate to provide a missing default without an error
37938 if(v === BADNUM) return dflt;
37939 if(exports.isJSDate(v) || (typeof v === 'number' && isFinite(v))) {
37940 // do not allow milliseconds (old) or jsdate objects (inherently
37941 // described as gregorian dates) with world calendars
37942 if(isWorldCalendar(calendar)) {
37943 Loggers.error('JS Dates and milliseconds are incompatible with world calendars', v);
37944 return dflt;
37945 }
37946
37947 // NOTE: if someone puts in a year as a number rather than a string,
37948 // this will mistakenly convert it thinking it's milliseconds from 1970
37949 // that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
37950 v = exports.ms2DateTimeLocal(+v);
37951 if(!v && dflt !== undefined) return dflt;
37952 } else if(!exports.isDateTime(v, calendar)) {
37953 Loggers.error('unrecognized date', v);
37954 return dflt;
37955 }
37956 return v;
37957};
37958
37959/*
37960 * Date formatting for ticks and hovertext
37961 */
37962
37963/*
37964 * modDateFormat: Support world calendars, and add one item to
37965 * d3's vocabulary:
37966 * %{n}f where n is the max number of digits of fractional seconds
37967 */
37968var fracMatch = /%\d?f/g;
37969function modDateFormat(fmt, x, formatter, calendar) {
37970 fmt = fmt.replace(fracMatch, function(match) {
37971 var digits = Math.min(+(match.charAt(1)) || 6, 6);
37972 var fracSecs = ((x / 1000 % 1) + 2)
37973 .toFixed(digits)
37974 .substr(2).replace(/0+$/, '') || '0';
37975 return fracSecs;
37976 });
37977
37978 var d = new Date(Math.floor(x + 0.05));
37979
37980 if(isWorldCalendar(calendar)) {
37981 try {
37982 fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar);
37983 } catch(e) {
37984 return 'Invalid';
37985 }
37986 }
37987 return formatter(fmt)(d);
37988}
37989
37990/*
37991 * formatTime: create a time string from:
37992 * x: milliseconds
37993 * tr: tickround ('M', 'S', or # digits)
37994 * only supports UTC times (where every day is 24 hours and 0 is at midnight)
37995 */
37996var MAXSECONDS = [59, 59.9, 59.99, 59.999, 59.9999];
37997function formatTime(x, tr) {
37998 var timePart = mod(x + 0.05, ONEDAY);
37999
38000 var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' +
38001 lpad(mod(Math.floor(timePart / ONEMIN), 60), 2);
38002
38003 if(tr !== 'M') {
38004 if(!isNumeric(tr)) tr = 0; // should only be 'S'
38005
38006 /*
38007 * this is a weird one - and shouldn't come up unless people
38008 * monkey with tick0 in weird ways, but we need to do something!
38009 * IN PARTICULAR we had better not display garbage (see below)
38010 * for numbers we always round to the nearest increment of the
38011 * precision we're showing, and this seems like the right way to
38012 * handle seconds and milliseconds, as they have a decimal point
38013 * and people will interpret that to mean rounding like numbers.
38014 * but for larger increments we floor the value: it's always
38015 * 2013 until the ball drops on the new year. We could argue about
38016 * which field it is where we start rounding (should 12:08:59
38017 * round to 12:09 if we're stopping at minutes?) but for now I'll
38018 * say we round seconds but floor everything else. BUT that means
38019 * we need to never round up to 60 seconds, ie 23:59:60
38020 */
38021 var sec = Math.min(mod(x / ONESEC, 60), MAXSECONDS[tr]);
38022
38023 var secStr = (100 + sec).toFixed(tr).substr(1);
38024 if(tr > 0) {
38025 secStr = secStr.replace(/0+$/, '').replace(/[\.]$/, '');
38026 }
38027
38028 timeStr += ':' + secStr;
38029 }
38030 return timeStr;
38031}
38032
38033/*
38034 * formatDate: turn a date into tick or hover label text.
38035 *
38036 * x: milliseconds, the value to convert
38037 * fmt: optional, an explicit format string (d3 format, even for world calendars)
38038 * tr: tickround ('y', 'm', 'd', 'M', 'S', or # digits)
38039 * used if no explicit fmt is provided
38040 * formatter: locale-aware d3 date formatter for standard gregorian calendars
38041 * should be the result of exports.getD3DateFormat(gd)
38042 * calendar: optional string, the world calendar system to use
38043 *
38044 * returns the date/time as a string, potentially with the leading portion
38045 * on a separate line (after '\n')
38046 * Note that this means if you provide an explicit format which includes '\n'
38047 * the axis may choose to strip things after it when they don't change from
38048 * one tick to the next (as it does with automatic formatting)
38049 */
38050exports.formatDate = function(x, fmt, tr, formatter, calendar, extraFormat) {
38051 calendar = isWorldCalendar(calendar) && calendar;
38052
38053 if(!fmt) {
38054 if(tr === 'y') fmt = extraFormat.year;
38055 else if(tr === 'm') fmt = extraFormat.month;
38056 else if(tr === 'd') {
38057 fmt = extraFormat.dayMonth + '\n' + extraFormat.year;
38058 } else {
38059 return formatTime(x, tr) + '\n' + modDateFormat(extraFormat.dayMonthYear, x, formatter, calendar);
38060 }
38061 }
38062
38063 return modDateFormat(fmt, x, formatter, calendar);
38064};
38065
38066/*
38067 * incrementMonth: make a new milliseconds value from the given one,
38068 * having changed the month
38069 *
38070 * special case for world calendars: multiples of 12 are treated as years,
38071 * even for calendar systems that don't have (always or ever) 12 months/year
38072 * TODO: perhaps we need a different code for year increments to support this?
38073 *
38074 * ms (number): the initial millisecond value
38075 * dMonth (int): the (signed) number of months to shift
38076 * calendar (string): the calendar system to use
38077 *
38078 * changing month does not (and CANNOT) always preserve day, since
38079 * months have different lengths. The worst example of this is:
38080 * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3
38081 *
38082 * But we want to be able to iterate over the last day of each month,
38083 * regardless of what its number is.
38084 * So shift 3 days forward, THEN set the new month, then unshift:
38085 * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...
38086 *
38087 * Note that odd behavior still exists if you start from the 26th-28th:
38088 * 1/28 -> 2/28 -> 3/31
38089 * but at least you can't shift any dates into the wrong month,
38090 * and ticks on these days incrementing by month would be very unusual
38091 */
38092var THREEDAYS = 3 * ONEDAY;
38093exports.incrementMonth = function(ms, dMonth, calendar) {
38094 calendar = isWorldCalendar(calendar) && calendar;
38095
38096 // pull time out and operate on pure dates, then add time back at the end
38097 // this gives maximum precision - not that we *normally* care if we're
38098 // incrementing by month, but better to be safe!
38099 var timeMs = mod(ms, ONEDAY);
38100 ms = Math.round(ms - timeMs);
38101
38102 if(calendar) {
38103 try {
38104 var dateJD = Math.round(ms / ONEDAY) + EPOCHJD;
38105 var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
38106 var cDate = calInstance.fromJD(dateJD);
38107
38108 if(dMonth % 12) calInstance.add(cDate, dMonth, 'm');
38109 else calInstance.add(cDate, dMonth / 12, 'y');
38110
38111 return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
38112 } catch(e) {
38113 Loggers.error('invalid ms ' + ms + ' in calendar ' + calendar);
38114 // then keep going in gregorian even though the result will be 'Invalid'
38115 }
38116 }
38117
38118 var y = new Date(ms + THREEDAYS);
38119 return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS;
38120};
38121
38122/*
38123 * findExactDates: what fraction of data is exact days, months, or years?
38124 *
38125 * data: array of millisecond values
38126 * calendar (string) the calendar to test against
38127 */
38128exports.findExactDates = function(data, calendar) {
38129 var exactYears = 0;
38130 var exactMonths = 0;
38131 var exactDays = 0;
38132 var blankCount = 0;
38133 var d;
38134 var di;
38135
38136 var calInstance = (
38137 isWorldCalendar(calendar) &&
38138 Registry.getComponentMethod('calendars', 'getCal')(calendar)
38139 );
38140
38141 for(var i = 0; i < data.length; i++) {
38142 di = data[i];
38143
38144 // not date data at all
38145 if(!isNumeric(di)) {
38146 blankCount ++;
38147 continue;
38148 }
38149
38150 // not an exact date
38151 if(di % ONEDAY) continue;
38152
38153 if(calInstance) {
38154 try {
38155 d = calInstance.fromJD(di / ONEDAY + EPOCHJD);
38156 if(d.day() === 1) {
38157 if(d.month() === 1) exactYears++;
38158 else exactMonths++;
38159 } else exactDays++;
38160 } catch(e) {
38161 // invalid date in this calendar - ignore it here.
38162 }
38163 } else {
38164 d = new Date(di);
38165 if(d.getUTCDate() === 1) {
38166 if(d.getUTCMonth() === 0) exactYears++;
38167 else exactMonths++;
38168 } else exactDays++;
38169 }
38170 }
38171 exactMonths += exactYears;
38172 exactDays += exactMonths;
38173
38174 var dataCount = data.length - blankCount;
38175
38176 return {
38177 exactYears: exactYears / dataCount,
38178 exactMonths: exactMonths / dataCount,
38179 exactDays: exactDays / dataCount
38180 };
38181};
38182
38183},{"../constants/numerical":158,"../registry":269,"./loggers":182,"./mod":185,"d3":16,"fast-isnumeric":18}],171:[function(_dereq_,module,exports){
38184/**
38185* Copyright 2012-2020, Plotly, Inc.
38186* All rights reserved.
38187*
38188* This source code is licensed under the MIT license found in the
38189* LICENSE file in the root directory of this source tree.
38190*/
38191
38192'use strict';
38193
38194var d3 = _dereq_('d3');
38195var loggers = _dereq_('./loggers');
38196
38197/**
38198 * Allow referencing a graph DOM element either directly
38199 * or by its id string
38200 *
38201 * @param {HTMLDivElement|string} gd: a graph element or its id
38202 *
38203 * @returns {HTMLDivElement} the DOM element of the graph
38204 */
38205function getGraphDiv(gd) {
38206 var gdElement;
38207
38208 if(typeof gd === 'string') {
38209 gdElement = document.getElementById(gd);
38210
38211 if(gdElement === null) {
38212 throw new Error('No DOM element with id \'' + gd + '\' exists on the page.');
38213 }
38214
38215 return gdElement;
38216 } else if(gd === null || gd === undefined) {
38217 throw new Error('DOM element provided is null or undefined');
38218 }
38219
38220 // otherwise assume that gd is a DOM element
38221 return gd;
38222}
38223
38224function isPlotDiv(el) {
38225 var el3 = d3.select(el);
38226 return el3.node() instanceof HTMLElement &&
38227 el3.size() &&
38228 el3.classed('js-plotly-plot');
38229}
38230
38231function removeElement(el) {
38232 var elParent = el && el.parentNode;
38233 if(elParent) elParent.removeChild(el);
38234}
38235
38236/**
38237 * for dynamically adding style rules
38238 * makes one stylesheet that contains all rules added
38239 * by all calls to this function
38240 */
38241function addStyleRule(selector, styleString) {
38242 addRelatedStyleRule('global', selector, styleString);
38243}
38244
38245/**
38246 * for dynamically adding style rules
38247 * to a stylesheet uniquely identified by a uid
38248 */
38249function addRelatedStyleRule(uid, selector, styleString) {
38250 var id = 'plotly.js-style-' + uid;
38251 var style = document.getElementById(id);
38252 if(!style) {
38253 style = document.createElement('style');
38254 style.setAttribute('id', id);
38255 // WebKit hack :(
38256 style.appendChild(document.createTextNode(''));
38257 document.head.appendChild(style);
38258 }
38259 var styleSheet = style.sheet;
38260
38261 if(styleSheet.insertRule) {
38262 styleSheet.insertRule(selector + '{' + styleString + '}', 0);
38263 } else if(styleSheet.addRule) {
38264 styleSheet.addRule(selector, styleString, 0);
38265 } else loggers.warn('addStyleRule failed');
38266}
38267
38268/**
38269 * to remove from the page a stylesheet identified by a given uid
38270 */
38271function deleteRelatedStyleRule(uid) {
38272 var id = 'plotly.js-style-' + uid;
38273 var style = document.getElementById(id);
38274 if(style) removeElement(style);
38275}
38276
38277module.exports = {
38278 getGraphDiv: getGraphDiv,
38279 isPlotDiv: isPlotDiv,
38280 removeElement: removeElement,
38281 addStyleRule: addStyleRule,
38282 addRelatedStyleRule: addRelatedStyleRule,
38283 deleteRelatedStyleRule: deleteRelatedStyleRule
38284};
38285
38286},{"./loggers":182,"d3":16}],172:[function(_dereq_,module,exports){
38287/**
38288* Copyright 2012-2020, Plotly, Inc.
38289* All rights reserved.
38290*
38291* This source code is licensed under the MIT license found in the
38292* LICENSE file in the root directory of this source tree.
38293*/
38294
38295
38296'use strict';
38297
38298/* global jQuery:false */
38299
38300var EventEmitter = _dereq_('events').EventEmitter;
38301
38302var Events = {
38303
38304 init: function(plotObj) {
38305 /*
38306 * If we have already instantiated an emitter for this plot
38307 * return early.
38308 */
38309 if(plotObj._ev instanceof EventEmitter) return plotObj;
38310
38311 var ev = new EventEmitter();
38312 var internalEv = new EventEmitter();
38313
38314 /*
38315 * Assign to plot._ev while we still live in a land
38316 * where plot is a DOM element with stuff attached to it.
38317 * In the future we can make plot the event emitter itself.
38318 */
38319 plotObj._ev = ev;
38320
38321 /*
38322 * Create a second event handler that will manage events *internally*.
38323 * This allows parts of plotly to respond to thing like relayout without
38324 * having to use the user-facing event handler. They cannot peacefully
38325 * coexist on the same handler because a user invoking
38326 * plotObj.removeAllListeners() would detach internal events, breaking
38327 * plotly.
38328 */
38329 plotObj._internalEv = internalEv;
38330
38331 /*
38332 * Assign bound methods from the ev to the plot object. These methods
38333 * will reference the 'this' of plot._ev even though they are methods
38334 * of plot. This will keep the event machinery away from the plot object
38335 * which currently is often a DOM element but presents an API that will
38336 * continue to function when plot becomes an emitter. Not all EventEmitter
38337 * methods have been bound to `plot` as some do not currently add value to
38338 * the Plotly event API.
38339 */
38340 plotObj.on = ev.on.bind(ev);
38341 plotObj.once = ev.once.bind(ev);
38342 plotObj.removeListener = ev.removeListener.bind(ev);
38343 plotObj.removeAllListeners = ev.removeAllListeners.bind(ev);
38344
38345 /*
38346 * Create functions for managing internal events. These are *only* triggered
38347 * by the mirroring of external events via the emit function.
38348 */
38349 plotObj._internalOn = internalEv.on.bind(internalEv);
38350 plotObj._internalOnce = internalEv.once.bind(internalEv);
38351 plotObj._removeInternalListener = internalEv.removeListener.bind(internalEv);
38352 plotObj._removeAllInternalListeners = internalEv.removeAllListeners.bind(internalEv);
38353
38354 /*
38355 * We must wrap emit to continue to support JQuery events. The idea
38356 * is to check to see if the user is using JQuery events, if they are
38357 * we emit JQuery events to trigger user handlers as well as the EventEmitter
38358 * events.
38359 */
38360 plotObj.emit = function(event, data) {
38361 if(typeof jQuery !== 'undefined') {
38362 jQuery(plotObj).trigger(event, data);
38363 }
38364
38365 ev.emit(event, data);
38366 internalEv.emit(event, data);
38367 };
38368
38369 return plotObj;
38370 },
38371
38372 /*
38373 * This function behaves like jQuery's triggerHandler. It calls
38374 * all handlers for a particular event and returns the return value
38375 * of the LAST handler. This function also triggers jQuery's
38376 * triggerHandler for backwards compatibility.
38377 */
38378 triggerHandler: function(plotObj, event, data) {
38379 var jQueryHandlerValue;
38380 var nodeEventHandlerValue;
38381
38382 /*
38383 * If jQuery exists run all its handlers for this event and
38384 * collect the return value of the LAST handler function
38385 */
38386 if(typeof jQuery !== 'undefined') {
38387 jQueryHandlerValue = jQuery(plotObj).triggerHandler(event, data);
38388 }
38389
38390 /*
38391 * Now run all the node style event handlers
38392 */
38393 var ev = plotObj._ev;
38394 if(!ev) return jQueryHandlerValue;
38395
38396 var handlers = ev._events[event];
38397 if(!handlers) return jQueryHandlerValue;
38398
38399 // making sure 'this' is the EventEmitter instance
38400 function apply(handler) {
38401 // The 'once' case, we can't just call handler() as we need
38402 // the return value here. So,
38403 // - remove handler
38404 // - call listener and grab return value!
38405 // - stash 'fired' key to not call handler twice
38406 if(handler.listener) {
38407 ev.removeListener(event, handler.listener);
38408 if(!handler.fired) {
38409 handler.fired = true;
38410 return handler.listener.apply(ev, [data]);
38411 }
38412 } else {
38413 return handler.apply(ev, [data]);
38414 }
38415 }
38416
38417 // handlers can be function or an array of functions
38418 handlers = Array.isArray(handlers) ? handlers : [handlers];
38419
38420 var i;
38421 for(i = 0; i < handlers.length - 1; i++) {
38422 apply(handlers[i]);
38423 }
38424 // now call the final handler and collect its value
38425 nodeEventHandlerValue = apply(handlers[i]);
38426
38427 /*
38428 * Return either the jQuery handler value if it exists or the
38429 * nodeEventHandler value. jQuery event value supersedes nodejs
38430 * events for backwards compatibility reasons.
38431 */
38432 return jQueryHandlerValue !== undefined ?
38433 jQueryHandlerValue :
38434 nodeEventHandlerValue;
38435 },
38436
38437 purge: function(plotObj) {
38438 delete plotObj._ev;
38439 delete plotObj.on;
38440 delete plotObj.once;
38441 delete plotObj.removeListener;
38442 delete plotObj.removeAllListeners;
38443 delete plotObj.emit;
38444
38445 delete plotObj._ev;
38446 delete plotObj._internalEv;
38447 delete plotObj._internalOn;
38448 delete plotObj._internalOnce;
38449 delete plotObj._removeInternalListener;
38450 delete plotObj._removeAllInternalListeners;
38451
38452 return plotObj;
38453 }
38454
38455};
38456
38457module.exports = Events;
38458
38459},{"events":15}],173:[function(_dereq_,module,exports){
38460/**
38461* Copyright 2012-2020, Plotly, Inc.
38462* All rights reserved.
38463*
38464* This source code is licensed under the MIT license found in the
38465* LICENSE file in the root directory of this source tree.
38466*/
38467
38468
38469'use strict';
38470
38471var isPlainObject = _dereq_('./is_plain_object.js');
38472var isArray = Array.isArray;
38473
38474function primitivesLoopSplice(source, target) {
38475 var i, value;
38476 for(i = 0; i < source.length; i++) {
38477 value = source[i];
38478 if(value !== null && typeof(value) === 'object') {
38479 return false;
38480 }
38481 if(value !== void(0)) {
38482 target[i] = value;
38483 }
38484 }
38485 return true;
38486}
38487
38488exports.extendFlat = function() {
38489 return _extend(arguments, false, false, false);
38490};
38491
38492exports.extendDeep = function() {
38493 return _extend(arguments, true, false, false);
38494};
38495
38496exports.extendDeepAll = function() {
38497 return _extend(arguments, true, true, false);
38498};
38499
38500exports.extendDeepNoArrays = function() {
38501 return _extend(arguments, true, false, true);
38502};
38503
38504/*
38505 * Inspired by https://github.com/justmoon/node-extend/blob/master/index.js
38506 * All credit to the jQuery authors for perfecting this amazing utility.
38507 *
38508 * API difference with jQuery version:
38509 * - No optional boolean (true -> deep extend) first argument,
38510 * use `extendFlat` for first-level only extend and
38511 * use `extendDeep` for a deep extend.
38512 *
38513 * Other differences with jQuery version:
38514 * - Uses a modern (and faster) isPlainObject routine.
38515 * - Expected to work with object {} and array [] arguments only.
38516 * - Does not check for circular structure.
38517 * FYI: jQuery only does a check across one level.
38518 * Warning: this might result in infinite loops.
38519 *
38520 */
38521function _extend(inputs, isDeep, keepAllKeys, noArrayCopies) {
38522 var target = inputs[0];
38523 var length = inputs.length;
38524
38525 var input, key, src, copy, copyIsArray, clone, allPrimitives;
38526
38527 // TODO does this do the right thing for typed arrays?
38528
38529 if(length === 2 && isArray(target) && isArray(inputs[1]) && target.length === 0) {
38530 allPrimitives = primitivesLoopSplice(inputs[1], target);
38531
38532 if(allPrimitives) {
38533 return target;
38534 } else {
38535 target.splice(0, target.length); // reset target and continue to next block
38536 }
38537 }
38538
38539 for(var i = 1; i < length; i++) {
38540 input = inputs[i];
38541
38542 for(key in input) {
38543 src = target[key];
38544 copy = input[key];
38545
38546 if(noArrayCopies && isArray(copy)) {
38547 // Stop early and just transfer the array if array copies are disallowed:
38548
38549 target[key] = copy;
38550 } else if(isDeep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
38551 // recurse if we're merging plain objects or arrays
38552
38553 if(copyIsArray) {
38554 copyIsArray = false;
38555 clone = src && isArray(src) ? src : [];
38556 } else {
38557 clone = src && isPlainObject(src) ? src : {};
38558 }
38559
38560 // never move original objects, clone them
38561 target[key] = _extend([clone, copy], isDeep, keepAllKeys, noArrayCopies);
38562 } else if(typeof copy !== 'undefined' || keepAllKeys) {
38563 // don't bring in undefined values, except for extendDeepAll
38564
38565 target[key] = copy;
38566 }
38567 }
38568 }
38569
38570 return target;
38571}
38572
38573},{"./is_plain_object.js":179}],174:[function(_dereq_,module,exports){
38574/**
38575* Copyright 2012-2020, Plotly, Inc.
38576* All rights reserved.
38577*
38578* This source code is licensed under the MIT license found in the
38579* LICENSE file in the root directory of this source tree.
38580*/
38581
38582
38583'use strict';
38584
38585
38586/**
38587 * Return news array containing only the unique items
38588 * found in input array.
38589 *
38590 * IMPORTANT: Note that items are considered unique
38591 * if `String({})` is unique. For example;
38592 *
38593 * Lib.filterUnique([ { a: 1 }, { b: 2 } ])
38594 *
38595 * returns [{ a: 1 }]
38596 *
38597 * and
38598 *
38599 * Lib.filterUnique([ '1', 1 ])
38600 *
38601 * returns ['1']
38602 *
38603 *
38604 * @param {array} array base array
38605 * @return {array} new filtered array
38606 */
38607module.exports = function filterUnique(array) {
38608 var seen = {};
38609 var out = [];
38610 var j = 0;
38611
38612 for(var i = 0; i < array.length; i++) {
38613 var item = array[i];
38614
38615 if(seen[item] !== 1) {
38616 seen[item] = 1;
38617 out[j++] = item;
38618 }
38619 }
38620
38621 return out;
38622};
38623
38624},{}],175:[function(_dereq_,module,exports){
38625/**
38626* Copyright 2012-2020, Plotly, Inc.
38627* All rights reserved.
38628*
38629* This source code is licensed under the MIT license found in the
38630* LICENSE file in the root directory of this source tree.
38631*/
38632
38633'use strict';
38634
38635/** Filter out object items with visible !== true
38636 * insider array container.
38637 *
38638 * @param {array of objects} container
38639 * @return {array of objects} of length <= container
38640 *
38641 */
38642module.exports = function filterVisible(container) {
38643 var filterFn = isCalcData(container) ? calcDataFilter : baseFilter;
38644 var out = [];
38645
38646 for(var i = 0; i < container.length; i++) {
38647 var item = container[i];
38648 if(filterFn(item)) out.push(item);
38649 }
38650
38651 return out;
38652};
38653
38654function baseFilter(item) {
38655 return item.visible === true;
38656}
38657
38658function calcDataFilter(item) {
38659 var trace = item[0].trace;
38660 return trace.visible === true && trace._length !== 0;
38661}
38662
38663function isCalcData(cont) {
38664 return (
38665 Array.isArray(cont) &&
38666 Array.isArray(cont[0]) &&
38667 cont[0][0] &&
38668 cont[0][0].trace
38669 );
38670}
38671
38672},{}],176:[function(_dereq_,module,exports){
38673/**
38674* Copyright 2012-2020, Plotly, Inc.
38675* All rights reserved.
38676*
38677* This source code is licensed under the MIT license found in the
38678* LICENSE file in the root directory of this source tree.
38679*/
38680
38681'use strict';
38682
38683var mod = _dereq_('./mod').mod;
38684
38685/*
38686 * look for intersection of two line segments
38687 * (1->2 and 3->4) - returns array [x,y] if they do, null if not
38688 */
38689exports.segmentsIntersect = segmentsIntersect;
38690function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
38691 var a = x2 - x1;
38692 var b = x3 - x1;
38693 var c = x4 - x3;
38694 var d = y2 - y1;
38695 var e = y3 - y1;
38696 var f = y4 - y3;
38697 var det = a * f - c * d;
38698 // parallel lines? intersection is undefined
38699 // ignore the case where they are colinear
38700 if(det === 0) return null;
38701 var t = (b * f - c * e) / det;
38702 var u = (b * d - a * e) / det;
38703 // segments do not intersect?
38704 if(u < 0 || u > 1 || t < 0 || t > 1) return null;
38705
38706 return {x: x1 + a * t, y: y1 + d * t};
38707}
38708
38709/*
38710 * find the minimum distance between two line segments (1->2 and 3->4)
38711 */
38712exports.segmentDistance = function segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4) {
38713 if(segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return 0;
38714
38715 // the two segments and their lengths squared
38716 var x12 = x2 - x1;
38717 var y12 = y2 - y1;
38718 var x34 = x4 - x3;
38719 var y34 = y4 - y3;
38720 var ll12 = x12 * x12 + y12 * y12;
38721 var ll34 = x34 * x34 + y34 * y34;
38722
38723 // calculate distance squared, then take the sqrt at the very end
38724 var dist2 = Math.min(
38725 perpDistance2(x12, y12, ll12, x3 - x1, y3 - y1),
38726 perpDistance2(x12, y12, ll12, x4 - x1, y4 - y1),
38727 perpDistance2(x34, y34, ll34, x1 - x3, y1 - y3),
38728 perpDistance2(x34, y34, ll34, x2 - x3, y2 - y3)
38729 );
38730
38731 return Math.sqrt(dist2);
38732};
38733
38734/*
38735 * distance squared from segment ab to point c
38736 * [xab, yab] is the vector b-a
38737 * [xac, yac] is the vector c-a
38738 * llab is the length squared of (b-a), just to simplify calculation
38739 */
38740function perpDistance2(xab, yab, llab, xac, yac) {
38741 var fcAB = (xac * xab + yac * yab);
38742 if(fcAB < 0) {
38743 // point c is closer to point a
38744 return xac * xac + yac * yac;
38745 } else if(fcAB > llab) {
38746 // point c is closer to point b
38747 var xbc = xac - xab;
38748 var ybc = yac - yab;
38749 return xbc * xbc + ybc * ybc;
38750 } else {
38751 // perpendicular distance is the shortest
38752 var crossProduct = xac * yab - yac * xab;
38753 return crossProduct * crossProduct / llab;
38754 }
38755}
38756
38757// a very short-term cache for getTextLocation, just because
38758// we're often looping over the same locations multiple times
38759// invalidated as soon as we look at a different path
38760var locationCache, workingPath, workingTextWidth;
38761
38762// turn a path and position along it into x, y, and angle for the given text
38763exports.getTextLocation = function getTextLocation(path, totalPathLen, positionOnPath, textWidth) {
38764 if(path !== workingPath || textWidth !== workingTextWidth) {
38765 locationCache = {};
38766 workingPath = path;
38767 workingTextWidth = textWidth;
38768 }
38769 if(locationCache[positionOnPath]) {
38770 return locationCache[positionOnPath];
38771 }
38772
38773 // for the angle, use points on the path separated by the text width
38774 // even though due to curvature, the text will cover a bit more than that
38775 var p0 = path.getPointAtLength(mod(positionOnPath - textWidth / 2, totalPathLen));
38776 var p1 = path.getPointAtLength(mod(positionOnPath + textWidth / 2, totalPathLen));
38777 // note: atan handles 1/0 nicely
38778 var theta = Math.atan((p1.y - p0.y) / (p1.x - p0.x));
38779 // center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint
38780 // that's the average position of this segment, assuming it's roughly quadratic
38781 var pCenter = path.getPointAtLength(mod(positionOnPath, totalPathLen));
38782 var x = (pCenter.x * 4 + p0.x + p1.x) / 6;
38783 var y = (pCenter.y * 4 + p0.y + p1.y) / 6;
38784
38785 var out = {x: x, y: y, theta: theta};
38786 locationCache[positionOnPath] = out;
38787 return out;
38788};
38789
38790exports.clearLocationCache = function() {
38791 workingPath = null;
38792};
38793
38794/*
38795 * Find the segment of `path` that's within the visible area
38796 * given by `bounds` {left, right, top, bottom}, to within a
38797 * precision of `buffer` px
38798 *
38799 * returns: undefined if nothing is visible, else object:
38800 * {
38801 * min: position where the path first enters bounds, or 0 if it
38802 * starts within bounds
38803 * max: position where the path last exits bounds, or the path length
38804 * if it finishes within bounds
38805 * len: max - min, ie the length of visible path
38806 * total: the total path length - just included so the caller doesn't
38807 * need to call path.getTotalLength() again
38808 * isClosed: true iff the start and end points of the path are both visible
38809 * and are at the same point
38810 * }
38811 *
38812 * Works by starting from either end and repeatedly finding the distance from
38813 * that point to the plot area, and if it's outside the plot, moving along the
38814 * path by that distance (because the plot must be at least that far away on
38815 * the path). Note that if a path enters, exits, and re-enters the plot, we
38816 * will not capture this behavior.
38817 */
38818exports.getVisibleSegment = function getVisibleSegment(path, bounds, buffer) {
38819 var left = bounds.left;
38820 var right = bounds.right;
38821 var top = bounds.top;
38822 var bottom = bounds.bottom;
38823
38824 var pMin = 0;
38825 var pTotal = path.getTotalLength();
38826 var pMax = pTotal;
38827
38828 var pt0, ptTotal;
38829
38830 function getDistToPlot(len) {
38831 var pt = path.getPointAtLength(len);
38832
38833 // hold on to the start and end points for `closed`
38834 if(len === 0) pt0 = pt;
38835 else if(len === pTotal) ptTotal = pt;
38836
38837 var dx = (pt.x < left) ? left - pt.x : (pt.x > right ? pt.x - right : 0);
38838 var dy = (pt.y < top) ? top - pt.y : (pt.y > bottom ? pt.y - bottom : 0);
38839 return Math.sqrt(dx * dx + dy * dy);
38840 }
38841
38842 var distToPlot = getDistToPlot(pMin);
38843 while(distToPlot) {
38844 pMin += distToPlot + buffer;
38845 if(pMin > pMax) return;
38846 distToPlot = getDistToPlot(pMin);
38847 }
38848
38849 distToPlot = getDistToPlot(pMax);
38850 while(distToPlot) {
38851 pMax -= distToPlot + buffer;
38852 if(pMin > pMax) return;
38853 distToPlot = getDistToPlot(pMax);
38854 }
38855
38856 return {
38857 min: pMin,
38858 max: pMax,
38859 len: pMax - pMin,
38860 total: pTotal,
38861 isClosed: pMin === 0 && pMax === pTotal &&
38862 Math.abs(pt0.x - ptTotal.x) < 0.1 &&
38863 Math.abs(pt0.y - ptTotal.y) < 0.1
38864 };
38865};
38866
38867/**
38868 * Find point on SVG path corresponding to a given constraint coordinate
38869 *
38870 * @param {SVGPathElement} path
38871 * @param {Number} val : constraint coordinate value
38872 * @param {String} coord : 'x' or 'y' the constraint coordinate
38873 * @param {Object} opts :
38874 * - {Number} pathLength : supply total path length before hand
38875 * - {Number} tolerance
38876 * - {Number} iterationLimit
38877 * @return {SVGPoint}
38878 */
38879exports.findPointOnPath = function findPointOnPath(path, val, coord, opts) {
38880 opts = opts || {};
38881
38882 var pathLength = opts.pathLength || path.getTotalLength();
38883 var tolerance = opts.tolerance || 1e-3;
38884 var iterationLimit = opts.iterationLimit || 30;
38885
38886 // if path starts at a val greater than the path tail (like on vertical violins),
38887 // we must flip the sign of the computed diff.
38888 var mul = path.getPointAtLength(0)[coord] > path.getPointAtLength(pathLength)[coord] ? -1 : 1;
38889
38890 var i = 0;
38891 var b0 = 0;
38892 var b1 = pathLength;
38893 var mid;
38894 var pt;
38895 var diff;
38896
38897 while(i < iterationLimit) {
38898 mid = (b0 + b1) / 2;
38899 pt = path.getPointAtLength(mid);
38900 diff = pt[coord] - val;
38901
38902 if(Math.abs(diff) < tolerance) {
38903 return pt;
38904 } else {
38905 if(mul * diff > 0) {
38906 b1 = mid;
38907 } else {
38908 b0 = mid;
38909 }
38910 i++;
38911 }
38912 }
38913 return pt;
38914};
38915
38916},{"./mod":185}],177:[function(_dereq_,module,exports){
38917/**
38918* Copyright 2012-2020, Plotly, Inc.
38919* All rights reserved.
38920*
38921* This source code is licensed under the MIT license found in the
38922* LICENSE file in the root directory of this source tree.
38923*/
38924
38925'use strict';
38926
38927// Simple helper functions
38928// none of these need any external deps
38929
38930module.exports = function identity(d) { return d; };
38931
38932},{}],178:[function(_dereq_,module,exports){
38933/**
38934* Copyright 2012-2020, Plotly, Inc.
38935* All rights reserved.
38936*
38937* This source code is licensed under the MIT license found in the
38938* LICENSE file in the root directory of this source tree.
38939*/
38940
38941'use strict';
38942
38943var d3 = _dereq_('d3');
38944var isNumeric = _dereq_('fast-isnumeric');
38945
38946var numConstants = _dereq_('../constants/numerical');
38947var FP_SAFE = numConstants.FP_SAFE;
38948var BADNUM = numConstants.BADNUM;
38949
38950var lib = module.exports = {};
38951
38952lib.nestedProperty = _dereq_('./nested_property');
38953lib.keyedContainer = _dereq_('./keyed_container');
38954lib.relativeAttr = _dereq_('./relative_attr');
38955lib.isPlainObject = _dereq_('./is_plain_object');
38956lib.toLogRange = _dereq_('./to_log_range');
38957lib.relinkPrivateKeys = _dereq_('./relink_private');
38958
38959var arrayModule = _dereq_('./array');
38960lib.isTypedArray = arrayModule.isTypedArray;
38961lib.isArrayOrTypedArray = arrayModule.isArrayOrTypedArray;
38962lib.isArray1D = arrayModule.isArray1D;
38963lib.ensureArray = arrayModule.ensureArray;
38964lib.concat = arrayModule.concat;
38965lib.maxRowLength = arrayModule.maxRowLength;
38966lib.minRowLength = arrayModule.minRowLength;
38967
38968var modModule = _dereq_('./mod');
38969lib.mod = modModule.mod;
38970lib.modHalf = modModule.modHalf;
38971
38972var coerceModule = _dereq_('./coerce');
38973lib.valObjectMeta = coerceModule.valObjectMeta;
38974lib.coerce = coerceModule.coerce;
38975lib.coerce2 = coerceModule.coerce2;
38976lib.coerceFont = coerceModule.coerceFont;
38977lib.coerceHoverinfo = coerceModule.coerceHoverinfo;
38978lib.coerceSelectionMarkerOpacity = coerceModule.coerceSelectionMarkerOpacity;
38979lib.validate = coerceModule.validate;
38980
38981var datesModule = _dereq_('./dates');
38982lib.dateTime2ms = datesModule.dateTime2ms;
38983lib.isDateTime = datesModule.isDateTime;
38984lib.ms2DateTime = datesModule.ms2DateTime;
38985lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal;
38986lib.cleanDate = datesModule.cleanDate;
38987lib.isJSDate = datesModule.isJSDate;
38988lib.formatDate = datesModule.formatDate;
38989lib.incrementMonth = datesModule.incrementMonth;
38990lib.dateTick0 = datesModule.dateTick0;
38991lib.dfltRange = datesModule.dfltRange;
38992lib.findExactDates = datesModule.findExactDates;
38993lib.MIN_MS = datesModule.MIN_MS;
38994lib.MAX_MS = datesModule.MAX_MS;
38995
38996var searchModule = _dereq_('./search');
38997lib.findBin = searchModule.findBin;
38998lib.sorterAsc = searchModule.sorterAsc;
38999lib.sorterDes = searchModule.sorterDes;
39000lib.distinctVals = searchModule.distinctVals;
39001lib.roundUp = searchModule.roundUp;
39002lib.sort = searchModule.sort;
39003lib.findIndexOfMin = searchModule.findIndexOfMin;
39004
39005var statsModule = _dereq_('./stats');
39006lib.aggNums = statsModule.aggNums;
39007lib.len = statsModule.len;
39008lib.mean = statsModule.mean;
39009lib.median = statsModule.median;
39010lib.midRange = statsModule.midRange;
39011lib.variance = statsModule.variance;
39012lib.stdev = statsModule.stdev;
39013lib.interp = statsModule.interp;
39014
39015var matrixModule = _dereq_('./matrix');
39016lib.init2dArray = matrixModule.init2dArray;
39017lib.transposeRagged = matrixModule.transposeRagged;
39018lib.dot = matrixModule.dot;
39019lib.translationMatrix = matrixModule.translationMatrix;
39020lib.rotationMatrix = matrixModule.rotationMatrix;
39021lib.rotationXYMatrix = matrixModule.rotationXYMatrix;
39022lib.apply2DTransform = matrixModule.apply2DTransform;
39023lib.apply2DTransform2 = matrixModule.apply2DTransform2;
39024
39025var anglesModule = _dereq_('./angles');
39026lib.deg2rad = anglesModule.deg2rad;
39027lib.rad2deg = anglesModule.rad2deg;
39028lib.angleDelta = anglesModule.angleDelta;
39029lib.angleDist = anglesModule.angleDist;
39030lib.isFullCircle = anglesModule.isFullCircle;
39031lib.isAngleInsideSector = anglesModule.isAngleInsideSector;
39032lib.isPtInsideSector = anglesModule.isPtInsideSector;
39033lib.pathArc = anglesModule.pathArc;
39034lib.pathSector = anglesModule.pathSector;
39035lib.pathAnnulus = anglesModule.pathAnnulus;
39036
39037var anchorUtils = _dereq_('./anchor_utils');
39038lib.isLeftAnchor = anchorUtils.isLeftAnchor;
39039lib.isCenterAnchor = anchorUtils.isCenterAnchor;
39040lib.isRightAnchor = anchorUtils.isRightAnchor;
39041lib.isTopAnchor = anchorUtils.isTopAnchor;
39042lib.isMiddleAnchor = anchorUtils.isMiddleAnchor;
39043lib.isBottomAnchor = anchorUtils.isBottomAnchor;
39044
39045var geom2dModule = _dereq_('./geometry2d');
39046lib.segmentsIntersect = geom2dModule.segmentsIntersect;
39047lib.segmentDistance = geom2dModule.segmentDistance;
39048lib.getTextLocation = geom2dModule.getTextLocation;
39049lib.clearLocationCache = geom2dModule.clearLocationCache;
39050lib.getVisibleSegment = geom2dModule.getVisibleSegment;
39051lib.findPointOnPath = geom2dModule.findPointOnPath;
39052
39053var extendModule = _dereq_('./extend');
39054lib.extendFlat = extendModule.extendFlat;
39055lib.extendDeep = extendModule.extendDeep;
39056lib.extendDeepAll = extendModule.extendDeepAll;
39057lib.extendDeepNoArrays = extendModule.extendDeepNoArrays;
39058
39059var loggersModule = _dereq_('./loggers');
39060lib.log = loggersModule.log;
39061lib.warn = loggersModule.warn;
39062lib.error = loggersModule.error;
39063
39064var regexModule = _dereq_('./regex');
39065lib.counterRegex = regexModule.counter;
39066
39067var throttleModule = _dereq_('./throttle');
39068lib.throttle = throttleModule.throttle;
39069lib.throttleDone = throttleModule.done;
39070lib.clearThrottle = throttleModule.clear;
39071
39072var domModule = _dereq_('./dom');
39073lib.getGraphDiv = domModule.getGraphDiv;
39074lib.isPlotDiv = domModule.isPlotDiv;
39075lib.removeElement = domModule.removeElement;
39076lib.addStyleRule = domModule.addStyleRule;
39077lib.addRelatedStyleRule = domModule.addRelatedStyleRule;
39078lib.deleteRelatedStyleRule = domModule.deleteRelatedStyleRule;
39079
39080lib.clearResponsive = _dereq_('./clear_responsive');
39081
39082lib.makeTraceGroups = _dereq_('./make_trace_groups');
39083
39084lib._ = _dereq_('./localize');
39085
39086lib.notifier = _dereq_('./notifier');
39087
39088lib.filterUnique = _dereq_('./filter_unique');
39089lib.filterVisible = _dereq_('./filter_visible');
39090lib.pushUnique = _dereq_('./push_unique');
39091
39092lib.cleanNumber = _dereq_('./clean_number');
39093
39094lib.ensureNumber = function ensureNumber(v) {
39095 if(!isNumeric(v)) return BADNUM;
39096 v = Number(v);
39097 if(v < -FP_SAFE || v > FP_SAFE) return BADNUM;
39098 return isNumeric(v) ? Number(v) : BADNUM;
39099};
39100
39101/**
39102 * Is v a valid array index? Accepts numeric strings as well as numbers.
39103 *
39104 * @param {any} v: the value to test
39105 * @param {Optional[integer]} len: the array length we are indexing
39106 *
39107 * @return {bool}: v is a valid array index
39108 */
39109lib.isIndex = function(v, len) {
39110 if(len !== undefined && v >= len) return false;
39111 return isNumeric(v) && (v >= 0) && (v % 1 === 0);
39112};
39113
39114lib.noop = _dereq_('./noop');
39115lib.identity = _dereq_('./identity');
39116
39117/**
39118 * create an array of length 'cnt' filled with 'v' at all indices
39119 *
39120 * @param {any} v
39121 * @param {number} cnt
39122 * @return {array}
39123 */
39124lib.repeat = function(v, cnt) {
39125 var out = new Array(cnt);
39126 for(var i = 0; i < cnt; i++) {
39127 out[i] = v;
39128 }
39129 return out;
39130};
39131
39132/**
39133 * swap x and y of the same attribute in container cont
39134 * specify attr with a ? in place of x/y
39135 * you can also swap other things than x/y by providing part1 and part2
39136 */
39137lib.swapAttrs = function(cont, attrList, part1, part2) {
39138 if(!part1) part1 = 'x';
39139 if(!part2) part2 = 'y';
39140 for(var i = 0; i < attrList.length; i++) {
39141 var attr = attrList[i];
39142 var xp = lib.nestedProperty(cont, attr.replace('?', part1));
39143 var yp = lib.nestedProperty(cont, attr.replace('?', part2));
39144 var temp = xp.get();
39145 xp.set(yp.get());
39146 yp.set(temp);
39147 }
39148};
39149
39150/**
39151 * SVG painter's algo worked around with reinsertion
39152 */
39153lib.raiseToTop = function raiseToTop(elem) {
39154 elem.parentNode.appendChild(elem);
39155};
39156
39157/**
39158 * cancel a possibly pending transition; returned selection may be used by caller
39159 */
39160lib.cancelTransition = function(selection) {
39161 return selection.transition().duration(0);
39162};
39163
39164// constrain - restrict a number v to be between v0 and v1
39165lib.constrain = function(v, v0, v1) {
39166 if(v0 > v1) return Math.max(v1, Math.min(v0, v));
39167 return Math.max(v0, Math.min(v1, v));
39168};
39169
39170/**
39171 * do two bounding boxes from getBoundingClientRect,
39172 * ie {left,right,top,bottom,width,height}, overlap?
39173 * takes optional padding pixels
39174 */
39175lib.bBoxIntersect = function(a, b, pad) {
39176 pad = pad || 0;
39177 return (a.left <= b.right + pad &&
39178 b.left <= a.right + pad &&
39179 a.top <= b.bottom + pad &&
39180 b.top <= a.bottom + pad);
39181};
39182
39183/*
39184 * simpleMap: alternative to Array.map that only
39185 * passes on the element and up to 2 extra args you
39186 * provide (but not the array index or the whole array)
39187 *
39188 * array: the array to map it to
39189 * func: the function to apply
39190 * x1, x2: optional extra args
39191 */
39192lib.simpleMap = function(array, func, x1, x2, opts) {
39193 var len = array.length;
39194 var out = new Array(len);
39195 for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2, opts);
39196 return out;
39197};
39198
39199/**
39200 * Random string generator
39201 *
39202 * @param {object} existing
39203 * pass in strings to avoid as keys with truthy values
39204 * @param {int} bits
39205 * bits of information in the output string, default 24
39206 * @param {int} base
39207 * base of string representation, default 16. Should be a power of 2.
39208 */
39209lib.randstr = function randstr(existing, bits, base, _recursion) {
39210 if(!base) base = 16;
39211 if(bits === undefined) bits = 24;
39212 if(bits <= 0) return '0';
39213
39214 var digits = Math.log(Math.pow(2, bits)) / Math.log(base);
39215 var res = '';
39216 var i, b, x;
39217
39218 for(i = 2; digits === Infinity; i *= 2) {
39219 digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i;
39220 }
39221
39222 var rem = digits - Math.floor(digits);
39223
39224 for(i = 0; i < Math.floor(digits); i++) {
39225 x = Math.floor(Math.random() * base).toString(base);
39226 res = x + res;
39227 }
39228
39229 if(rem) {
39230 b = Math.pow(base, rem);
39231 x = Math.floor(Math.random() * b).toString(base);
39232 res = x + res;
39233 }
39234
39235 var parsed = parseInt(res, base);
39236 if((existing && existing[res]) ||
39237 (parsed !== Infinity && parsed >= Math.pow(2, bits))) {
39238 if(_recursion > 10) {
39239 lib.warn('randstr failed uniqueness');
39240 return res;
39241 }
39242 return randstr(existing, bits, base, (_recursion || 0) + 1);
39243 } else return res;
39244};
39245
39246lib.OptionControl = function(opt, optname) {
39247 /*
39248 * An environment to contain all option setters and
39249 * getters that collectively modify opts.
39250 *
39251 * You can call up opts from any function in new object
39252 * as this.optname || this.opt
39253 *
39254 * See FitOpts for example of usage
39255 */
39256 if(!opt) opt = {};
39257 if(!optname) optname = 'opt';
39258
39259 var self = {};
39260 self.optionList = [];
39261
39262 self._newoption = function(optObj) {
39263 optObj[optname] = opt;
39264 self[optObj.name] = optObj;
39265 self.optionList.push(optObj);
39266 };
39267
39268 self['_' + optname] = opt;
39269 return self;
39270};
39271
39272/**
39273 * lib.smooth: smooth arrayIn by convolving with
39274 * a hann window with given full width at half max
39275 * bounce the ends in, so the output has the same length as the input
39276 */
39277lib.smooth = function(arrayIn, FWHM) {
39278 FWHM = Math.round(FWHM) || 0; // only makes sense for integers
39279 if(FWHM < 2) return arrayIn;
39280
39281 var alen = arrayIn.length;
39282 var alen2 = 2 * alen;
39283 var wlen = 2 * FWHM - 1;
39284 var w = new Array(wlen);
39285 var arrayOut = new Array(alen);
39286 var i;
39287 var j;
39288 var k;
39289 var v;
39290
39291 // first make the window array
39292 for(i = 0; i < wlen; i++) {
39293 w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM);
39294 }
39295
39296 // now do the convolution
39297 for(i = 0; i < alen; i++) {
39298 v = 0;
39299 for(j = 0; j < wlen; j++) {
39300 k = i + j + 1 - FWHM;
39301
39302 // multibounce
39303 if(k < -alen) k -= alen2 * Math.round(k / alen2);
39304 else if(k >= alen2) k -= alen2 * Math.floor(k / alen2);
39305
39306 // single bounce
39307 if(k < 0) k = - 1 - k;
39308 else if(k >= alen) k = alen2 - 1 - k;
39309
39310 v += arrayIn[k] * w[j];
39311 }
39312 arrayOut[i] = v;
39313 }
39314
39315 return arrayOut;
39316};
39317
39318/**
39319 * syncOrAsync: run a sequence of functions synchronously
39320 * as long as its returns are not promises (ie have no .then)
39321 * includes one argument arg to send to all functions...
39322 * this is mainly just to prevent us having to make wrapper functions
39323 * when the only purpose of the wrapper is to reference gd
39324 * and a final step to be executed at the end
39325 * TODO: if there's an error and everything is sync,
39326 * this doesn't happen yet because we want to make sure
39327 * that it gets reported
39328 */
39329lib.syncOrAsync = function(sequence, arg, finalStep) {
39330 var ret, fni;
39331
39332 function continueAsync() {
39333 return lib.syncOrAsync(sequence, arg, finalStep);
39334 }
39335
39336 while(sequence.length) {
39337 fni = sequence.splice(0, 1)[0];
39338 ret = fni(arg);
39339
39340 if(ret && ret.then) {
39341 return ret.then(continueAsync)
39342 .then(undefined, lib.promiseError);
39343 }
39344 }
39345
39346 return finalStep && finalStep(arg);
39347};
39348
39349
39350/**
39351 * Helper to strip trailing slash, from
39352 * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash
39353 */
39354lib.stripTrailingSlash = function(str) {
39355 if(str.substr(-1) === '/') return str.substr(0, str.length - 1);
39356 return str;
39357};
39358
39359lib.noneOrAll = function(containerIn, containerOut, attrList) {
39360 /**
39361 * some attributes come together, so if you have one of them
39362 * in the input, you should copy the default values of the others
39363 * to the input as well.
39364 */
39365 if(!containerIn) return;
39366
39367 var hasAny = false;
39368 var hasAll = true;
39369 var i;
39370 var val;
39371
39372 for(i = 0; i < attrList.length; i++) {
39373 val = containerIn[attrList[i]];
39374 if(val !== undefined && val !== null) hasAny = true;
39375 else hasAll = false;
39376 }
39377
39378 if(hasAny && !hasAll) {
39379 for(i = 0; i < attrList.length; i++) {
39380 containerIn[attrList[i]] = containerOut[attrList[i]];
39381 }
39382 }
39383};
39384
39385/** merges calcdata field (given by cdAttr) with traceAttr values
39386 *
39387 * N.B. Loop over minimum of cd.length and traceAttr.length
39388 * i.e. it does not try to fill in beyond traceAttr.length-1
39389 *
39390 * @param {array} traceAttr : trace attribute
39391 * @param {object} cd : calcdata trace
39392 * @param {string} cdAttr : calcdata key
39393 */
39394lib.mergeArray = function(traceAttr, cd, cdAttr, fn) {
39395 var hasFn = typeof fn === 'function';
39396 if(lib.isArrayOrTypedArray(traceAttr)) {
39397 var imax = Math.min(traceAttr.length, cd.length);
39398 for(var i = 0; i < imax; i++) {
39399 var v = traceAttr[i];
39400 cd[i][cdAttr] = hasFn ? fn(v) : v;
39401 }
39402 }
39403};
39404
39405// cast numbers to positive numbers, returns 0 if not greater than 0
39406lib.mergeArrayCastPositive = function(traceAttr, cd, cdAttr) {
39407 return lib.mergeArray(traceAttr, cd, cdAttr, function(v) {
39408 var w = +v;
39409 return !isFinite(w) ? 0 : w > 0 ? w : 0;
39410 });
39411};
39412
39413/** fills calcdata field (given by cdAttr) with traceAttr values
39414 * or function of traceAttr values (e.g. some fallback)
39415 *
39416 * N.B. Loops over all cd items.
39417 *
39418 * @param {array} traceAttr : trace attribute
39419 * @param {object} cd : calcdata trace
39420 * @param {string} cdAttr : calcdata key
39421 * @param {function} [fn] : optional function to apply to each array item
39422 */
39423lib.fillArray = function(traceAttr, cd, cdAttr, fn) {
39424 fn = fn || lib.identity;
39425
39426 if(lib.isArrayOrTypedArray(traceAttr)) {
39427 for(var i = 0; i < cd.length; i++) {
39428 cd[i][cdAttr] = fn(traceAttr[i]);
39429 }
39430 }
39431};
39432
39433/** Handler for trace-wide vs per-point options
39434 *
39435 * @param {object} trace : (full) trace object
39436 * @param {number} ptNumber : index of the point in question
39437 * @param {string} astr : attribute string
39438 * @param {function} [fn] : optional function to apply to each array item
39439 *
39440 * @return {any}
39441 */
39442lib.castOption = function(trace, ptNumber, astr, fn) {
39443 fn = fn || lib.identity;
39444
39445 var val = lib.nestedProperty(trace, astr).get();
39446
39447 if(lib.isArrayOrTypedArray(val)) {
39448 if(Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) {
39449 return fn(val[ptNumber[0]][ptNumber[1]]);
39450 } else {
39451 return fn(val[ptNumber]);
39452 }
39453 } else {
39454 return val;
39455 }
39456};
39457
39458/** Extract option from calcdata item, correctly falling back to
39459 * trace value if not found.
39460 *
39461 * @param {object} calcPt : calcdata[i][j] item
39462 * @param {object} trace : (full) trace object
39463 * @param {string} calcKey : calcdata key
39464 * @param {string} traceKey : aka trace attribute string
39465 * @return {any}
39466 */
39467lib.extractOption = function(calcPt, trace, calcKey, traceKey) {
39468 if(calcKey in calcPt) return calcPt[calcKey];
39469
39470 // fallback to trace value,
39471 // must check if value isn't itself an array
39472 // which means the trace attribute has a corresponding
39473 // calcdata key, but its value is falsy
39474 var traceVal = lib.nestedProperty(trace, traceKey).get();
39475 if(!Array.isArray(traceVal)) return traceVal;
39476};
39477
39478function makePtIndex2PtNumber(indexToPoints) {
39479 var ptIndex2ptNumber = {};
39480 for(var k in indexToPoints) {
39481 var pts = indexToPoints[k];
39482 for(var j = 0; j < pts.length; j++) {
39483 ptIndex2ptNumber[pts[j]] = +k;
39484 }
39485 }
39486 return ptIndex2ptNumber;
39487}
39488
39489/** Tag selected calcdata items
39490 *
39491 * N.B. note that point 'index' corresponds to input data array index
39492 * whereas 'number' is its post-transform version.
39493 *
39494 * @param {array} calcTrace
39495 * @param {object} trace
39496 * - selectedpoints {array}
39497 * - _indexToPoints {object}
39498 * @param {ptNumber2cdIndex} ptNumber2cdIndex (optional)
39499 * optional map object for trace types that do not have 1-to-1 point number to
39500 * calcdata item index correspondence (e.g. histogram)
39501 */
39502lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) {
39503 var selectedpoints = trace.selectedpoints;
39504 var indexToPoints = trace._indexToPoints;
39505 var ptIndex2ptNumber;
39506
39507 // make pt index-to-number map object, which takes care of transformed traces
39508 if(indexToPoints) {
39509 ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints);
39510 }
39511
39512 function isCdIndexValid(v) {
39513 return v !== undefined && v < calcTrace.length;
39514 }
39515
39516 for(var i = 0; i < selectedpoints.length; i++) {
39517 var ptIndex = selectedpoints[i];
39518
39519 if(lib.isIndex(ptIndex) ||
39520 (lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1]))
39521 ) {
39522 var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex;
39523 var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber;
39524
39525 if(isCdIndexValid(cdIndex)) {
39526 calcTrace[cdIndex].selected = 1;
39527 }
39528 }
39529 }
39530};
39531
39532lib.selIndices2selPoints = function(trace) {
39533 var selectedpoints = trace.selectedpoints;
39534 var indexToPoints = trace._indexToPoints;
39535
39536 if(indexToPoints) {
39537 var ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints);
39538 var out = [];
39539
39540 for(var i = 0; i < selectedpoints.length; i++) {
39541 var ptIndex = selectedpoints[i];
39542 if(lib.isIndex(ptIndex)) {
39543 var ptNumber = ptIndex2ptNumber[ptIndex];
39544 if(lib.isIndex(ptNumber)) {
39545 out.push(ptNumber);
39546 }
39547 }
39548 }
39549
39550 return out;
39551 } else {
39552 return selectedpoints;
39553 }
39554};
39555
39556/** Returns target as set by 'target' transform attribute
39557 *
39558 * @param {object} trace : full trace object
39559 * @param {object} transformOpts : transform option object
39560 * - target (string} :
39561 * either an attribute string referencing an array in the trace object, or
39562 * a set array.
39563 *
39564 * @return {array or false} : the target array (NOT a copy!!) or false if invalid
39565 */
39566lib.getTargetArray = function(trace, transformOpts) {
39567 var target = transformOpts.target;
39568
39569 if(typeof target === 'string' && target) {
39570 var array = lib.nestedProperty(trace, target).get();
39571 return Array.isArray(array) ? array : false;
39572 } else if(Array.isArray(target)) {
39573 return target;
39574 }
39575
39576 return false;
39577};
39578
39579/**
39580 * modified version of jQuery's extend to strip out private objs and functions,
39581 * and cut arrays down to first <arraylen> or 1 elements
39582 * because extend-like algorithms are hella slow
39583 * obj2 is assumed to already be clean of these things (including no arrays)
39584 */
39585lib.minExtend = function(obj1, obj2) {
39586 var objOut = {};
39587 if(typeof obj2 !== 'object') obj2 = {};
39588 var arrayLen = 3;
39589 var keys = Object.keys(obj1);
39590 var i, k, v;
39591
39592 for(i = 0; i < keys.length; i++) {
39593 k = keys[i];
39594 v = obj1[k];
39595 if(k.charAt(0) === '_' || typeof v === 'function') continue;
39596 else if(k === 'module') objOut[k] = v;
39597 else if(Array.isArray(v)) {
39598 if(k === 'colorscale') {
39599 objOut[k] = v.slice();
39600 } else {
39601 objOut[k] = v.slice(0, arrayLen);
39602 }
39603 } else if(lib.isTypedArray(v)) {
39604 objOut[k] = v.subarray(0, arrayLen);
39605 } else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);
39606 else objOut[k] = v;
39607 }
39608
39609 keys = Object.keys(obj2);
39610 for(i = 0; i < keys.length; i++) {
39611 k = keys[i];
39612 v = obj2[k];
39613 if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') {
39614 objOut[k] = v;
39615 }
39616 }
39617
39618 return objOut;
39619};
39620
39621lib.titleCase = function(s) {
39622 return s.charAt(0).toUpperCase() + s.substr(1);
39623};
39624
39625lib.containsAny = function(s, fragments) {
39626 for(var i = 0; i < fragments.length; i++) {
39627 if(s.indexOf(fragments[i]) !== -1) return true;
39628 }
39629 return false;
39630};
39631
39632lib.isIE = function() {
39633 return typeof window.navigator.msSaveBlob !== 'undefined';
39634};
39635
39636var IS_IE9_OR_BELOW_REGEX = /MSIE [1-9]\./;
39637lib.isIE9orBelow = function() {
39638 return lib.isIE() && IS_IE9_OR_BELOW_REGEX.test(window.navigator.userAgent);
39639};
39640
39641var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/;
39642lib.isSafari = function() {
39643 return IS_SAFARI_REGEX.test(window.navigator.userAgent);
39644};
39645
39646/**
39647 * Duck typing to recognize a d3 selection, mostly for IE9's benefit
39648 * because it doesn't handle instanceof like modern browsers
39649 */
39650lib.isD3Selection = function(obj) {
39651 return obj && (typeof obj.classed === 'function');
39652};
39653
39654/**
39655 * Append element to DOM only if not present.
39656 *
39657 * @param {d3 selection} parent : parent selection of the element in question
39658 * @param {string} nodeType : node type of element to append
39659 * @param {string} className (optional) : class name of element in question
39660 * @param {fn} enterFn (optional) : optional fn applied to entering elements only
39661 * @return {d3 selection} selection of new layer
39662 *
39663 * Previously, we were using the following pattern:
39664 *
39665 * ```
39666 * var sel = parent.selectAll('.' + className)
39667 * .data([0]);
39668 *
39669 * sel.enter().append(nodeType)
39670 * .classed(className, true);
39671 *
39672 * return sel;
39673 * ```
39674 *
39675 * in numerous places in our codebase to achieve the same behavior.
39676 *
39677 * The logic below performs much better, mostly as we are using
39678 * `.select` instead `.selectAll` that is `querySelector` instead of
39679 * `querySelectorAll`.
39680 *
39681 */
39682lib.ensureSingle = function(parent, nodeType, className, enterFn) {
39683 var sel = parent.select(nodeType + (className ? '.' + className : ''));
39684 if(sel.size()) return sel;
39685
39686 var layer = parent.append(nodeType);
39687 if(className) layer.classed(className, true);
39688 if(enterFn) layer.call(enterFn);
39689
39690 return layer;
39691};
39692
39693/**
39694 * Same as Lib.ensureSingle, but using id as selector.
39695 * This version is mostly used for clipPath nodes.
39696 *
39697 * @param {d3 selection} parent : parent selection of the element in question
39698 * @param {string} nodeType : node type of element to append
39699 * @param {string} id : id of element in question
39700 * @param {fn} enterFn (optional) : optional fn applied to entering elements only
39701 * @return {d3 selection} selection of new layer
39702 */
39703lib.ensureSingleById = function(parent, nodeType, id, enterFn) {
39704 var sel = parent.select(nodeType + '#' + id);
39705 if(sel.size()) return sel;
39706
39707 var layer = parent.append(nodeType).attr('id', id);
39708 if(enterFn) layer.call(enterFn);
39709
39710 return layer;
39711};
39712
39713/**
39714 * Converts a string path to an object.
39715 *
39716 * When given a string containing an array element, it will create a `null`
39717 * filled array of the given size.
39718 *
39719 * @example
39720 * lib.objectFromPath('nested.test[2].path', 'value');
39721 * // returns { nested: { test: [null, null, { path: 'value' }]}
39722 *
39723 * @param {string} path to nested value
39724 * @param {*} any value to be set
39725 *
39726 * @return {Object} the constructed object with a full nested path
39727 */
39728lib.objectFromPath = function(path, value) {
39729 var keys = path.split('.');
39730 var tmpObj;
39731 var obj = tmpObj = {};
39732
39733 for(var i = 0; i < keys.length; i++) {
39734 var key = keys[i];
39735 var el = null;
39736
39737 var parts = keys[i].match(/(.*)\[([0-9]+)\]/);
39738
39739 if(parts) {
39740 key = parts[1];
39741 el = parts[2];
39742
39743 tmpObj = tmpObj[key] = [];
39744
39745 if(i === keys.length - 1) {
39746 tmpObj[el] = value;
39747 } else {
39748 tmpObj[el] = {};
39749 }
39750
39751 tmpObj = tmpObj[el];
39752 } else {
39753 if(i === keys.length - 1) {
39754 tmpObj[key] = value;
39755 } else {
39756 tmpObj[key] = {};
39757 }
39758
39759 tmpObj = tmpObj[key];
39760 }
39761 }
39762
39763 return obj;
39764};
39765
39766/**
39767 * Iterate through an object in-place, converting dotted properties to objects.
39768 *
39769 * Examples:
39770 *
39771 * lib.expandObjectPaths({'nested.test.path': 'value'});
39772 * => { nested: { test: {path: 'value'}}}
39773 *
39774 * It also handles array notation, e.g.:
39775 *
39776 * lib.expandObjectPaths({'foo[1].bar': 'value'});
39777 * => { foo: [null, {bar: value}] }
39778 *
39779 * It handles merges the results when two properties are specified in parallel:
39780 *
39781 * lib.expandObjectPaths({'foo[1].bar': 10, 'foo[0].bar': 20});
39782 * => { foo: [{bar: 10}, {bar: 20}] }
39783 *
39784 * It does NOT, however, merge mulitple mutliply-nested arrays::
39785 *
39786 * lib.expandObjectPaths({'marker[1].range[1]': 5, 'marker[1].range[0]': 4})
39787 * => { marker: [null, {range: 4}] }
39788 */
39789
39790// Store this to avoid recompiling regex on *every* prop since this may happen many
39791// many times for animations. Could maybe be inside the function. Not sure about
39792// scoping vs. recompilation tradeoff, but at least it's not just inlining it into
39793// the inner loop.
39794var dottedPropertyRegex = /^([^\[\.]+)\.(.+)?/;
39795var indexedPropertyRegex = /^([^\.]+)\[([0-9]+)\](\.)?(.+)?/;
39796
39797lib.expandObjectPaths = function(data) {
39798 var match, key, prop, datum, idx, dest, trailingPath;
39799 if(typeof data === 'object' && !Array.isArray(data)) {
39800 for(key in data) {
39801 if(data.hasOwnProperty(key)) {
39802 if((match = key.match(dottedPropertyRegex))) {
39803 datum = data[key];
39804 prop = match[1];
39805
39806 delete data[key];
39807
39808 data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]);
39809 } else if((match = key.match(indexedPropertyRegex))) {
39810 datum = data[key];
39811
39812 prop = match[1];
39813 idx = parseInt(match[2]);
39814
39815 delete data[key];
39816
39817 data[prop] = data[prop] || [];
39818
39819 if(match[3] === '.') {
39820 // This is the case where theere are subsequent properties into which
39821 // we must recurse, e.g. transforms[0].value
39822 trailingPath = match[4];
39823 dest = data[prop][idx] = data[prop][idx] || {};
39824
39825 // NB: Extend deep no arrays prevents this from working on multiple
39826 // nested properties in the same object, e.g.
39827 //
39828 // {
39829 // foo[0].bar[1].range
39830 // foo[0].bar[0].range
39831 // }
39832 //
39833 // In this case, the extendDeepNoArrays will overwrite one array with
39834 // the other, so that both properties *will not* be present in the
39835 // result. Fixing this would require a more intelligent tracking
39836 // of changes and merging than extendDeepNoArrays currently accomplishes.
39837 lib.extendDeepNoArrays(dest, lib.objectFromPath(trailingPath, lib.expandObjectPaths(datum)));
39838 } else {
39839 // This is the case where this property is the end of the line,
39840 // e.g. xaxis.range[0]
39841 data[prop][idx] = lib.expandObjectPaths(datum);
39842 }
39843 } else {
39844 data[key] = lib.expandObjectPaths(data[key]);
39845 }
39846 }
39847 }
39848 }
39849
39850 return data;
39851};
39852
39853/**
39854 * Converts value to string separated by the provided separators.
39855 *
39856 * @example
39857 * lib.numSeparate(2016, '.,');
39858 * // returns '2016'
39859 *
39860 * @example
39861 * lib.numSeparate(3000, '.,', true);
39862 * // returns '3,000'
39863 *
39864 * @example
39865 * lib.numSeparate(1234.56, '|,')
39866 * // returns '1,234|56'
39867 *
39868 * @param {string|number} value the value to be converted
39869 * @param {string} separators string of decimal, then thousands separators
39870 * @param {boolean} separatethousands boolean, 4-digit integers are separated if true
39871 *
39872 * @return {string} the value that has been separated
39873 */
39874lib.numSeparate = function(value, separators, separatethousands) {
39875 if(!separatethousands) separatethousands = false;
39876
39877 if(typeof separators !== 'string' || separators.length === 0) {
39878 throw new Error('Separator string required for formatting!');
39879 }
39880
39881 if(typeof value === 'number') {
39882 value = String(value);
39883 }
39884
39885 var thousandsRe = /(\d+)(\d{3})/;
39886 var decimalSep = separators.charAt(0);
39887 var thouSep = separators.charAt(1);
39888
39889 var x = value.split('.');
39890 var x1 = x[0];
39891 var x2 = x.length > 1 ? decimalSep + x[1] : '';
39892
39893 // Years are ignored for thousands separators
39894 if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) {
39895 while(thousandsRe.test(x1)) {
39896 x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2');
39897 }
39898 }
39899
39900 return x1 + x2;
39901};
39902
39903lib.TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)([:|\|][^}]*)?}/g;
39904var SIMPLE_PROPERTY_REGEX = /^\w*$/;
39905
39906/**
39907 * Substitute values from an object into a string
39908 *
39909 * Examples:
39910 * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'
39911 * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
39912 *
39913 * @param {string} input string containing %{...} template strings
39914 * @param {obj} data object containing substitution values
39915 *
39916 * @return {string} templated string
39917 */
39918lib.templateString = function(string, obj) {
39919 // Not all that useful, but cache nestedProperty instantiation
39920 // just in case it speeds things up *slightly*:
39921 var getterCache = {};
39922
39923 return string.replace(lib.TEMPLATE_STRING_REGEX, function(dummy, key) {
39924 var v;
39925 if(SIMPLE_PROPERTY_REGEX.test(key)) {
39926 v = obj[key];
39927 } else {
39928 getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get;
39929 v = getterCache[key]();
39930 }
39931 return lib.isValidTextValue(v) ? v : '';
39932 });
39933};
39934
39935var hovertemplateWarnings = {
39936 max: 10,
39937 count: 0,
39938 name: 'hovertemplate'
39939};
39940lib.hovertemplateString = function() {
39941 return templateFormatString.apply(hovertemplateWarnings, arguments);
39942};
39943
39944var texttemplateWarnings = {
39945 max: 10,
39946 count: 0,
39947 name: 'texttemplate'
39948};
39949lib.texttemplateString = function() {
39950 return templateFormatString.apply(texttemplateWarnings, arguments);
39951};
39952
39953var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/;
39954/**
39955 * Substitute values from an object into a string and optionally formats them using d3-format,
39956 * or fallback to associated labels.
39957 *
39958 * Examples:
39959 * Lib.hovertemplateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf'
39960 * Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf'
39961 * Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00'
39962 *
39963 * @param {string} input string containing %{...:...} template strings
39964 * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'}
39965 * @param {obj} d3 locale
39966 * @param {obj} data objects containing substitution values
39967 *
39968 * @return {string} templated string
39969 */
39970function templateFormatString(string, labels, d3locale) {
39971 var opts = this;
39972 var args = arguments;
39973 if(!labels) labels = {};
39974 // Not all that useful, but cache nestedProperty instantiation
39975 // just in case it speeds things up *slightly*:
39976 var getterCache = {};
39977
39978 return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, key, format) {
39979 var obj, value, i;
39980 for(i = 3; i < args.length; i++) {
39981 obj = args[i];
39982 if(!obj) continue;
39983 if(obj.hasOwnProperty(key)) {
39984 value = obj[key];
39985 break;
39986 }
39987
39988 if(!SIMPLE_PROPERTY_REGEX.test(key)) {
39989 value = getterCache[key] || lib.nestedProperty(obj, key).get();
39990 if(value) getterCache[key] = value;
39991 }
39992 if(value !== undefined) break;
39993 }
39994
39995 if(value === undefined && opts) {
39996 if(opts.count < opts.max) {
39997 lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!');
39998 value = match;
39999 }
40000
40001 if(opts.count === opts.max) {
40002 lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed');
40003 }
40004 opts.count++;
40005
40006 return match;
40007 }
40008
40009 if(format) {
40010 var fmt;
40011 if(format[0] === ':') {
40012 fmt = d3locale ? d3locale.numberFormat : d3.format;
40013 value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value);
40014 }
40015
40016 if(format[0] === '|') {
40017 fmt = d3locale ? d3locale.timeFormat.utc : d3.time.format.utc;
40018 var ms = lib.dateTime2ms(value);
40019 value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt);
40020 }
40021 } else {
40022 if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label'];
40023 }
40024 return value;
40025 });
40026}
40027
40028/*
40029 * alphanumeric string sort, tailored for subplot IDs like scene2, scene10, x10y13 etc
40030 */
40031var char0 = 48;
40032var char9 = 57;
40033lib.subplotSort = function(a, b) {
40034 var l = Math.min(a.length, b.length) + 1;
40035 var numA = 0;
40036 var numB = 0;
40037 for(var i = 0; i < l; i++) {
40038 var charA = a.charCodeAt(i) || 0;
40039 var charB = b.charCodeAt(i) || 0;
40040 var isNumA = charA >= char0 && charA <= char9;
40041 var isNumB = charB >= char0 && charB <= char9;
40042
40043 if(isNumA) numA = 10 * numA + charA - char0;
40044 if(isNumB) numB = 10 * numB + charB - char0;
40045
40046 if(!isNumA || !isNumB) {
40047 if(numA !== numB) return numA - numB;
40048 if(charA !== charB) return charA - charB;
40049 }
40050 }
40051 return numB - numA;
40052};
40053
40054// repeatable pseudorandom generator
40055var randSeed = 2000000000;
40056
40057lib.seedPseudoRandom = function() {
40058 randSeed = 2000000000;
40059};
40060
40061lib.pseudoRandom = function() {
40062 var lastVal = randSeed;
40063 randSeed = (69069 * randSeed + 1) % 4294967296;
40064 // don't let consecutive vals be too close together
40065 // gets away from really trying to be random, in favor of better local uniformity
40066 if(Math.abs(randSeed - lastVal) < 429496729) return lib.pseudoRandom();
40067 return randSeed / 4294967296;
40068};
40069
40070
40071/** Fill hover 'pointData' container with 'correct' hover text value
40072 *
40073 * - If trace hoverinfo contains a 'text' flag and hovertext is not set,
40074 * the text elements will be seen in the hover labels.
40075 *
40076 * - If trace hoverinfo contains a 'text' flag and hovertext is set,
40077 * hovertext takes precedence over text
40078 * i.e. the hoverinfo elements will be seen in the hover labels
40079 *
40080 * @param {object} calcPt
40081 * @param {object} trace
40082 * @param {object || array} contOut (mutated here)
40083 */
40084lib.fillText = function(calcPt, trace, contOut) {
40085 var fill = Array.isArray(contOut) ?
40086 function(v) { contOut.push(v); } :
40087 function(v) { contOut.text = v; };
40088
40089 var htx = lib.extractOption(calcPt, trace, 'htx', 'hovertext');
40090 if(lib.isValidTextValue(htx)) return fill(htx);
40091
40092 var tx = lib.extractOption(calcPt, trace, 'tx', 'text');
40093 if(lib.isValidTextValue(tx)) return fill(tx);
40094};
40095
40096// accept all truthy values and 0 (which gets cast to '0' in the hover labels)
40097lib.isValidTextValue = function(v) {
40098 return v || v === 0;
40099};
40100
40101/**
40102 * @param {number} ratio
40103 * @param {number} n (number of decimal places)
40104 */
40105lib.formatPercent = function(ratio, n) {
40106 n = n || 0;
40107 var str = (Math.round(100 * ratio * Math.pow(10, n)) * Math.pow(0.1, n)).toFixed(n) + '%';
40108 for(var i = 0; i < n; i++) {
40109 if(str.indexOf('.') !== -1) {
40110 str = str.replace('0%', '%');
40111 str = str.replace('.%', '%');
40112 }
40113 }
40114 return str;
40115};
40116
40117lib.isHidden = function(gd) {
40118 var display = window.getComputedStyle(gd).display;
40119 return !display || display === 'none';
40120};
40121
40122/** Return transform text for bar bar-like rectangles and pie-like slices
40123 * @param {object} transform
40124 * - targetX: desired position on the x-axis
40125 * - targetY: desired position on the y-axis
40126 * - textX: text middle position on the x-axis
40127 * - textY: text middle position on the y-axis
40128 * - anchorX: (optional) text anchor position on the x-axis (computed from textX), zero for middle anchor
40129 * - anchorY: (optional) text anchor position on the y-axis (computed from textY), zero for middle anchor
40130 * - scale: (optional) scale applied after translate
40131 * - rotate: (optional) rotation applied after scale
40132 * - noCenter: when defined no extra arguments needed in rotation
40133 */
40134lib.getTextTransform = function(transform) {
40135 var noCenter = transform.noCenter;
40136 var textX = transform.textX;
40137 var textY = transform.textY;
40138 var targetX = transform.targetX;
40139 var targetY = transform.targetY;
40140 var anchorX = transform.anchorX || 0;
40141 var anchorY = transform.anchorY || 0;
40142 var rotate = transform.rotate;
40143 var scale = transform.scale;
40144 if(!scale) scale = 0;
40145 else if(scale > 1) scale = 1;
40146
40147 return (
40148 'translate(' +
40149 (targetX - scale * (textX + anchorX)) + ',' +
40150 (targetY - scale * (textY + anchorY)) +
40151 ')' +
40152 (scale < 1 ?
40153 'scale(' + scale + ')' : ''
40154 ) +
40155 (rotate ?
40156 'rotate(' + rotate +
40157 (noCenter ? '' : ' ' + textX + ' ' + textY) +
40158 ')' : ''
40159 )
40160 );
40161};
40162
40163lib.ensureUniformFontSize = function(gd, baseFont) {
40164 var out = lib.extendFlat({}, baseFont);
40165 out.size = Math.max(
40166 baseFont.size,
40167 gd._fullLayout.uniformtext.minsize || 0
40168 );
40169 return out;
40170};
40171
40172},{"../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){
40173/**
40174* Copyright 2012-2020, Plotly, Inc.
40175* All rights reserved.
40176*
40177* This source code is licensed under the MIT license found in the
40178* LICENSE file in the root directory of this source tree.
40179*/
40180
40181
40182'use strict';
40183
40184// more info: http://stackoverflow.com/questions/18531624/isplainobject-thing
40185module.exports = function isPlainObject(obj) {
40186 // We need to be a little less strict in the `imagetest` container because
40187 // of how async image requests are handled.
40188 //
40189 // N.B. isPlainObject(new Constructor()) will return true in `imagetest`
40190 if(window && window.process && window.process.versions) {
40191 return Object.prototype.toString.call(obj) === '[object Object]';
40192 }
40193
40194 return (
40195 Object.prototype.toString.call(obj) === '[object Object]' &&
40196 Object.getPrototypeOf(obj) === Object.prototype
40197 );
40198};
40199
40200},{}],180:[function(_dereq_,module,exports){
40201/**
40202* Copyright 2012-2020, Plotly, Inc.
40203* All rights reserved.
40204*
40205* This source code is licensed under the MIT license found in the
40206* LICENSE file in the root directory of this source tree.
40207*/
40208
40209'use strict';
40210
40211var nestedProperty = _dereq_('./nested_property');
40212
40213var SIMPLE_PROPERTY_REGEX = /^\w*$/;
40214
40215// bitmask for deciding what's updated. Sometimes the name needs to be updated,
40216// sometimes the value needs to be updated, and sometimes both do. This is just
40217// a simple way to track what's updated such that it's a simple OR operation to
40218// assimilate new updates.
40219//
40220// The only exception is the UNSET bit that tracks when we need to explicitly
40221// unset and remove the property. This concrn arises because of the special
40222// way in which nestedProperty handles null/undefined. When you specify `null`,
40223// it prunes any unused items in the tree. I ran into some issues with it getting
40224// null vs undefined confused, so UNSET is just a bit that forces the property
40225// update to send `null`, removing the property explicitly rather than setting
40226// it to undefined.
40227var NONE = 0;
40228var NAME = 1;
40229var VALUE = 2;
40230var BOTH = 3;
40231var UNSET = 4;
40232
40233module.exports = function keyedContainer(baseObj, path, keyName, valueName) {
40234 keyName = keyName || 'name';
40235 valueName = valueName || 'value';
40236 var i, arr, baseProp;
40237 var changeTypes = {};
40238
40239 if(path && path.length) {
40240 baseProp = nestedProperty(baseObj, path);
40241 arr = baseProp.get();
40242 } else {
40243 arr = baseObj;
40244 }
40245
40246 path = path || '';
40247
40248 // Construct an index:
40249 var indexLookup = {};
40250 if(arr) {
40251 for(i = 0; i < arr.length; i++) {
40252 indexLookup[arr[i][keyName]] = i;
40253 }
40254 }
40255
40256 var isSimpleValueProp = SIMPLE_PROPERTY_REGEX.test(valueName);
40257
40258 var obj = {
40259 set: function(name, value) {
40260 var changeType = value === null ? UNSET : NONE;
40261
40262 // create the base array if necessary
40263 if(!arr) {
40264 if(!baseProp || changeType === UNSET) return;
40265
40266 arr = [];
40267 baseProp.set(arr);
40268 }
40269
40270 var idx = indexLookup[name];
40271 if(idx === undefined) {
40272 if(changeType === UNSET) return;
40273
40274 changeType = changeType | BOTH;
40275 idx = arr.length;
40276 indexLookup[name] = idx;
40277 } else if(value !== (isSimpleValueProp ? arr[idx][valueName] : nestedProperty(arr[idx], valueName).get())) {
40278 changeType = changeType | VALUE;
40279 }
40280
40281 var newValue = arr[idx] = arr[idx] || {};
40282 newValue[keyName] = name;
40283
40284 if(isSimpleValueProp) {
40285 newValue[valueName] = value;
40286 } else {
40287 nestedProperty(newValue, valueName).set(value);
40288 }
40289
40290 // If it's not an unset, force that bit to be unset. This is all related to the fact
40291 // that undefined and null are a bit specially implemented in nestedProperties.
40292 if(value !== null) {
40293 changeType = changeType & ~UNSET;
40294 }
40295
40296 changeTypes[idx] = changeTypes[idx] | changeType;
40297
40298 return obj;
40299 },
40300 get: function(name) {
40301 if(!arr) return;
40302
40303 var idx = indexLookup[name];
40304
40305 if(idx === undefined) {
40306 return undefined;
40307 } else if(isSimpleValueProp) {
40308 return arr[idx][valueName];
40309 } else {
40310 return nestedProperty(arr[idx], valueName).get();
40311 }
40312 },
40313 rename: function(name, newName) {
40314 var idx = indexLookup[name];
40315
40316 if(idx === undefined) return obj;
40317 changeTypes[idx] = changeTypes[idx] | NAME;
40318
40319 indexLookup[newName] = idx;
40320 delete indexLookup[name];
40321
40322 arr[idx][keyName] = newName;
40323
40324 return obj;
40325 },
40326 remove: function(name) {
40327 var idx = indexLookup[name];
40328
40329 if(idx === undefined) return obj;
40330
40331 var object = arr[idx];
40332 if(Object.keys(object).length > 2) {
40333 // This object contains more than just the key/value, so unset
40334 // the value without modifying the entry otherwise:
40335 changeTypes[idx] = changeTypes[idx] | VALUE;
40336 return obj.set(name, null);
40337 }
40338
40339 if(isSimpleValueProp) {
40340 for(i = idx; i < arr.length; i++) {
40341 changeTypes[i] = changeTypes[i] | BOTH;
40342 }
40343 for(i = idx; i < arr.length; i++) {
40344 indexLookup[arr[i][keyName]]--;
40345 }
40346 arr.splice(idx, 1);
40347 delete(indexLookup[name]);
40348 } else {
40349 // Perform this update *strictly* so we can check whether the result's
40350 // been pruned. If so, it's a removal. If not, it's a value unset only.
40351 nestedProperty(object, valueName).set(null);
40352
40353 // Now check if the top level nested property has any keys left. If so,
40354 // the object still has values so we only want to unset the key. If not,
40355 // the entire object can be removed since there's no other data.
40356 // var topLevelKeys = Object.keys(object[valueName.split('.')[0]] || []);
40357
40358 changeTypes[idx] = changeTypes[idx] | VALUE | UNSET;
40359 }
40360
40361 return obj;
40362 },
40363 constructUpdate: function() {
40364 var astr, idx;
40365 var update = {};
40366 var changed = Object.keys(changeTypes);
40367 for(var i = 0; i < changed.length; i++) {
40368 idx = changed[i];
40369 astr = path + '[' + idx + ']';
40370 if(arr[idx]) {
40371 if(changeTypes[idx] & NAME) {
40372 update[astr + '.' + keyName] = arr[idx][keyName];
40373 }
40374 if(changeTypes[idx] & VALUE) {
40375 if(isSimpleValueProp) {
40376 update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : arr[idx][valueName];
40377 } else {
40378 update[astr + '.' + valueName] = (changeTypes[idx] & UNSET) ? null : nestedProperty(arr[idx], valueName).get();
40379 }
40380 }
40381 } else {
40382 update[astr] = null;
40383 }
40384 }
40385
40386 return update;
40387 }
40388 };
40389
40390 return obj;
40391};
40392
40393},{"./nested_property":186}],181:[function(_dereq_,module,exports){
40394/**
40395* Copyright 2012-2020, Plotly, Inc.
40396* All rights reserved.
40397*
40398* This source code is licensed under the MIT license found in the
40399* LICENSE file in the root directory of this source tree.
40400*/
40401
40402
40403'use strict';
40404
40405var Registry = _dereq_('../registry');
40406
40407/**
40408 * localize: translate a string for the current locale
40409 *
40410 * @param {object} gd: the graphDiv for context
40411 * gd._context.locale determines the language (& optional region/country)
40412 * the dictionary for each locale may either be supplied in
40413 * gd._context.locales or globally via Plotly.register
40414 * @param {string} s: the string to translate
40415 */
40416module.exports = function localize(gd, s) {
40417 var locale = gd._context.locale;
40418
40419 /*
40420 * Priority of lookup:
40421 * contextDicts[locale],
40422 * registeredDicts[locale],
40423 * contextDicts[baseLocale], (if baseLocale is distinct)
40424 * registeredDicts[baseLocale]
40425 * Return the first translation we find.
40426 * This way if you have a regionalization you are allowed to specify
40427 * only what's different from the base locale, everything else will
40428 * fall back on the base.
40429 */
40430 for(var i = 0; i < 2; i++) {
40431 var locales = gd._context.locales;
40432 for(var j = 0; j < 2; j++) {
40433 var dict = (locales[locale] || {}).dictionary;
40434 if(dict) {
40435 var out = dict[s];
40436 if(out) return out;
40437 }
40438 locales = Registry.localeRegistry;
40439 }
40440
40441 var baseLocale = locale.split('-')[0];
40442 if(baseLocale === locale) break;
40443 locale = baseLocale;
40444 }
40445
40446 return s;
40447};
40448
40449},{"../registry":269}],182:[function(_dereq_,module,exports){
40450/**
40451* Copyright 2012-2020, Plotly, Inc.
40452* All rights reserved.
40453*
40454* This source code is licensed under the MIT license found in the
40455* LICENSE file in the root directory of this source tree.
40456*/
40457
40458'use strict';
40459
40460/* eslint-disable no-console */
40461
40462var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig;
40463
40464var notifier = _dereq_('./notifier');
40465
40466var loggers = module.exports = {};
40467
40468/**
40469 * ------------------------------------------
40470 * debugging tools
40471 * ------------------------------------------
40472 */
40473
40474loggers.log = function() {
40475 var i;
40476
40477 if(dfltConfig.logging > 1) {
40478 var messages = ['LOG:'];
40479 for(i = 0; i < arguments.length; i++) {
40480 messages.push(arguments[i]);
40481 }
40482 apply(console.trace || console.log, messages);
40483 }
40484
40485 if(dfltConfig.notifyOnLogging > 1) {
40486 var lines = [];
40487 for(i = 0; i < arguments.length; i++) {
40488 lines.push(arguments[i]);
40489 }
40490 notifier(lines.join('<br>'), 'long');
40491 }
40492};
40493
40494loggers.warn = function() {
40495 var i;
40496
40497 if(dfltConfig.logging > 0) {
40498 var messages = ['WARN:'];
40499 for(i = 0; i < arguments.length; i++) {
40500 messages.push(arguments[i]);
40501 }
40502 apply(console.trace || console.log, messages);
40503 }
40504
40505 if(dfltConfig.notifyOnLogging > 0) {
40506 var lines = [];
40507 for(i = 0; i < arguments.length; i++) {
40508 lines.push(arguments[i]);
40509 }
40510 notifier(lines.join('<br>'), 'stick');
40511 }
40512};
40513
40514loggers.error = function() {
40515 var i;
40516
40517 if(dfltConfig.logging > 0) {
40518 var messages = ['ERROR:'];
40519 for(i = 0; i < arguments.length; i++) {
40520 messages.push(arguments[i]);
40521 }
40522 apply(console.error, messages);
40523 }
40524
40525 if(dfltConfig.notifyOnLogging > 0) {
40526 var lines = [];
40527 for(i = 0; i < arguments.length; i++) {
40528 lines.push(arguments[i]);
40529 }
40530 notifier(lines.join('<br>'), 'stick');
40531 }
40532};
40533
40534/*
40535 * Robust apply, for IE9 where console.log doesn't support
40536 * apply like other functions do
40537 */
40538function apply(f, args) {
40539 if(f && f.apply) {
40540 try {
40541 // `this` should always be console, since here we're always
40542 // applying a method of the console object.
40543 f.apply(console, args);
40544 return;
40545 } catch(e) { /* in case apply failed, fall back on the code below */ }
40546 }
40547
40548 // no apply - just try calling the function on each arg independently
40549 for(var i = 0; i < args.length; i++) {
40550 try {
40551 f(args[i]);
40552 } catch(e) {
40553 // still fails - last resort simple console.log
40554 console.log(args[i]);
40555 }
40556 }
40557}
40558
40559},{"../plot_api/plot_config":210,"./notifier":188}],183:[function(_dereq_,module,exports){
40560/**
40561* Copyright 2012-2020, Plotly, Inc.
40562* All rights reserved.
40563*
40564* This source code is licensed under the MIT license found in the
40565* LICENSE file in the root directory of this source tree.
40566*/
40567
40568'use strict';
40569
40570var d3 = _dereq_('d3');
40571
40572/**
40573 * General helper to manage trace groups based on calcdata
40574 *
40575 * @param {d3.selection} traceLayer: a selection containing a single group
40576 * to draw these traces into
40577 * @param {array} cdModule: array of calcdata items for this
40578 * module and subplot combination. Assumes the calcdata item for each
40579 * trace is an array with the fullData trace attached to the first item.
40580 * @param {string} cls: the class attribute to give each trace group
40581 * so you can give multiple classes separated by spaces
40582 */
40583module.exports = function makeTraceGroups(traceLayer, cdModule, cls) {
40584 var traces = traceLayer.selectAll('g.' + cls.replace(/\s/g, '.'))
40585 .data(cdModule, function(cd) { return cd[0].trace.uid; });
40586
40587 traces.exit().remove();
40588
40589 traces.enter().append('g')
40590 .attr('class', cls);
40591
40592 traces.order();
40593
40594 // stash ref node to trace group in calcdata,
40595 // useful for (fast) styleOnSelect
40596 var k = traceLayer.classed('rangeplot') ? 'nodeRangePlot3' : 'node3';
40597 traces.each(function(cd) { cd[0][k] = d3.select(this); });
40598
40599 return traces;
40600};
40601
40602},{"d3":16}],184:[function(_dereq_,module,exports){
40603/**
40604* Copyright 2012-2020, Plotly, Inc.
40605* All rights reserved.
40606*
40607* This source code is licensed under the MIT license found in the
40608* LICENSE file in the root directory of this source tree.
40609*/
40610
40611
40612'use strict';
40613
40614
40615exports.init2dArray = function(rowLength, colLength) {
40616 var array = new Array(rowLength);
40617 for(var i = 0; i < rowLength; i++) array[i] = new Array(colLength);
40618 return array;
40619};
40620
40621/**
40622 * transpose a (possibly ragged) 2d array z. inspired by
40623 * http://stackoverflow.com/questions/17428587/
40624 * transposing-a-2d-array-in-javascript
40625 */
40626exports.transposeRagged = function(z) {
40627 var maxlen = 0;
40628 var zlen = z.length;
40629 var i, j;
40630 // Maximum row length:
40631 for(i = 0; i < zlen; i++) maxlen = Math.max(maxlen, z[i].length);
40632
40633 var t = new Array(maxlen);
40634 for(i = 0; i < maxlen; i++) {
40635 t[i] = new Array(zlen);
40636 for(j = 0; j < zlen; j++) t[i][j] = z[j][i];
40637 }
40638
40639 return t;
40640};
40641
40642// our own dot function so that we don't need to include numeric
40643exports.dot = function(x, y) {
40644 if(!(x.length && y.length) || x.length !== y.length) return null;
40645
40646 var len = x.length;
40647 var out;
40648 var i;
40649
40650 if(x[0].length) {
40651 // mat-vec or mat-mat
40652 out = new Array(len);
40653 for(i = 0; i < len; i++) out[i] = exports.dot(x[i], y);
40654 } else if(y[0].length) {
40655 // vec-mat
40656 var yTranspose = exports.transposeRagged(y);
40657 out = new Array(yTranspose.length);
40658 for(i = 0; i < yTranspose.length; i++) out[i] = exports.dot(x, yTranspose[i]);
40659 } else {
40660 // vec-vec
40661 out = 0;
40662 for(i = 0; i < len; i++) out += x[i] * y[i];
40663 }
40664
40665 return out;
40666};
40667
40668// translate by (x,y)
40669exports.translationMatrix = function(x, y) {
40670 return [[1, 0, x], [0, 1, y], [0, 0, 1]];
40671};
40672
40673// rotate by alpha around (0,0)
40674exports.rotationMatrix = function(alpha) {
40675 var a = alpha * Math.PI / 180;
40676 return [[Math.cos(a), -Math.sin(a), 0],
40677 [Math.sin(a), Math.cos(a), 0],
40678 [0, 0, 1]];
40679};
40680
40681// rotate by alpha around (x,y)
40682exports.rotationXYMatrix = function(a, x, y) {
40683 return exports.dot(
40684 exports.dot(exports.translationMatrix(x, y),
40685 exports.rotationMatrix(a)),
40686 exports.translationMatrix(-x, -y));
40687};
40688
40689// applies a 2D transformation matrix to either x and y params or an [x,y] array
40690exports.apply2DTransform = function(transform) {
40691 return function() {
40692 var args = arguments;
40693 if(args.length === 3) {
40694 args = args[0];
40695 }// from map
40696 var xy = arguments.length === 1 ? args[0] : [args[0], args[1]];
40697 return exports.dot(transform, [xy[0], xy[1], 1]).slice(0, 2);
40698 };
40699};
40700
40701// applies a 2D transformation matrix to an [x1,y1,x2,y2] array (to transform a segment)
40702exports.apply2DTransform2 = function(transform) {
40703 var at = exports.apply2DTransform(transform);
40704 return function(xys) {
40705 return at(xys.slice(0, 2)).concat(at(xys.slice(2, 4)));
40706 };
40707};
40708
40709},{}],185:[function(_dereq_,module,exports){
40710/**
40711* Copyright 2012-2020, Plotly, Inc.
40712* All rights reserved.
40713*
40714* This source code is licensed under the MIT license found in the
40715* LICENSE file in the root directory of this source tree.
40716*/
40717
40718'use strict';
40719
40720/**
40721 * sanitized modulus function that always returns in the range [0, d)
40722 * rather than (-d, 0] if v is negative
40723 */
40724function mod(v, d) {
40725 var out = v % d;
40726 return out < 0 ? out + d : out;
40727}
40728
40729/**
40730 * sanitized modulus function that always returns in the range [-d/2, d/2]
40731 * rather than (-d, 0] if v is negative
40732 */
40733function modHalf(v, d) {
40734 return Math.abs(v) > (d / 2) ?
40735 v - Math.round(v / d) * d :
40736 v;
40737}
40738
40739module.exports = {
40740 mod: mod,
40741 modHalf: modHalf
40742};
40743
40744},{}],186:[function(_dereq_,module,exports){
40745/**
40746* Copyright 2012-2020, Plotly, Inc.
40747* All rights reserved.
40748*
40749* This source code is licensed under the MIT license found in the
40750* LICENSE file in the root directory of this source tree.
40751*/
40752
40753
40754'use strict';
40755
40756var isNumeric = _dereq_('fast-isnumeric');
40757var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
40758
40759/**
40760 * convert a string s (such as 'xaxis.range[0]')
40761 * representing a property of nested object into set and get methods
40762 * also return the string and object so we don't have to keep track of them
40763 * allows [-1] for an array index, to set a property inside all elements
40764 * of an array
40765 * eg if obj = {arr: [{a: 1}, {a: 2}]}
40766 * you can do p = nestedProperty(obj, 'arr[-1].a')
40767 * but you cannot set the array itself this way, to do that
40768 * just set the whole array.
40769 * eg if obj = {arr: [1, 2, 3]}
40770 * you can't do nestedProperty(obj, 'arr[-1]').set(5)
40771 * but you can do nestedProperty(obj, 'arr').set([5, 5, 5])
40772 */
40773module.exports = function nestedProperty(container, propStr) {
40774 if(isNumeric(propStr)) propStr = String(propStr);
40775 else if(typeof propStr !== 'string' ||
40776 propStr.substr(propStr.length - 4) === '[-1]') {
40777 throw 'bad property string';
40778 }
40779
40780 var j = 0;
40781 var propParts = propStr.split('.');
40782 var indexed;
40783 var indices;
40784 var i;
40785
40786 // check for parts of the nesting hierarchy that are numbers (ie array elements)
40787 while(j < propParts.length) {
40788 // look for non-bracket chars, then any number of [##] blocks
40789 indexed = String(propParts[j]).match(/^([^\[\]]*)((\[\-?[0-9]*\])+)$/);
40790 if(indexed) {
40791 if(indexed[1]) propParts[j] = indexed[1];
40792 // allow propStr to start with bracketed array indices
40793 else if(j === 0) propParts.splice(0, 1);
40794 else throw 'bad property string';
40795
40796 indices = indexed[2]
40797 .substr(1, indexed[2].length - 2)
40798 .split('][');
40799
40800 for(i = 0; i < indices.length; i++) {
40801 j++;
40802 propParts.splice(j, 0, Number(indices[i]));
40803 }
40804 }
40805 j++;
40806 }
40807
40808 if(typeof container !== 'object') {
40809 return badContainer(container, propStr, propParts);
40810 }
40811
40812 return {
40813 set: npSet(container, propParts, propStr),
40814 get: npGet(container, propParts),
40815 astr: propStr,
40816 parts: propParts,
40817 obj: container
40818 };
40819};
40820
40821function npGet(cont, parts) {
40822 return function() {
40823 var curCont = cont;
40824 var curPart;
40825 var allSame;
40826 var out;
40827 var i;
40828 var j;
40829
40830 for(i = 0; i < parts.length - 1; i++) {
40831 curPart = parts[i];
40832 if(curPart === -1) {
40833 allSame = true;
40834 out = [];
40835 for(j = 0; j < curCont.length; j++) {
40836 out[j] = npGet(curCont[j], parts.slice(i + 1))();
40837 if(out[j] !== out[0]) allSame = false;
40838 }
40839 return allSame ? out[0] : out;
40840 }
40841 if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) {
40842 return undefined;
40843 }
40844 curCont = curCont[curPart];
40845 if(typeof curCont !== 'object' || curCont === null) {
40846 return undefined;
40847 }
40848 }
40849
40850 // only hit this if parts.length === 1
40851 if(typeof curCont !== 'object' || curCont === null) return undefined;
40852
40853 out = curCont[parts[i]];
40854 if(out === null) return undefined;
40855 return out;
40856 };
40857}
40858
40859/*
40860 * Can this value be deleted? We can delete `undefined`, and `null` except INSIDE an
40861 * *args* array.
40862 *
40863 * Previously we also deleted some `{}` and `[]`, in order to try and make set/unset
40864 * a net noop; but this causes far more complication than it's worth, and still had
40865 * lots of exceptions. See https://github.com/plotly/plotly.js/issues/1410
40866 *
40867 * *args* arrays get passed directly to API methods and we should respect null if
40868 * the user put it there, but otherwise null is deleted as we use it as code
40869 * in restyle/relayout/update for "delete this value" whereas undefined means
40870 * "ignore this edit"
40871 */
40872var ARGS_PATTERN = /(^|\.)args\[/;
40873function isDeletable(val, propStr) {
40874 return (val === undefined) || (val === null && !propStr.match(ARGS_PATTERN));
40875}
40876
40877function npSet(cont, parts, propStr) {
40878 return function(val) {
40879 var curCont = cont;
40880 var propPart = '';
40881 var containerLevels = [[cont, propPart]];
40882 var toDelete = isDeletable(val, propStr);
40883 var curPart;
40884 var i;
40885
40886 for(i = 0; i < parts.length - 1; i++) {
40887 curPart = parts[i];
40888
40889 if(typeof curPart === 'number' && !isArrayOrTypedArray(curCont)) {
40890 throw 'array index but container is not an array';
40891 }
40892
40893 // handle special -1 array index
40894 if(curPart === -1) {
40895 toDelete = !setArrayAll(curCont, parts.slice(i + 1), val, propStr);
40896 if(toDelete) break;
40897 else return;
40898 }
40899
40900 if(!checkNewContainer(curCont, curPart, parts[i + 1], toDelete)) {
40901 break;
40902 }
40903
40904 curCont = curCont[curPart];
40905
40906 if(typeof curCont !== 'object' || curCont === null) {
40907 throw 'container is not an object';
40908 }
40909
40910 propPart = joinPropStr(propPart, curPart);
40911
40912 containerLevels.push([curCont, propPart]);
40913 }
40914
40915 if(toDelete) {
40916 if(i === parts.length - 1) {
40917 delete curCont[parts[i]];
40918
40919 // The one bit of pruning we still do: drop `undefined` from the end of arrays.
40920 // In case someone has already unset previous items, continue until we hit a
40921 // non-undefined value.
40922 if(Array.isArray(curCont) && +parts[i] === curCont.length - 1) {
40923 while(curCont.length && curCont[curCont.length - 1] === undefined) {
40924 curCont.pop();
40925 }
40926 }
40927 }
40928 } else curCont[parts[i]] = val;
40929 };
40930}
40931
40932function joinPropStr(propStr, newPart) {
40933 var toAdd = newPart;
40934 if(isNumeric(newPart)) toAdd = '[' + newPart + ']';
40935 else if(propStr) toAdd = '.' + newPart;
40936
40937 return propStr + toAdd;
40938}
40939
40940// handle special -1 array index
40941function setArrayAll(containerArray, innerParts, val, propStr) {
40942 var arrayVal = isArrayOrTypedArray(val);
40943 var allSet = true;
40944 var thisVal = val;
40945 var thisPropStr = propStr.replace('-1', 0);
40946 var deleteThis = arrayVal ? false : isDeletable(val, thisPropStr);
40947 var firstPart = innerParts[0];
40948 var i;
40949
40950 for(i = 0; i < containerArray.length; i++) {
40951 thisPropStr = propStr.replace('-1', i);
40952 if(arrayVal) {
40953 thisVal = val[i % val.length];
40954 deleteThis = isDeletable(thisVal, thisPropStr);
40955 }
40956 if(deleteThis) allSet = false;
40957 if(!checkNewContainer(containerArray, i, firstPart, deleteThis)) {
40958 continue;
40959 }
40960 npSet(containerArray[i], innerParts, propStr.replace('-1', i))(thisVal);
40961 }
40962 return allSet;
40963}
40964
40965/**
40966 * make new sub-container as needed.
40967 * returns false if there's no container and none is needed
40968 * because we're only deleting an attribute
40969 */
40970function checkNewContainer(container, part, nextPart, toDelete) {
40971 if(container[part] === undefined) {
40972 if(toDelete) return false;
40973
40974 if(typeof nextPart === 'number') container[part] = [];
40975 else container[part] = {};
40976 }
40977 return true;
40978}
40979
40980function badContainer(container, propStr, propParts) {
40981 return {
40982 set: function() { throw 'bad container'; },
40983 get: function() {},
40984 astr: propStr,
40985 parts: propParts,
40986 obj: container
40987 };
40988}
40989
40990},{"./array":165,"fast-isnumeric":18}],187:[function(_dereq_,module,exports){
40991/**
40992* Copyright 2012-2020, Plotly, Inc.
40993* All rights reserved.
40994*
40995* This source code is licensed under the MIT license found in the
40996* LICENSE file in the root directory of this source tree.
40997*/
40998
40999'use strict';
41000
41001// Simple helper functions
41002// none of these need any external deps
41003
41004module.exports = function noop() {};
41005
41006},{}],188:[function(_dereq_,module,exports){
41007/**
41008* Copyright 2012-2020, Plotly, Inc.
41009* All rights reserved.
41010*
41011* This source code is licensed under the MIT license found in the
41012* LICENSE file in the root directory of this source tree.
41013*/
41014
41015
41016'use strict';
41017
41018var d3 = _dereq_('d3');
41019var isNumeric = _dereq_('fast-isnumeric');
41020
41021var NOTEDATA = [];
41022
41023/**
41024 * notifier
41025 * @param {String} text The person's user name
41026 * @param {Number} [delay=1000] The delay time in milliseconds
41027 * or 'long' which provides 2000 ms delay time.
41028 * @return {undefined} this function does not return a value
41029 */
41030module.exports = function(text, displayLength) {
41031 if(NOTEDATA.indexOf(text) !== -1) return;
41032
41033 NOTEDATA.push(text);
41034
41035 var ts = 1000;
41036 if(isNumeric(displayLength)) ts = displayLength;
41037 else if(displayLength === 'long') ts = 3000;
41038
41039 var notifierContainer = d3.select('body')
41040 .selectAll('.plotly-notifier')
41041 .data([0]);
41042 notifierContainer.enter()
41043 .append('div')
41044 .classed('plotly-notifier', true);
41045
41046 var notes = notifierContainer.selectAll('.notifier-note').data(NOTEDATA);
41047
41048 function killNote(transition) {
41049 transition
41050 .duration(700)
41051 .style('opacity', 0)
41052 .each('end', function(thisText) {
41053 var thisIndex = NOTEDATA.indexOf(thisText);
41054 if(thisIndex !== -1) NOTEDATA.splice(thisIndex, 1);
41055 d3.select(this).remove();
41056 });
41057 }
41058
41059 notes.enter().append('div')
41060 .classed('notifier-note', true)
41061 .style('opacity', 0)
41062 .each(function(thisText) {
41063 var note = d3.select(this);
41064
41065 note.append('button')
41066 .classed('notifier-close', true)
41067 .html('&times;')
41068 .on('click', function() {
41069 note.transition().call(killNote);
41070 });
41071
41072 var p = note.append('p');
41073 var lines = thisText.split(/<br\s*\/?>/g);
41074 for(var i = 0; i < lines.length; i++) {
41075 if(i) p.append('br');
41076 p.append('span').text(lines[i]);
41077 }
41078
41079 if(displayLength === 'stick') {
41080 note.transition()
41081 .duration(350)
41082 .style('opacity', 1);
41083 } else {
41084 note.transition()
41085 .duration(700)
41086 .style('opacity', 1)
41087 .transition()
41088 .delay(ts)
41089 .call(killNote);
41090 }
41091 });
41092};
41093
41094},{"d3":16,"fast-isnumeric":18}],189:[function(_dereq_,module,exports){
41095/**
41096* Copyright 2012-2020, Plotly, Inc.
41097* All rights reserved.
41098*
41099* This source code is licensed under the MIT license found in the
41100* LICENSE file in the root directory of this source tree.
41101*/
41102
41103
41104'use strict';
41105
41106var setCursor = _dereq_('./setcursor');
41107
41108var STASHATTR = 'data-savedcursor';
41109var NO_CURSOR = '!!';
41110
41111/*
41112 * works with our CSS cursor classes (see css/_cursor.scss)
41113 * to override a previous cursor set on d3 single-element selections,
41114 * by moving the name of the original cursor to the data-savedcursor attr.
41115 * omit cursor to revert to the previously set value.
41116 */
41117module.exports = function overrideCursor(el3, csr) {
41118 var savedCursor = el3.attr(STASHATTR);
41119 if(csr) {
41120 if(!savedCursor) {
41121 var classes = (el3.attr('class') || '').split(' ');
41122 for(var i = 0; i < classes.length; i++) {
41123 var cls = classes[i];
41124 if(cls.indexOf('cursor-') === 0) {
41125 el3.attr(STASHATTR, cls.substr(7))
41126 .classed(cls, false);
41127 }
41128 }
41129 if(!el3.attr(STASHATTR)) {
41130 el3.attr(STASHATTR, NO_CURSOR);
41131 }
41132 }
41133 setCursor(el3, csr);
41134 } else if(savedCursor) {
41135 el3.attr(STASHATTR, null);
41136
41137 if(savedCursor === NO_CURSOR) setCursor(el3);
41138 else setCursor(el3, savedCursor);
41139 }
41140};
41141
41142},{"./setcursor":197}],190:[function(_dereq_,module,exports){
41143/**
41144* Copyright 2012-2020, Plotly, Inc.
41145* All rights reserved.
41146*
41147* This source code is licensed under the MIT license found in the
41148* LICENSE file in the root directory of this source tree.
41149*/
41150
41151
41152'use strict';
41153
41154var dot = _dereq_('./matrix').dot;
41155var BADNUM = _dereq_('../constants/numerical').BADNUM;
41156
41157var polygon = module.exports = {};
41158
41159/**
41160 * Turn an array of [x, y] pairs into a polygon object
41161 * that can test if points are inside it
41162 *
41163 * @param ptsIn Array of [x, y] pairs
41164 *
41165 * @returns polygon Object {xmin, xmax, ymin, ymax, pts, contains}
41166 * (x|y)(min|max) are the bounding rect of the polygon
41167 * pts is the original array, with the first pair repeated at the end
41168 * contains is a function: (pt, omitFirstEdge)
41169 * pt is the [x, y] pair to test
41170 * omitFirstEdge truthy means points exactly on the first edge don't
41171 * count. This is for use adding one polygon to another so we
41172 * don't double-count the edge where they meet.
41173 * returns boolean: is pt inside the polygon (including on its edges)
41174 */
41175polygon.tester = function tester(ptsIn) {
41176 var pts = ptsIn.slice();
41177 var xmin = pts[0][0];
41178 var xmax = xmin;
41179 var ymin = pts[0][1];
41180 var ymax = ymin;
41181 var i;
41182
41183 pts.push(pts[0]);
41184 for(i = 1; i < pts.length; i++) {
41185 xmin = Math.min(xmin, pts[i][0]);
41186 xmax = Math.max(xmax, pts[i][0]);
41187 ymin = Math.min(ymin, pts[i][1]);
41188 ymax = Math.max(ymax, pts[i][1]);
41189 }
41190
41191 // do we have a rectangle? Handle this here, so we can use the same
41192 // tester for the rectangular case without sacrificing speed
41193
41194 var isRect = false;
41195 var rectFirstEdgeTest;
41196
41197 if(pts.length === 5) {
41198 if(pts[0][0] === pts[1][0]) { // vert, horz, vert, horz
41199 if(pts[2][0] === pts[3][0] &&
41200 pts[0][1] === pts[3][1] &&
41201 pts[1][1] === pts[2][1]) {
41202 isRect = true;
41203 rectFirstEdgeTest = function(pt) { return pt[0] === pts[0][0]; };
41204 }
41205 } else if(pts[0][1] === pts[1][1]) { // horz, vert, horz, vert
41206 if(pts[2][1] === pts[3][1] &&
41207 pts[0][0] === pts[3][0] &&
41208 pts[1][0] === pts[2][0]) {
41209 isRect = true;
41210 rectFirstEdgeTest = function(pt) { return pt[1] === pts[0][1]; };
41211 }
41212 }
41213 }
41214
41215 function rectContains(pt, omitFirstEdge) {
41216 var x = pt[0];
41217 var y = pt[1];
41218
41219 if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) {
41220 // pt is outside the bounding box of polygon
41221 return false;
41222 }
41223 if(omitFirstEdge && rectFirstEdgeTest(pt)) return false;
41224
41225 return true;
41226 }
41227
41228 function contains(pt, omitFirstEdge) {
41229 var x = pt[0];
41230 var y = pt[1];
41231
41232 if(x === BADNUM || x < xmin || x > xmax || y === BADNUM || y < ymin || y > ymax) {
41233 // pt is outside the bounding box of polygon
41234 return false;
41235 }
41236
41237 var imax = pts.length;
41238 var x1 = pts[0][0];
41239 var y1 = pts[0][1];
41240 var crossings = 0;
41241 var i;
41242 var x0;
41243 var y0;
41244 var xmini;
41245 var ycross;
41246
41247 for(i = 1; i < imax; i++) {
41248 // find all crossings of a vertical line upward from pt with
41249 // polygon segments
41250 // crossings exactly at xmax don't count, unless the point is
41251 // exactly on the segment, then it counts as inside.
41252 x0 = x1;
41253 y0 = y1;
41254 x1 = pts[i][0];
41255 y1 = pts[i][1];
41256 xmini = Math.min(x0, x1);
41257
41258 if(x < xmini || x > Math.max(x0, x1) || y > Math.max(y0, y1)) {
41259 // outside the bounding box of this segment, it's only a crossing
41260 // if it's below the box.
41261
41262 continue;
41263 } else if(y < Math.min(y0, y1)) {
41264 // don't count the left-most point of the segment as a crossing
41265 // because we don't want to double-count adjacent crossings
41266 // UNLESS the polygon turns past vertical at exactly this x
41267 // Note that this is repeated below, but we can't factor it out
41268 // because
41269 if(x !== xmini) crossings++;
41270 } else {
41271 // inside the bounding box, check the actual line intercept
41272
41273 // vertical segment - we know already that the point is exactly
41274 // on the segment, so mark the crossing as exactly at the point.
41275 if(x1 === x0) ycross = y;
41276 // any other angle
41277 else ycross = y0 + (x - x0) * (y1 - y0) / (x1 - x0);
41278
41279 // exactly on the edge: counts as inside the polygon, unless it's the
41280 // first edge and we're omitting it.
41281 if(y === ycross) {
41282 if(i === 1 && omitFirstEdge) return false;
41283 return true;
41284 }
41285
41286 if(y <= ycross && x !== xmini) crossings++;
41287 }
41288 }
41289
41290 // if we've gotten this far, odd crossings means inside, even is outside
41291 return crossings % 2 === 1;
41292 }
41293
41294 // detect if poly is degenerate
41295 var degenerate = true;
41296 var lastPt = pts[0];
41297 for(i = 1; i < pts.length; i++) {
41298 if(lastPt[0] !== pts[i][0] || lastPt[1] !== pts[i][1]) {
41299 degenerate = false;
41300 break;
41301 }
41302 }
41303
41304 return {
41305 xmin: xmin,
41306 xmax: xmax,
41307 ymin: ymin,
41308 ymax: ymax,
41309 pts: pts,
41310 contains: isRect ? rectContains : contains,
41311 isRect: isRect,
41312 degenerate: degenerate
41313 };
41314};
41315
41316/**
41317 * Test if a segment of a points array is bent or straight
41318 *
41319 * @param pts Array of [x, y] pairs
41320 * @param start the index of the proposed start of the straight section
41321 * @param end the index of the proposed end point
41322 * @param tolerance the max distance off the line connecting start and end
41323 * before the line counts as bent
41324 * @returns boolean: true means this segment is bent, false means straight
41325 */
41326polygon.isSegmentBent = function isSegmentBent(pts, start, end, tolerance) {
41327 var startPt = pts[start];
41328 var segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]];
41329 var segmentSquared = dot(segment, segment);
41330 var segmentLen = Math.sqrt(segmentSquared);
41331 var unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen];
41332 var i;
41333 var part;
41334 var partParallel;
41335
41336 for(i = start + 1; i < end; i++) {
41337 part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
41338 partParallel = dot(part, segment);
41339
41340 if(partParallel < 0 || partParallel > segmentSquared ||
41341 Math.abs(dot(part, unitPerp)) > tolerance) return true;
41342 }
41343 return false;
41344};
41345
41346/**
41347 * Make a filtering polygon, to minimize the number of segments
41348 *
41349 * @param pts Array of [x, y] pairs (must start with at least 1 pair)
41350 * @param tolerance the maximum deviation from straight allowed for
41351 * removing points to simplify the polygon
41352 *
41353 * @returns Object {addPt, raw, filtered}
41354 * addPt is a function(pt: [x, y] pair) to add a raw point and
41355 * continue filtering
41356 * raw is all the input points
41357 * filtered is the resulting filtered Array of [x, y] pairs
41358 */
41359polygon.filter = function filter(pts, tolerance) {
41360 var ptsFiltered = [pts[0]];
41361 var doneRawIndex = 0;
41362 var doneFilteredIndex = 0;
41363
41364 function addPt(pt) {
41365 pts.push(pt);
41366 var prevFilterLen = ptsFiltered.length;
41367 var iLast = doneRawIndex;
41368 ptsFiltered.splice(doneFilteredIndex + 1);
41369
41370 for(var i = iLast + 1; i < pts.length; i++) {
41371 if(i === pts.length - 1 || polygon.isSegmentBent(pts, iLast, i + 1, tolerance)) {
41372 ptsFiltered.push(pts[i]);
41373 if(ptsFiltered.length < prevFilterLen - 2) {
41374 doneRawIndex = i;
41375 doneFilteredIndex = ptsFiltered.length - 1;
41376 }
41377 iLast = i;
41378 }
41379 }
41380 }
41381
41382 if(pts.length > 1) {
41383 var lastPt = pts.pop();
41384 addPt(lastPt);
41385 }
41386
41387 return {
41388 addPt: addPt,
41389 raw: pts,
41390 filtered: ptsFiltered
41391 };
41392};
41393
41394},{"../constants/numerical":158,"./matrix":184}],191:[function(_dereq_,module,exports){
41395/**
41396* Copyright 2012-2020, Plotly, Inc.
41397* All rights reserved.
41398*
41399* This source code is licensed under the MIT license found in the
41400* LICENSE file in the root directory of this source tree.
41401*/
41402
41403'use strict';
41404
41405/**
41406 * Push array with unique items
41407 *
41408 * Ignores falsy items, except 0 so we can use it to construct arrays of indices.
41409 *
41410 * @param {array} array
41411 * array to be filled
41412 * @param {any} item
41413 * item to be or not to be inserted
41414 * @return {array}
41415 * ref to array (now possibly containing one more item)
41416 *
41417 */
41418module.exports = function pushUnique(array, item) {
41419 if(item instanceof RegExp) {
41420 var itemStr = item.toString();
41421 for(var i = 0; i < array.length; i++) {
41422 if(array[i] instanceof RegExp && array[i].toString() === itemStr) {
41423 return array;
41424 }
41425 }
41426 array.push(item);
41427 } else if((item || item === 0) && array.indexOf(item) === -1) array.push(item);
41428
41429 return array;
41430};
41431
41432},{}],192:[function(_dereq_,module,exports){
41433/**
41434* Copyright 2012-2020, Plotly, Inc.
41435* All rights reserved.
41436*
41437* This source code is licensed under the MIT license found in the
41438* LICENSE file in the root directory of this source tree.
41439*/
41440
41441'use strict';
41442
41443var Lib = _dereq_('../lib');
41444var dfltConfig = _dereq_('../plot_api/plot_config').dfltConfig;
41445
41446/**
41447 * Copy arg array *without* removing `undefined` values from objects.
41448 *
41449 * @param gd
41450 * @param args
41451 * @returns {Array}
41452 */
41453function copyArgArray(gd, args) {
41454 var copy = [];
41455 var arg;
41456
41457 for(var i = 0; i < args.length; i++) {
41458 arg = args[i];
41459
41460 if(arg === gd) copy[i] = arg;
41461 else if(typeof arg === 'object') {
41462 copy[i] = Array.isArray(arg) ?
41463 Lib.extendDeep([], arg) :
41464 Lib.extendDeepAll({}, arg);
41465 } else copy[i] = arg;
41466 }
41467
41468 return copy;
41469}
41470
41471
41472// -----------------------------------------------------
41473// Undo/Redo queue for plots
41474// -----------------------------------------------------
41475
41476
41477var queue = {};
41478
41479// TODO: disable/enable undo and redo buttons appropriately
41480
41481/**
41482 * Add an item to the undoQueue for a graphDiv
41483 *
41484 * @param gd
41485 * @param undoFunc Function undo this operation
41486 * @param undoArgs Args to supply undoFunc with
41487 * @param redoFunc Function to redo this operation
41488 * @param redoArgs Args to supply redoFunc with
41489 */
41490queue.add = function(gd, undoFunc, undoArgs, redoFunc, redoArgs) {
41491 var queueObj,
41492 queueIndex;
41493
41494 // make sure we have the queue and our position in it
41495 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41496 queueIndex = gd.undoQueue.index;
41497
41498 // if we're already playing an undo or redo, or if this is an auto operation
41499 // (like pane resize... any others?) then we don't save this to the undo queue
41500 if(gd.autoplay) {
41501 if(!gd.undoQueue.inSequence) gd.autoplay = false;
41502 return;
41503 }
41504
41505 // if we're not in a sequence or are just starting, we need a new queue item
41506 if(!gd.undoQueue.sequence || gd.undoQueue.beginSequence) {
41507 queueObj = {undo: {calls: [], args: []}, redo: {calls: [], args: []}};
41508 gd.undoQueue.queue.splice(queueIndex, gd.undoQueue.queue.length - queueIndex, queueObj);
41509 gd.undoQueue.index += 1;
41510 } else {
41511 queueObj = gd.undoQueue.queue[queueIndex - 1];
41512 }
41513 gd.undoQueue.beginSequence = false;
41514
41515 // we unshift to handle calls for undo in a forward for loop later
41516 if(queueObj) {
41517 queueObj.undo.calls.unshift(undoFunc);
41518 queueObj.undo.args.unshift(undoArgs);
41519 queueObj.redo.calls.push(redoFunc);
41520 queueObj.redo.args.push(redoArgs);
41521 }
41522
41523 if(gd.undoQueue.queue.length > dfltConfig.queueLength) {
41524 gd.undoQueue.queue.shift();
41525 gd.undoQueue.index--;
41526 }
41527};
41528
41529/**
41530 * Begin a sequence of undoQueue changes
41531 *
41532 * @param gd
41533 */
41534queue.startSequence = function(gd) {
41535 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41536 gd.undoQueue.sequence = true;
41537 gd.undoQueue.beginSequence = true;
41538};
41539
41540/**
41541 * Stop a sequence of undoQueue changes
41542 *
41543 * Call this *after* you're sure your undo chain has ended
41544 *
41545 * @param gd
41546 */
41547queue.stopSequence = function(gd) {
41548 gd.undoQueue = gd.undoQueue || {index: 0, queue: [], sequence: false};
41549 gd.undoQueue.sequence = false;
41550 gd.undoQueue.beginSequence = false;
41551};
41552
41553/**
41554 * Move one step back in the undo queue, and undo the object there.
41555 *
41556 * @param gd
41557 */
41558queue.undo = function undo(gd) {
41559 var queueObj, i;
41560
41561 if(gd.framework && gd.framework.isPolar) {
41562 gd.framework.undo();
41563 return;
41564 }
41565 if(gd.undoQueue === undefined ||
41566 isNaN(gd.undoQueue.index) ||
41567 gd.undoQueue.index <= 0) {
41568 return;
41569 }
41570
41571 // index is pointing to next *forward* queueObj, point to the one we're undoing
41572 gd.undoQueue.index--;
41573
41574 // get the queueObj for instructions on how to undo
41575 queueObj = gd.undoQueue.queue[gd.undoQueue.index];
41576
41577 // this sequence keeps things from adding to the queue during undo/redo
41578 gd.undoQueue.inSequence = true;
41579 for(i = 0; i < queueObj.undo.calls.length; i++) {
41580 queue.plotDo(gd, queueObj.undo.calls[i], queueObj.undo.args[i]);
41581 }
41582 gd.undoQueue.inSequence = false;
41583 gd.autoplay = false;
41584};
41585
41586/**
41587 * Redo the current object in the undo, then move forward in the queue.
41588 *
41589 * @param gd
41590 */
41591queue.redo = function redo(gd) {
41592 var queueObj, i;
41593
41594 if(gd.framework && gd.framework.isPolar) {
41595 gd.framework.redo();
41596 return;
41597 }
41598 if(gd.undoQueue === undefined ||
41599 isNaN(gd.undoQueue.index) ||
41600 gd.undoQueue.index >= gd.undoQueue.queue.length) {
41601 return;
41602 }
41603
41604 // get the queueObj for instructions on how to undo
41605 queueObj = gd.undoQueue.queue[gd.undoQueue.index];
41606
41607 // this sequence keeps things from adding to the queue during undo/redo
41608 gd.undoQueue.inSequence = true;
41609 for(i = 0; i < queueObj.redo.calls.length; i++) {
41610 queue.plotDo(gd, queueObj.redo.calls[i], queueObj.redo.args[i]);
41611 }
41612 gd.undoQueue.inSequence = false;
41613 gd.autoplay = false;
41614
41615 // index is pointing to the thing we just redid, move it
41616 gd.undoQueue.index++;
41617};
41618
41619/**
41620 * Called by undo/redo to make the actual changes.
41621 *
41622 * Not meant to be called publically, but included for mocking out in tests.
41623 *
41624 * @param gd
41625 * @param func
41626 * @param args
41627 */
41628queue.plotDo = function(gd, func, args) {
41629 gd.autoplay = true;
41630
41631 // this *won't* copy gd and it preserves `undefined` properties!
41632 args = copyArgArray(gd, args);
41633
41634 // call the supplied function
41635 func.apply(null, args);
41636};
41637
41638module.exports = queue;
41639
41640},{"../lib":178,"../plot_api/plot_config":210}],193:[function(_dereq_,module,exports){
41641/**
41642* Copyright 2012-2020, Plotly, Inc.
41643* All rights reserved.
41644*
41645* This source code is licensed under the MIT license found in the
41646* LICENSE file in the root directory of this source tree.
41647*/
41648
41649'use strict';
41650
41651/*
41652 * make a regex for matching counter ids/names ie xaxis, xaxis2, xaxis10...
41653 *
41654 * @param {string} head: the head of the pattern, eg 'x' matches 'x', 'x2', 'x10' etc.
41655 * 'xy' is a special case for cartesian subplots: it matches 'x2y3' etc
41656 * @param {Optional(string)} tail: a fixed piece after the id
41657 * eg counterRegex('scene', '.annotations') for scene2.annotations etc.
41658 * @param {boolean} openEnded: if true, the string may continue past the match.
41659 * @param {boolean} matchBeginning: if false, the string may start before the match.
41660 */
41661exports.counter = function(head, tail, openEnded, matchBeginning) {
41662 var fullTail = (tail || '') + (openEnded ? '' : '$');
41663 var startWithPrefix = matchBeginning === false ? '' : '^';
41664 if(head === 'xy') {
41665 return new RegExp(startWithPrefix + 'x([2-9]|[1-9][0-9]+)?y([2-9]|[1-9][0-9]+)?' + fullTail);
41666 }
41667 return new RegExp(startWithPrefix + head + '([2-9]|[1-9][0-9]+)?' + fullTail);
41668};
41669
41670},{}],194:[function(_dereq_,module,exports){
41671/**
41672* Copyright 2012-2020, Plotly, Inc.
41673* All rights reserved.
41674*
41675* This source code is licensed under the MIT license found in the
41676* LICENSE file in the root directory of this source tree.
41677*/
41678
41679
41680'use strict';
41681
41682// ASCEND: chop off the last nesting level - either [<n>] or .<key> - to ascend
41683// the attribute tree. the remaining attrString is in match[1]
41684var ASCEND = /^(.*)(\.[^\.\[\]]+|\[\d\])$/;
41685
41686// SIMPLEATTR: is this an un-nested attribute? (no dots or brackets)
41687var SIMPLEATTR = /^[^\.\[\]]+$/;
41688
41689/*
41690 * calculate a relative attribute string, similar to a relative path
41691 *
41692 * @param {string} baseAttr:
41693 * an attribute string, such as 'annotations[3].x'. The "current location"
41694 * is the attribute string minus the last component ('annotations[3]')
41695 * @param {string} relativeAttr:
41696 * a route to the desired attribute string, using '^' to ascend
41697 *
41698 * @return {string} attrString:
41699 * for example:
41700 * relativeAttr('annotations[3].x', 'y') = 'annotations[3].y'
41701 * relativeAttr('annotations[3].x', '^[2].z') = 'annotations[2].z'
41702 * relativeAttr('annotations[3].x', '^^margin') = 'margin'
41703 * relativeAttr('annotations[3].x', '^^margin.r') = 'margin.r'
41704 */
41705module.exports = function(baseAttr, relativeAttr) {
41706 while(relativeAttr) {
41707 var match = baseAttr.match(ASCEND);
41708
41709 if(match) baseAttr = match[1];
41710 else if(baseAttr.match(SIMPLEATTR)) baseAttr = '';
41711 else throw new Error('bad relativeAttr call:' + [baseAttr, relativeAttr]);
41712
41713 if(relativeAttr.charAt(0) === '^') relativeAttr = relativeAttr.slice(1);
41714 else break;
41715 }
41716
41717 if(baseAttr && relativeAttr.charAt(0) !== '[') {
41718 return baseAttr + '.' + relativeAttr;
41719 }
41720 return baseAttr + relativeAttr;
41721};
41722
41723},{}],195:[function(_dereq_,module,exports){
41724/**
41725* Copyright 2012-2020, Plotly, Inc.
41726* All rights reserved.
41727*
41728* This source code is licensed under the MIT license found in the
41729* LICENSE file in the root directory of this source tree.
41730*/
41731
41732
41733'use strict';
41734
41735var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
41736var isPlainObject = _dereq_('./is_plain_object');
41737
41738/**
41739 * Relink private _keys and keys with a function value from one container
41740 * to the new container.
41741 * Relink means copying if object is pass-by-value and adding a reference
41742 * if object is pass-by-ref.
41743 * This prevents deepCopying massive structures like a webgl context.
41744 */
41745module.exports = function relinkPrivateKeys(toContainer, fromContainer) {
41746 for(var k in fromContainer) {
41747 var fromVal = fromContainer[k];
41748 var toVal = toContainer[k];
41749
41750 if(toVal === fromVal) continue;
41751 if(toContainer.matches && k === '_categoriesMap') continue;
41752
41753 if(k.charAt(0) === '_' || typeof fromVal === 'function') {
41754 // if it already exists at this point, it's something
41755 // that we recreate each time around, so ignore it
41756 if(k in toContainer) continue;
41757
41758 toContainer[k] = fromVal;
41759 } else if(isArrayOrTypedArray(fromVal) && isArrayOrTypedArray(toVal) && isPlainObject(fromVal[0])) {
41760 // filter out data_array items that can contain user objects
41761 // most of the time the toVal === fromVal check will catch these early
41762 // but if the user makes new ones we also don't want to recurse in.
41763 if(k === 'customdata' || k === 'ids') continue;
41764
41765 // recurse into arrays containers
41766 var minLen = Math.min(fromVal.length, toVal.length);
41767 for(var j = 0; j < minLen; j++) {
41768 if((toVal[j] !== fromVal[j]) && isPlainObject(fromVal[j]) && isPlainObject(toVal[j])) {
41769 relinkPrivateKeys(toVal[j], fromVal[j]);
41770 }
41771 }
41772 } else if(isPlainObject(fromVal) && isPlainObject(toVal)) {
41773 // recurse into objects, but only if they still exist
41774 relinkPrivateKeys(toVal, fromVal);
41775
41776 if(!Object.keys(toVal).length) delete toContainer[k];
41777 }
41778 }
41779};
41780
41781},{"./array":165,"./is_plain_object":179}],196:[function(_dereq_,module,exports){
41782/**
41783* Copyright 2012-2020, Plotly, Inc.
41784* All rights reserved.
41785*
41786* This source code is licensed under the MIT license found in the
41787* LICENSE file in the root directory of this source tree.
41788*/
41789
41790
41791'use strict';
41792
41793var isNumeric = _dereq_('fast-isnumeric');
41794var loggers = _dereq_('./loggers');
41795var identity = _dereq_('./identity');
41796var BADNUM = _dereq_('../constants/numerical').BADNUM;
41797
41798// don't trust floating point equality - fraction of bin size to call
41799// "on the line" and ensure that they go the right way specified by
41800// linelow
41801var roundingError = 1e-9;
41802
41803
41804/**
41805 * findBin - find the bin for val - note that it can return outside the
41806 * bin range any pos. or neg. integer for linear bins, or -1 or
41807 * bins.length-1 for explicit.
41808 * bins is either an object {start,size,end} or an array length #bins+1
41809 * bins can be either increasing or decreasing but must be monotonic
41810 * for linear bins, we can just calculate. For listed bins, run a binary
41811 * search linelow (truthy) says the bin boundary should be attributed to
41812 * the lower bin rather than the default upper bin
41813 */
41814exports.findBin = function(val, bins, linelow) {
41815 if(isNumeric(bins.start)) {
41816 return linelow ?
41817 Math.ceil((val - bins.start) / bins.size - roundingError) - 1 :
41818 Math.floor((val - bins.start) / bins.size + roundingError);
41819 } else {
41820 var n1 = 0;
41821 var n2 = bins.length;
41822 var c = 0;
41823 var binSize = (n2 > 1) ? (bins[n2 - 1] - bins[0]) / (n2 - 1) : 1;
41824 var n, test;
41825 if(binSize >= 0) {
41826 test = linelow ? lessThan : lessOrEqual;
41827 } else {
41828 test = linelow ? greaterOrEqual : greaterThan;
41829 }
41830 val += binSize * roundingError * (linelow ? -1 : 1) * (binSize >= 0 ? 1 : -1);
41831 // c is just to avoid infinite loops if there's an error
41832 while(n1 < n2 && c++ < 100) {
41833 n = Math.floor((n1 + n2) / 2);
41834 if(test(bins[n], val)) n1 = n + 1;
41835 else n2 = n;
41836 }
41837 if(c > 90) loggers.log('Long binary search...');
41838 return n1 - 1;
41839 }
41840};
41841
41842function lessThan(a, b) { return a < b; }
41843function lessOrEqual(a, b) { return a <= b; }
41844function greaterThan(a, b) { return a > b; }
41845function greaterOrEqual(a, b) { return a >= b; }
41846
41847exports.sorterAsc = function(a, b) { return a - b; };
41848exports.sorterDes = function(a, b) { return b - a; };
41849
41850/**
41851 * find distinct values in an array, lumping together ones that appear to
41852 * just be off by a rounding error
41853 * return the distinct values and the minimum difference between any two
41854 */
41855exports.distinctVals = function(valsIn) {
41856 var vals = valsIn.slice(); // otherwise we sort the original array...
41857 vals.sort(exports.sorterAsc); // undefined listed in the end - also works on IE11
41858
41859 var last;
41860 for(last = vals.length - 1; last > -1; last--) {
41861 if(vals[last] !== BADNUM) break;
41862 }
41863
41864 var minDiff = (vals[last] - vals[0]) || 1;
41865 var errDiff = minDiff / (last || 1) / 10000;
41866 var newVals = [];
41867 var preV;
41868 for(var i = 0; i <= last; i++) {
41869 var v = vals[i];
41870
41871 // make sure values aren't just off by a rounding error
41872 var diff = v - preV;
41873
41874 if(preV === undefined) {
41875 newVals.push(v);
41876 preV = v;
41877 } else if(diff > errDiff) {
41878 minDiff = Math.min(minDiff, diff);
41879
41880 newVals.push(v);
41881 preV = v;
41882 }
41883 }
41884
41885 return {vals: newVals, minDiff: minDiff};
41886};
41887
41888/**
41889 * return the smallest element from (sorted) array arrayIn that's bigger than val,
41890 * or (reverse) the largest element smaller than val
41891 * used to find the best tick given the minimum (non-rounded) tick
41892 * particularly useful for date/time where things are not powers of 10
41893 * binary search is probably overkill here...
41894 */
41895exports.roundUp = function(val, arrayIn, reverse) {
41896 var low = 0;
41897 var high = arrayIn.length - 1;
41898 var mid;
41899 var c = 0;
41900 var dlow = reverse ? 0 : 1;
41901 var dhigh = reverse ? 1 : 0;
41902 var rounded = reverse ? Math.ceil : Math.floor;
41903 // c is just to avoid infinite loops if there's an error
41904 while(low < high && c++ < 100) {
41905 mid = rounded((low + high) / 2);
41906 if(arrayIn[mid] <= val) low = mid + dlow;
41907 else high = mid - dhigh;
41908 }
41909 return arrayIn[low];
41910};
41911
41912/**
41913 * Tweak to Array.sort(sortFn) that improves performance for pre-sorted arrays
41914 *
41915 * Note that newer browsers (such as Chrome v70+) are starting to pick up
41916 * on pre-sorted arrays which may render the following optimization unnecessary
41917 * in the future.
41918 *
41919 * Motivation: sometimes we need to sort arrays but the input is likely to
41920 * already be sorted. Browsers don't seem to pick up on pre-sorted arrays,
41921 * and in fact Chrome is actually *slower* sorting pre-sorted arrays than purely
41922 * random arrays. FF is at least faster if the array is pre-sorted, but still
41923 * not as fast as it could be.
41924 * Here's how this plays out sorting a length-1e6 array:
41925 *
41926 * Calls to Sort FN | Chrome bare | FF bare | Chrome tweak | FF tweak
41927 * | v68.0 Mac | v61.0 Mac| |
41928 * ------------------+---------------+-----------+----------------+------------
41929 * ordered | 30.4e6 | 10.1e6 | 1e6 | 1e6
41930 * reversed | 29.4e6 | 9.9e6 | 1e6 + reverse | 1e6 + reverse
41931 * random | ~21e6 | ~18.7e6 | ~21e6 | ~18.7e6
41932 *
41933 * So this is a substantial win for pre-sorted (ordered or exactly reversed)
41934 * arrays. Including this wrapper on an unsorted array adds a penalty that will
41935 * in general be only a few calls to the sort function. The only case this
41936 * penalty will be significant is if the array is mostly sorted but there are
41937 * a few unsorted items near the end, but the penalty is still at most N calls
41938 * out of (for N=1e6) ~20N total calls
41939 *
41940 * @param {Array} array: the array, to be sorted in place
41941 * @param {function} sortFn: As in Array.sort, function(a, b) that puts
41942 * item a before item b if the return is negative, a after b if positive,
41943 * and no change if zero.
41944 * @return {Array}: the original array, sorted in place.
41945 */
41946exports.sort = function(array, sortFn) {
41947 var notOrdered = 0;
41948 var notReversed = 0;
41949 for(var i = 1; i < array.length; i++) {
41950 var pairOrder = sortFn(array[i], array[i - 1]);
41951 if(pairOrder < 0) notOrdered = 1;
41952 else if(pairOrder > 0) notReversed = 1;
41953 if(notOrdered && notReversed) return array.sort(sortFn);
41954 }
41955 return notReversed ? array : array.reverse();
41956};
41957
41958/**
41959 * find index in array 'arr' that minimizes 'fn'
41960 *
41961 * @param {array} arr : array where to search
41962 * @param {fn (optional)} fn : function to minimize,
41963 * if not given, fn is the identity function
41964 * @return {integer}
41965 */
41966exports.findIndexOfMin = function(arr, fn) {
41967 fn = fn || identity;
41968
41969 var min = Infinity;
41970 var ind;
41971
41972 for(var i = 0; i < arr.length; i++) {
41973 var v = fn(arr[i]);
41974 if(v < min) {
41975 min = v;
41976 ind = i;
41977 }
41978 }
41979 return ind;
41980};
41981
41982},{"../constants/numerical":158,"./identity":177,"./loggers":182,"fast-isnumeric":18}],197:[function(_dereq_,module,exports){
41983/**
41984* Copyright 2012-2020, Plotly, Inc.
41985* All rights reserved.
41986*
41987* This source code is licensed under the MIT license found in the
41988* LICENSE file in the root directory of this source tree.
41989*/
41990
41991
41992'use strict';
41993
41994// works with our CSS cursor classes (see css/_cursor.scss)
41995// to apply cursors to d3 single-element selections.
41996// omit cursor to revert to the default.
41997module.exports = function setCursor(el3, csr) {
41998 (el3.attr('class') || '').split(' ').forEach(function(cls) {
41999 if(cls.indexOf('cursor-') === 0) el3.classed(cls, false);
42000 });
42001
42002 if(csr) el3.classed('cursor-' + csr, true);
42003};
42004
42005},{}],198:[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
42017var isNumeric = _dereq_('fast-isnumeric');
42018var isArrayOrTypedArray = _dereq_('./array').isArrayOrTypedArray;
42019
42020/**
42021 * aggNums() returns the result of an aggregate function applied to an array of
42022 * values, where non-numerical values have been tossed out.
42023 *
42024 * @param {function} f - aggregation function (e.g., Math.min)
42025 * @param {Number} v - initial value (continuing from previous calls)
42026 * if there's no continuing value, use null for selector-type
42027 * functions (max,min), or 0 for summations
42028 * @param {Array} a - array to aggregate (may be nested, we will recurse,
42029 * but all elements must have the same dimension)
42030 * @param {Number} len - maximum length of a to aggregate
42031 * @return {Number} - result of f applied to a starting from v
42032 */
42033exports.aggNums = function(f, v, a, len) {
42034 var i,
42035 b;
42036 if(!len || len > a.length) len = a.length;
42037 if(!isNumeric(v)) v = false;
42038 if(isArrayOrTypedArray(a[0])) {
42039 b = new Array(len);
42040 for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
42041 a = b;
42042 }
42043
42044 for(i = 0; i < len; i++) {
42045 if(!isNumeric(v)) v = a[i];
42046 else if(isNumeric(a[i])) v = f(+v, +a[i]);
42047 }
42048 return v;
42049};
42050
42051/**
42052 * mean & std dev functions using aggNums, so it handles non-numerics nicely
42053 * even need to use aggNums instead of .length, to toss out non-numerics
42054 */
42055exports.len = function(data) {
42056 return exports.aggNums(function(a) { return a + 1; }, 0, data);
42057};
42058
42059exports.mean = function(data, len) {
42060 if(!len) len = exports.len(data);
42061 return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
42062};
42063
42064exports.midRange = function(numArr) {
42065 if(numArr === undefined || numArr.length === 0) return undefined;
42066 return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2;
42067};
42068
42069exports.variance = function(data, len, mean) {
42070 if(!len) len = exports.len(data);
42071 if(!isNumeric(mean)) mean = exports.mean(data, len);
42072
42073 return exports.aggNums(function(a, b) {
42074 return a + Math.pow(b - mean, 2);
42075 }, 0, data) / len;
42076};
42077
42078exports.stdev = function(data, len, mean) {
42079 return Math.sqrt(exports.variance(data, len, mean));
42080};
42081
42082/**
42083 * median of a finite set of numbers
42084 * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers
42085**/
42086exports.median = function(data) {
42087 var b = data.slice().sort();
42088 return exports.interp(b, 0.5);
42089};
42090
42091/**
42092 * interp() computes a percentile (quantile) for a given distribution.
42093 * We interpolate the distribution (to compute quantiles, we follow method #10 here:
42094 * http://www.amstat.org/publications/jse/v14n3/langford.html).
42095 * Typically the index or rank (n * arr.length) may be non-integer.
42096 * For reference: ends are clipped to the extreme values in the array;
42097 * For box plots: index you get is half a point too high (see
42098 * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition
42099 * indexes from 1 rather than 0, so we subtract 1/2 (instead of add).
42100 *
42101 * @param {Array} arr - This array contains the values that make up the distribution.
42102 * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile.
42103 * For example, the 50th percentile (or median) corresponds to n = 0.5
42104 * @return {Number} - percentile
42105 */
42106exports.interp = function(arr, n) {
42107 if(!isNumeric(n)) throw 'n should be a finite number';
42108 n = n * arr.length - 0.5;
42109 if(n < 0) return arr[0];
42110 if(n > arr.length - 1) return arr[arr.length - 1];
42111 var frac = n % 1;
42112 return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)];
42113};
42114
42115},{"./array":165,"fast-isnumeric":18}],199:[function(_dereq_,module,exports){
42116/**
42117* Copyright 2012-2020, Plotly, Inc.
42118* All rights reserved.
42119*
42120* This source code is licensed under the MIT license found in the
42121* LICENSE file in the root directory of this source tree.
42122*/
42123
42124
42125'use strict';
42126
42127/* global MathJax:false */
42128
42129var d3 = _dereq_('d3');
42130
42131var Lib = _dereq_('../lib');
42132var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
42133var LINE_SPACING = _dereq_('../constants/alignment').LINE_SPACING;
42134
42135// text converter
42136
42137function getSize(_selection, _dimension) {
42138 return _selection.node().getBoundingClientRect()[_dimension];
42139}
42140
42141var FIND_TEX = /([^$]*)([$]+[^$]*[$]+)([^$]*)/;
42142
42143exports.convertToTspans = function(_context, gd, _callback) {
42144 var str = _context.text();
42145
42146 // Until we get tex integrated more fully (so it can be used along with non-tex)
42147 // allow some elements to prohibit it by attaching 'data-notex' to the original
42148 var tex = (!_context.attr('data-notex')) &&
42149 (typeof MathJax !== 'undefined') &&
42150 str.match(FIND_TEX);
42151
42152 var parent = d3.select(_context.node().parentNode);
42153 if(parent.empty()) return;
42154 var svgClass = (_context.attr('class')) ? _context.attr('class').split(' ')[0] : 'text';
42155 svgClass += '-math';
42156 parent.selectAll('svg.' + svgClass).remove();
42157 parent.selectAll('g.' + svgClass + '-group').remove();
42158 _context.style('display', null)
42159 .attr({
42160 // some callers use data-unformatted *from the <text> element* in 'cancel'
42161 // so we need it here even if we're going to turn it into math
42162 // these two (plus style and text-anchor attributes) form the key we're
42163 // going to use for Drawing.bBox
42164 'data-unformatted': str,
42165 'data-math': 'N'
42166 });
42167
42168 function showText() {
42169 if(!parent.empty()) {
42170 svgClass = _context.attr('class') + '-math';
42171 parent.select('svg.' + svgClass).remove();
42172 }
42173 _context.text('')
42174 .style('white-space', 'pre');
42175
42176 var hasLink = buildSVGText(_context.node(), str);
42177
42178 if(hasLink) {
42179 // at least in Chrome, pointer-events does not seem
42180 // to be honored in children of <text> elements
42181 // so if we have an anchor, we have to make the
42182 // whole element respond
42183 _context.style('pointer-events', 'all');
42184 }
42185
42186 exports.positionText(_context);
42187
42188 if(_callback) _callback.call(_context);
42189 }
42190
42191 if(tex) {
42192 ((gd && gd._promises) || []).push(new Promise(function(resolve) {
42193 _context.style('display', 'none');
42194 var fontSize = parseInt(_context.node().style.fontSize, 10);
42195 var config = {fontSize: fontSize};
42196
42197 texToSVG(tex[2], config, function(_svgEl, _glyphDefs, _svgBBox) {
42198 parent.selectAll('svg.' + svgClass).remove();
42199 parent.selectAll('g.' + svgClass + '-group').remove();
42200
42201 var newSvg = _svgEl && _svgEl.select('svg');
42202 if(!newSvg || !newSvg.node()) {
42203 showText();
42204 resolve();
42205 return;
42206 }
42207
42208 var mathjaxGroup = parent.append('g')
42209 .classed(svgClass + '-group', true)
42210 .attr({
42211 'pointer-events': 'none',
42212 'data-unformatted': str,
42213 'data-math': 'Y'
42214 });
42215
42216 mathjaxGroup.node().appendChild(newSvg.node());
42217
42218 // stitch the glyph defs
42219 if(_glyphDefs && _glyphDefs.node()) {
42220 newSvg.node().insertBefore(_glyphDefs.node().cloneNode(true),
42221 newSvg.node().firstChild);
42222 }
42223
42224 newSvg.attr({
42225 'class': svgClass,
42226 height: _svgBBox.height,
42227 preserveAspectRatio: 'xMinYMin meet'
42228 })
42229 .style({overflow: 'visible', 'pointer-events': 'none'});
42230
42231 var fill = _context.node().style.fill || 'black';
42232 var g = newSvg.select('g');
42233 g.attr({fill: fill, stroke: fill});
42234
42235 var newSvgW = getSize(g, 'width');
42236 var newSvgH = getSize(g, 'height');
42237 var newX = +_context.attr('x') - newSvgW *
42238 {start: 0, middle: 0.5, end: 1}[_context.attr('text-anchor') || 'start'];
42239 // font baseline is about 1/4 fontSize below centerline
42240 var textHeight = fontSize || getSize(_context, 'height');
42241 var dy = -textHeight / 4;
42242
42243 if(svgClass[0] === 'y') {
42244 mathjaxGroup.attr({
42245 transform: 'rotate(' + [-90, +_context.attr('x'), +_context.attr('y')] +
42246 ') translate(' + [-newSvgW / 2, dy - newSvgH / 2] + ')'
42247 });
42248 newSvg.attr({x: +_context.attr('x'), y: +_context.attr('y')});
42249 } else if(svgClass[0] === 'l') {
42250 newSvg.attr({x: _context.attr('x'), y: dy - (newSvgH / 2)});
42251 } else if(svgClass[0] === 'a' && svgClass.indexOf('atitle') !== 0) {
42252 newSvg.attr({x: 0, y: dy});
42253 } else {
42254 newSvg.attr({x: newX, y: (+_context.attr('y') + dy - newSvgH / 2)});
42255 }
42256
42257 if(_callback) _callback.call(_context, mathjaxGroup);
42258 resolve(mathjaxGroup);
42259 });
42260 }));
42261 } else showText();
42262
42263 return _context;
42264};
42265
42266
42267// MathJax
42268
42269var LT_MATCH = /(<|&lt;|&#60;)/g;
42270var GT_MATCH = /(>|&gt;|&#62;)/g;
42271
42272function cleanEscapesForTex(s) {
42273 return s.replace(LT_MATCH, '\\lt ')
42274 .replace(GT_MATCH, '\\gt ');
42275}
42276
42277function texToSVG(_texString, _config, _callback) {
42278 var originalRenderer,
42279 originalConfig,
42280 originalProcessSectionDelay,
42281 tmpDiv;
42282
42283 MathJax.Hub.Queue(
42284 function() {
42285 originalConfig = Lib.extendDeepAll({}, MathJax.Hub.config);
42286
42287 originalProcessSectionDelay = MathJax.Hub.processSectionDelay;
42288 if(MathJax.Hub.processSectionDelay !== undefined) {
42289 // MathJax 2.5+
42290 MathJax.Hub.processSectionDelay = 0;
42291 }
42292
42293 return MathJax.Hub.Config({
42294 messageStyle: 'none',
42295 tex2jax: {
42296 inlineMath: [['$', '$'], ['\\(', '\\)']]
42297 },
42298 displayAlign: 'left',
42299 });
42300 },
42301 function() {
42302 // Get original renderer
42303 originalRenderer = MathJax.Hub.config.menuSettings.renderer;
42304 if(originalRenderer !== 'SVG') {
42305 return MathJax.Hub.setRenderer('SVG');
42306 }
42307 },
42308 function() {
42309 var randomID = 'math-output-' + Lib.randstr({}, 64);
42310 tmpDiv = d3.select('body').append('div')
42311 .attr({id: randomID})
42312 .style({visibility: 'hidden', position: 'absolute'})
42313 .style({'font-size': _config.fontSize + 'px'})
42314 .text(cleanEscapesForTex(_texString));
42315
42316 return MathJax.Hub.Typeset(tmpDiv.node());
42317 },
42318 function() {
42319 var glyphDefs = d3.select('body').select('#MathJax_SVG_glyphs');
42320
42321 if(tmpDiv.select('.MathJax_SVG').empty() || !tmpDiv.select('svg').node()) {
42322 Lib.log('There was an error in the tex syntax.', _texString);
42323 _callback();
42324 } else {
42325 var svgBBox = tmpDiv.select('svg').node().getBoundingClientRect();
42326 _callback(tmpDiv.select('.MathJax_SVG'), glyphDefs, svgBBox);
42327 }
42328
42329 tmpDiv.remove();
42330
42331 if(originalRenderer !== 'SVG') {
42332 return MathJax.Hub.setRenderer(originalRenderer);
42333 }
42334 },
42335 function() {
42336 if(originalProcessSectionDelay !== undefined) {
42337 MathJax.Hub.processSectionDelay = originalProcessSectionDelay;
42338 }
42339 return MathJax.Hub.Config(originalConfig);
42340 });
42341}
42342
42343var TAG_STYLES = {
42344 // would like to use baseline-shift for sub/sup but FF doesn't support it
42345 // so we need to use dy along with the uber hacky shift-back-to
42346 // baseline below
42347 sup: 'font-size:70%',
42348 sub: 'font-size:70%',
42349 b: 'font-weight:bold',
42350 i: 'font-style:italic',
42351 a: 'cursor:pointer',
42352 span: '',
42353 em: 'font-style:italic;font-weight:bold'
42354};
42355
42356// baseline shifts for sub and sup
42357var SHIFT_DY = {
42358 sub: '0.3em',
42359 sup: '-0.6em'
42360};
42361// reset baseline by adding a tspan (empty except for a zero-width space)
42362// with dy of -70% * SHIFT_DY (because font-size=70%)
42363var RESET_DY = {
42364 sub: '-0.21em',
42365 sup: '0.42em'
42366};
42367var ZERO_WIDTH_SPACE = '\u200b';
42368
42369/*
42370 * Whitelist of protocols in user-supplied urls. Mostly we want to avoid javascript
42371 * and related attack vectors. The empty items are there for IE, that in various
42372 * versions treats relative paths as having different flavors of no protocol, while
42373 * other browsers have these explicitly inherit the protocol of the page they're in.
42374 */
42375var PROTOCOLS = ['http:', 'https:', 'mailto:', '', undefined, ':'];
42376
42377var NEWLINES = exports.NEWLINES = /(\r\n?|\n)/g;
42378
42379var SPLIT_TAGS = /(<[^<>]*>)/;
42380
42381var ONE_TAG = /<(\/?)([^ >]*)(\s+(.*))?>/i;
42382
42383var BR_TAG = /<br(\s+.*)?>/i;
42384exports.BR_TAG_ALL = /<br(\s+.*)?>/gi;
42385
42386/*
42387 * style and href: pull them out of either single or double quotes. Also
42388 * - target: (_blank|_self|_parent|_top|framename)
42389 * note that you can't use target to get a popup but if you use popup,
42390 * a `framename` will be passed along as the name of the popup window.
42391 * per the spec, cannot contain whitespace.
42392 * for backward compatibility we default to '_blank'
42393 * - popup: a custom one for us to enable popup (new window) links. String
42394 * for window.open -> strWindowFeatures, like 'menubar=yes,width=500,height=550'
42395 * note that at least in Chrome, you need to give at least one property
42396 * in this string or the page will open in a new tab anyway. We follow this
42397 * convention and will not make a popup if this string is empty.
42398 * per the spec, cannot contain whitespace.
42399 *
42400 * Because we hack in other attributes with style (sub & sup), drop any trailing
42401 * semicolon in user-supplied styles so we can consistently append the tag-dependent style
42402 *
42403 * These are for tag attributes; Chrome anyway will convert entities in
42404 * attribute values, but not in attribute names
42405 * you can test this by for example:
42406 * > p = document.createElement('p')
42407 * > p.innerHTML = '<span styl&#x65;="font-color:r&#x65;d;">Hi</span>'
42408 * > p.innerHTML
42409 * <- '<span styl&#x65;="font-color:red;">Hi</span>'
42410 */
42411var STYLEMATCH = /(^|[\s"'])style\s*=\s*("([^"]*);?"|'([^']*);?')/i;
42412var HREFMATCH = /(^|[\s"'])href\s*=\s*("([^"]*)"|'([^']*)')/i;
42413var TARGETMATCH = /(^|[\s"'])target\s*=\s*("([^"\s]*)"|'([^'\s]*)')/i;
42414var POPUPMATCH = /(^|[\s"'])popup\s*=\s*("([\w=,]*)"|'([\w=,]*)')/i;
42415
42416// dedicated matcher for these quoted regexes, that can return their results
42417// in two different places
42418function getQuotedMatch(_str, re) {
42419 if(!_str) return null;
42420 var match = _str.match(re);
42421 var result = match && (match[3] || match[4]);
42422 return result && convertEntities(result);
42423}
42424
42425var COLORMATCH = /(^|;)\s*color:/;
42426
42427/**
42428 * Strip string of tags
42429 *
42430 * @param {string} _str : input string
42431 * @param {object} opts :
42432 * - len {number} max length of output string
42433 * - allowedTags {array} list of pseudo-html tags to NOT strip
42434 * @return {string}
42435 */
42436exports.plainText = function(_str, opts) {
42437 opts = opts || {};
42438
42439 var len = (opts.len !== undefined && opts.len !== -1) ? opts.len : Infinity;
42440 var allowedTags = opts.allowedTags !== undefined ? opts.allowedTags : ['br'];
42441
42442 var ellipsis = '...';
42443 var eLen = ellipsis.length;
42444
42445 var oldParts = _str.split(SPLIT_TAGS);
42446 var newParts = [];
42447 var prevTag = '';
42448 var l = 0;
42449
42450 for(var i = 0; i < oldParts.length; i++) {
42451 var p = oldParts[i];
42452 var match = p.match(ONE_TAG);
42453 var tagType = match && match[2].toLowerCase();
42454
42455 if(tagType) {
42456 // N.B. tags do not count towards string length
42457 if(allowedTags.indexOf(tagType) !== -1) {
42458 newParts.push(p);
42459 prevTag = tagType;
42460 }
42461 } else {
42462 var pLen = p.length;
42463
42464 if((l + pLen) < len) {
42465 newParts.push(p);
42466 l += pLen;
42467 } else if(l < len) {
42468 var pLen2 = len - l;
42469
42470 if(prevTag && (prevTag !== 'br' || pLen2 <= eLen || pLen <= eLen)) {
42471 newParts.pop();
42472 }
42473
42474 if(len > eLen) {
42475 newParts.push(p.substr(0, pLen2 - eLen) + ellipsis);
42476 } else {
42477 newParts.push(p.substr(0, pLen2));
42478 }
42479 break;
42480 }
42481
42482 prevTag = '';
42483 }
42484 }
42485
42486 return newParts.join('');
42487};
42488
42489/*
42490 * N.B. HTML entities are listed without the leading '&' and trailing ';'
42491 * https://www.freeformatter.com/html-entities.html
42492 *
42493 * FWIW if we wanted to support the full set, it has 2261 entries:
42494 * https://www.w3.org/TR/html5/entities.json
42495 * though I notice that some of these are duplicates and/or are missing ";"
42496 * eg: "&amp;", "&amp", "&AMP;", and "&AMP" all map to "&"
42497 * We no longer need to include numeric entities here, these are now handled
42498 * by String.fromCodePoint/fromCharCode
42499 *
42500 * Anyway the only ones that are really important to allow are the HTML special
42501 * chars <, >, and &, because these ones can trigger special processing if not
42502 * replaced by the corresponding entity.
42503 */
42504var entityToUnicode = {
42505 mu: 'μ',
42506 amp: '&',
42507 lt: '<',
42508 gt: '>',
42509 nbsp: ' ',
42510 times: '×',
42511 plusmn: '±',
42512 deg: '°'
42513};
42514
42515// NOTE: in general entities can contain uppercase too (so [a-zA-Z]) but all the
42516// ones we support use only lowercase. If we ever change that, update the regex.
42517var ENTITY_MATCH = /&(#\d+|#x[\da-fA-F]+|[a-z]+);/g;
42518function convertEntities(_str) {
42519 return _str.replace(ENTITY_MATCH, function(fullMatch, innerMatch) {
42520 var outChar;
42521 if(innerMatch.charAt(0) === '#') {
42522 // cannot use String.fromCodePoint in IE
42523 outChar = fromCodePoint(
42524 innerMatch.charAt(1) === 'x' ?
42525 parseInt(innerMatch.substr(2), 16) :
42526 parseInt(innerMatch.substr(1), 10)
42527 );
42528 } else outChar = entityToUnicode[innerMatch];
42529
42530 // as in regular HTML, if we didn't decode the entity just
42531 // leave the raw text in place.
42532 return outChar || fullMatch;
42533 });
42534}
42535exports.convertEntities = convertEntities;
42536
42537function fromCodePoint(code) {
42538 // Don't allow overflow. In Chrome this turns into � but I feel like it's
42539 // more useful to just not convert it at all.
42540 if(code > 0x10FFFF) return;
42541 var stringFromCodePoint = String.fromCodePoint;
42542 if(stringFromCodePoint) return stringFromCodePoint(code);
42543
42544 // IE doesn't have String.fromCodePoint
42545 // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint
42546 var stringFromCharCode = String.fromCharCode;
42547 if(code <= 0xFFFF) return stringFromCharCode(code);
42548 return stringFromCharCode(
42549 (code >> 10) + 0xD7C0,
42550 (code % 0x400) + 0xDC00
42551 );
42552}
42553
42554/*
42555 * buildSVGText: convert our pseudo-html into SVG tspan elements, and attach these
42556 * to containerNode
42557 *
42558 * @param {svg text element} containerNode: the <text> node to insert this text into
42559 * @param {string} str: the pseudo-html string to convert to svg
42560 *
42561 * @returns {bool}: does the result contain any links? We need to handle the text element
42562 * somewhat differently if it does, so just keep track of this when it happens.
42563 */
42564function buildSVGText(containerNode, str) {
42565 /*
42566 * Normalize behavior between IE and others wrt newlines and whitespace:pre
42567 * this combination makes IE barf https://github.com/plotly/plotly.js/issues/746
42568 * Chrome and FF display \n, \r, or \r\n as a space in this mode.
42569 * I feel like at some point we turned these into <br> but currently we don't so
42570 * I'm just going to cement what we do now in Chrome and FF
42571 */
42572 str = str.replace(NEWLINES, ' ');
42573
42574 var hasLink = false;
42575
42576 // as we're building the text, keep track of what elements we're nested inside
42577 // nodeStack will be an array of {node, type, style, href, target, popup}
42578 // where only type: 'a' gets the last 3 and node is only added when it's created
42579 var nodeStack = [];
42580 var currentNode;
42581 var currentLine = -1;
42582
42583 function newLine() {
42584 currentLine++;
42585
42586 var lineNode = document.createElementNS(xmlnsNamespaces.svg, 'tspan');
42587 d3.select(lineNode).attr({
42588 class: 'line',
42589 dy: (currentLine * LINE_SPACING) + 'em'
42590 });
42591 containerNode.appendChild(lineNode);
42592
42593 currentNode = lineNode;
42594
42595 var oldNodeStack = nodeStack;
42596 nodeStack = [{node: lineNode}];
42597
42598 if(oldNodeStack.length > 1) {
42599 for(var i = 1; i < oldNodeStack.length; i++) {
42600 enterNode(oldNodeStack[i]);
42601 }
42602 }
42603 }
42604
42605 function enterNode(nodeSpec) {
42606 var type = nodeSpec.type;
42607 var nodeAttrs = {};
42608 var nodeType;
42609
42610 if(type === 'a') {
42611 nodeType = 'a';
42612 var target = nodeSpec.target;
42613 var href = nodeSpec.href;
42614 var popup = nodeSpec.popup;
42615 if(href) {
42616 nodeAttrs = {
42617 'xlink:xlink:show': (target === '_blank' || target.charAt(0) !== '_') ? 'new' : 'replace',
42618 target: target,
42619 'xlink:xlink:href': href
42620 };
42621 if(popup) {
42622 // security: href and target are not inserted as code but
42623 // as attributes. popup is, but limited to /[A-Za-z0-9_=,]/
42624 nodeAttrs.onclick = 'window.open(this.href.baseVal,this.target.baseVal,"' +
42625 popup + '");return false;';
42626 }
42627 }
42628 } else nodeType = 'tspan';
42629
42630 if(nodeSpec.style) nodeAttrs.style = nodeSpec.style;
42631
42632 var newNode = document.createElementNS(xmlnsNamespaces.svg, nodeType);
42633
42634 if(type === 'sup' || type === 'sub') {
42635 addTextNode(currentNode, ZERO_WIDTH_SPACE);
42636 currentNode.appendChild(newNode);
42637
42638 var resetter = document.createElementNS(xmlnsNamespaces.svg, 'tspan');
42639 addTextNode(resetter, ZERO_WIDTH_SPACE);
42640 d3.select(resetter).attr('dy', RESET_DY[type]);
42641 nodeAttrs.dy = SHIFT_DY[type];
42642
42643 currentNode.appendChild(newNode);
42644 currentNode.appendChild(resetter);
42645 } else {
42646 currentNode.appendChild(newNode);
42647 }
42648
42649 d3.select(newNode).attr(nodeAttrs);
42650
42651 currentNode = nodeSpec.node = newNode;
42652 nodeStack.push(nodeSpec);
42653 }
42654
42655 function addTextNode(node, text) {
42656 node.appendChild(document.createTextNode(text));
42657 }
42658
42659 function exitNode(type) {
42660 // A bare closing tag can't close the root node. If we encounter this it
42661 // means there's an extra closing tag that can just be ignored:
42662 if(nodeStack.length === 1) {
42663 Lib.log('Ignoring unexpected end tag </' + type + '>.', str);
42664 return;
42665 }
42666
42667 var innerNode = nodeStack.pop();
42668
42669 if(type !== innerNode.type) {
42670 Lib.log('Start tag <' + innerNode.type + '> doesnt match end tag <' +
42671 type + '>. Pretending it did match.', str);
42672 }
42673 currentNode = nodeStack[nodeStack.length - 1].node;
42674 }
42675
42676 var hasLines = BR_TAG.test(str);
42677
42678 if(hasLines) newLine();
42679 else {
42680 currentNode = containerNode;
42681 nodeStack = [{node: containerNode}];
42682 }
42683
42684 var parts = str.split(SPLIT_TAGS);
42685 for(var i = 0; i < parts.length; i++) {
42686 var parti = parts[i];
42687 var match = parti.match(ONE_TAG);
42688 var tagType = match && match[2].toLowerCase();
42689 var tagStyle = TAG_STYLES[tagType];
42690
42691 if(tagType === 'br') {
42692 newLine();
42693 } else if(tagStyle === undefined) {
42694 addTextNode(currentNode, convertEntities(parti));
42695 } else {
42696 // tag - open or close
42697 if(match[1]) {
42698 exitNode(tagType);
42699 } else {
42700 var extra = match[4];
42701
42702 var nodeSpec = {type: tagType};
42703
42704 // now add style, from both the tag name and any extra css
42705 // Most of the svg css that users will care about is just like html,
42706 // but font color is different (uses fill). Let our users ignore this.
42707 var css = getQuotedMatch(extra, STYLEMATCH);
42708 if(css) {
42709 css = css.replace(COLORMATCH, '$1 fill:');
42710 if(tagStyle) css += ';' + tagStyle;
42711 } else if(tagStyle) css = tagStyle;
42712
42713 if(css) nodeSpec.style = css;
42714
42715 if(tagType === 'a') {
42716 hasLink = true;
42717
42718 var href = getQuotedMatch(extra, HREFMATCH);
42719
42720 if(href) {
42721 // check safe protocols
42722 var dummyAnchor = document.createElement('a');
42723 dummyAnchor.href = href;
42724 if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
42725 // Decode href to allow both already encoded and not encoded
42726 // URIs. Without decoding prior encoding, an already encoded
42727 // URI would be encoded twice producing a semantically different URI.
42728 nodeSpec.href = encodeURI(decodeURI(href));
42729 nodeSpec.target = getQuotedMatch(extra, TARGETMATCH) || '_blank';
42730 nodeSpec.popup = getQuotedMatch(extra, POPUPMATCH);
42731 }
42732 }
42733 }
42734
42735 enterNode(nodeSpec);
42736 }
42737 }
42738 }
42739
42740 return hasLink;
42741}
42742
42743/*
42744 * sanitizeHTML: port of buildSVGText aimed at providing a clean subset of HTML
42745 * @param {string} str: the html string to clean
42746 * @returns {string}: a cleaned and normalized version of the input,
42747 * supporting only a small subset of html
42748 */
42749exports.sanitizeHTML = function sanitizeHTML(str) {
42750 str = str.replace(NEWLINES, ' ');
42751
42752 var rootNode = document.createElement('p');
42753 var currentNode = rootNode;
42754 var nodeStack = [];
42755
42756 var parts = str.split(SPLIT_TAGS);
42757 for(var i = 0; i < parts.length; i++) {
42758 var parti = parts[i];
42759 var match = parti.match(ONE_TAG);
42760 var tagType = match && match[2].toLowerCase();
42761
42762 if(tagType in TAG_STYLES) {
42763 if(match[1]) {
42764 if(nodeStack.length) {
42765 currentNode = nodeStack.pop();
42766 }
42767 } else {
42768 var extra = match[4];
42769
42770 var css = getQuotedMatch(extra, STYLEMATCH);
42771 var nodeAttrs = css ? {style: css} : {};
42772
42773 if(tagType === 'a') {
42774 var href = getQuotedMatch(extra, HREFMATCH);
42775
42776 if(href) {
42777 var dummyAnchor = document.createElement('a');
42778 dummyAnchor.href = href;
42779 if(PROTOCOLS.indexOf(dummyAnchor.protocol) !== -1) {
42780 nodeAttrs.href = encodeURI(decodeURI(href));
42781 var target = getQuotedMatch(extra, TARGETMATCH);
42782 if(target) {
42783 nodeAttrs.target = target;
42784 }
42785 }
42786 }
42787 }
42788
42789 var newNode = document.createElement(tagType);
42790 currentNode.appendChild(newNode);
42791 d3.select(newNode).attr(nodeAttrs);
42792
42793 currentNode = newNode;
42794 nodeStack.push(newNode);
42795 }
42796 } else {
42797 currentNode.appendChild(
42798 document.createTextNode(convertEntities(parti))
42799 );
42800 }
42801 }
42802 var key = 'innerHTML'; // i.e. to avoid pass test-syntax
42803 return rootNode[key];
42804};
42805
42806exports.lineCount = function lineCount(s) {
42807 return s.selectAll('tspan.line').size() || 1;
42808};
42809
42810exports.positionText = function positionText(s, x, y) {
42811 return s.each(function() {
42812 var text = d3.select(this);
42813
42814 function setOrGet(attr, val) {
42815 if(val === undefined) {
42816 val = text.attr(attr);
42817 if(val === null) {
42818 text.attr(attr, 0);
42819 val = 0;
42820 }
42821 } else text.attr(attr, val);
42822 return val;
42823 }
42824
42825 var thisX = setOrGet('x', x);
42826 var thisY = setOrGet('y', y);
42827
42828 if(this.nodeName === 'text') {
42829 text.selectAll('tspan.line').attr({x: thisX, y: thisY});
42830 }
42831 });
42832};
42833
42834function alignHTMLWith(_base, container, options) {
42835 var alignH = options.horizontalAlign;
42836 var alignV = options.verticalAlign || 'top';
42837 var bRect = _base.node().getBoundingClientRect();
42838 var cRect = container.node().getBoundingClientRect();
42839 var thisRect;
42840 var getTop;
42841 var getLeft;
42842
42843 if(alignV === 'bottom') {
42844 getTop = function() { return bRect.bottom - thisRect.height; };
42845 } else if(alignV === 'middle') {
42846 getTop = function() { return bRect.top + (bRect.height - thisRect.height) / 2; };
42847 } else { // default: top
42848 getTop = function() { return bRect.top; };
42849 }
42850
42851 if(alignH === 'right') {
42852 getLeft = function() { return bRect.right - thisRect.width; };
42853 } else if(alignH === 'center') {
42854 getLeft = function() { return bRect.left + (bRect.width - thisRect.width) / 2; };
42855 } else { // default: left
42856 getLeft = function() { return bRect.left; };
42857 }
42858
42859 return function() {
42860 thisRect = this.node().getBoundingClientRect();
42861 this.style({
42862 top: (getTop() - cRect.top) + 'px',
42863 left: (getLeft() - cRect.left) + 'px',
42864 'z-index': 1000
42865 });
42866 return this;
42867 };
42868}
42869
42870/*
42871 * Editable title
42872 * @param {d3.selection} context: the element being edited. Normally text,
42873 * but if it isn't, you should provide the styling options
42874 * @param {object} options:
42875 * @param {div} options.gd: graphDiv
42876 * @param {d3.selection} options.delegate: item to bind events to if not this
42877 * @param {boolean} options.immediate: start editing now (true) or on click (false, default)
42878 * @param {string} options.fill: font color if not as shown
42879 * @param {string} options.background: background color if not as shown
42880 * @param {string} options.text: initial text, if not as shown
42881 * @param {string} options.horizontalAlign: alignment of the edit box wrt. the bound element
42882 * @param {string} options.verticalAlign: alignment of the edit box wrt. the bound element
42883 */
42884
42885exports.makeEditable = function(context, options) {
42886 var gd = options.gd;
42887 var _delegate = options.delegate;
42888 var dispatch = d3.dispatch('edit', 'input', 'cancel');
42889 var handlerElement = _delegate || context;
42890
42891 context.style({'pointer-events': _delegate ? 'none' : 'all'});
42892
42893 if(context.size() !== 1) throw new Error('boo');
42894
42895 function handleClick() {
42896 appendEditable();
42897 context.style({opacity: 0});
42898 // also hide any mathjax svg
42899 var svgClass = handlerElement.attr('class');
42900 var mathjaxClass;
42901 if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
42902 else mathjaxClass = '[class*=-math-group]';
42903 if(mathjaxClass) {
42904 d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0});
42905 }
42906 }
42907
42908 function selectElementContents(_el) {
42909 var el = _el.node();
42910 var range = document.createRange();
42911 range.selectNodeContents(el);
42912 var sel = window.getSelection();
42913 sel.removeAllRanges();
42914 sel.addRange(range);
42915 el.focus();
42916 }
42917
42918 function appendEditable() {
42919 var plotDiv = d3.select(gd);
42920 var container = plotDiv.select('.svg-container');
42921 var div = container.append('div');
42922 var cStyle = context.node().style;
42923 var fontSize = parseFloat(cStyle.fontSize || 12);
42924
42925 var initialText = options.text;
42926 if(initialText === undefined) initialText = context.attr('data-unformatted');
42927
42928 div.classed('plugin-editable editable', true)
42929 .style({
42930 position: 'absolute',
42931 'font-family': cStyle.fontFamily || 'Arial',
42932 'font-size': fontSize,
42933 color: options.fill || cStyle.fill || 'black',
42934 opacity: 1,
42935 'background-color': options.background || 'transparent',
42936 outline: '#ffffff33 1px solid',
42937 margin: [-fontSize / 8 + 1, 0, 0, -1].join('px ') + 'px',
42938 padding: '0',
42939 'box-sizing': 'border-box'
42940 })
42941 .attr({contenteditable: true})
42942 .text(initialText)
42943 .call(alignHTMLWith(context, container, options))
42944 .on('blur', function() {
42945 gd._editing = false;
42946 context.text(this.textContent)
42947 .style({opacity: 1});
42948 var svgClass = d3.select(this).attr('class');
42949 var mathjaxClass;
42950 if(svgClass) mathjaxClass = '.' + svgClass.split(' ')[0] + '-math-group';
42951 else mathjaxClass = '[class*=-math-group]';
42952 if(mathjaxClass) {
42953 d3.select(context.node().parentNode).select(mathjaxClass).style({opacity: 0});
42954 }
42955 var text = this.textContent;
42956 d3.select(this).transition().duration(0).remove();
42957 d3.select(document).on('mouseup', null);
42958 dispatch.edit.call(context, text);
42959 })
42960 .on('focus', function() {
42961 var editDiv = this;
42962 gd._editing = true;
42963 d3.select(document).on('mouseup', function() {
42964 if(d3.event.target === editDiv) return false;
42965 if(document.activeElement === div.node()) div.node().blur();
42966 });
42967 })
42968 .on('keyup', function() {
42969 if(d3.event.which === 27) {
42970 gd._editing = false;
42971 context.style({opacity: 1});
42972 d3.select(this)
42973 .style({opacity: 0})
42974 .on('blur', function() { return false; })
42975 .transition().remove();
42976 dispatch.cancel.call(context, this.textContent);
42977 } else {
42978 dispatch.input.call(context, this.textContent);
42979 d3.select(this).call(alignHTMLWith(context, container, options));
42980 }
42981 })
42982 .on('keydown', function() {
42983 if(d3.event.which === 13) this.blur();
42984 })
42985 .call(selectElementContents);
42986 }
42987
42988 if(options.immediate) handleClick();
42989 else handlerElement.on('click', handleClick);
42990
42991 return d3.rebind(context, dispatch, 'on');
42992};
42993
42994},{"../constants/alignment":154,"../constants/xmlns_namespaces":159,"../lib":178,"d3":16}],200:[function(_dereq_,module,exports){
42995/**
42996* Copyright 2012-2020, Plotly, Inc.
42997* All rights reserved.
42998*
42999* This source code is licensed under the MIT license found in the
43000* LICENSE file in the root directory of this source tree.
43001*/
43002
43003'use strict';
43004
43005var timerCache = {};
43006
43007/**
43008 * Throttle a callback. `callback` executes synchronously only if
43009 * more than `minInterval` milliseconds have already elapsed since the latest
43010 * call (if any). Otherwise we wait until `minInterval` is over and execute the
43011 * last callback received while waiting.
43012 * So the first and last events in a train are always executed (eventually)
43013 * but some of the events in the middle can be dropped.
43014 *
43015 * @param {string} id: an identifier to mark events to throttle together
43016 * @param {number} minInterval: minimum time, in milliseconds, between
43017 * invocations of `callback`
43018 * @param {function} callback: the function to throttle. `callback` itself
43019 * should be a purely synchronous function.
43020 */
43021exports.throttle = function throttle(id, minInterval, callback) {
43022 var cache = timerCache[id];
43023 var now = Date.now();
43024
43025 if(!cache) {
43026 /*
43027 * Throw out old items before making a new one, to prevent the cache
43028 * getting overgrown, for example from old plots that have been replaced.
43029 * 1 minute age is arbitrary.
43030 */
43031 for(var idi in timerCache) {
43032 if(timerCache[idi].ts < now - 60000) {
43033 delete timerCache[idi];
43034 }
43035 }
43036 cache = timerCache[id] = {ts: 0, timer: null};
43037 }
43038
43039 _clearTimeout(cache);
43040
43041 function exec() {
43042 callback();
43043 cache.ts = Date.now();
43044 if(cache.onDone) {
43045 cache.onDone();
43046 cache.onDone = null;
43047 }
43048 }
43049
43050 if(now > cache.ts + minInterval) {
43051 exec();
43052 return;
43053 }
43054
43055 cache.timer = setTimeout(function() {
43056 exec();
43057 cache.timer = null;
43058 }, minInterval);
43059};
43060
43061exports.done = function(id) {
43062 var cache = timerCache[id];
43063 if(!cache || !cache.timer) return Promise.resolve();
43064
43065 return new Promise(function(resolve) {
43066 var previousOnDone = cache.onDone;
43067 cache.onDone = function onDone() {
43068 if(previousOnDone) previousOnDone();
43069 resolve();
43070 cache.onDone = null;
43071 };
43072 });
43073};
43074
43075/**
43076 * Clear the throttle cache for one or all timers
43077 * @param {optional string} id:
43078 * if provided, clear just this timer
43079 * if omitted, clear all timers (mainly useful for testing)
43080 */
43081exports.clear = function(id) {
43082 if(id) {
43083 _clearTimeout(timerCache[id]);
43084 delete timerCache[id];
43085 } else {
43086 for(var idi in timerCache) exports.clear(idi);
43087 }
43088};
43089
43090function _clearTimeout(cache) {
43091 if(cache && cache.timer !== null) {
43092 clearTimeout(cache.timer);
43093 cache.timer = null;
43094 }
43095}
43096
43097},{}],201:[function(_dereq_,module,exports){
43098/**
43099* Copyright 2012-2020, Plotly, Inc.
43100* All rights reserved.
43101*
43102* This source code is licensed under the MIT license found in the
43103* LICENSE file in the root directory of this source tree.
43104*/
43105
43106'use strict';
43107
43108var isNumeric = _dereq_('fast-isnumeric');
43109
43110/**
43111 * convert a linear value into a logged value, folding negative numbers into
43112 * the given range
43113 */
43114module.exports = function toLogRange(val, range) {
43115 if(val > 0) return Math.log(val) / Math.LN10;
43116
43117 // move a negative value reference to a log axis - just put the
43118 // result at the lowest range value on the plot (or if the range also went negative,
43119 // one millionth of the top of the range)
43120 var newVal = Math.log(Math.min(range[0], range[1])) / Math.LN10;
43121 if(!isNumeric(newVal)) newVal = Math.log(Math.max(range[0], range[1])) / Math.LN10 - 6;
43122 return newVal;
43123};
43124
43125},{"fast-isnumeric":18}],202:[function(_dereq_,module,exports){
43126/**
43127* Copyright 2012-2020, Plotly, Inc.
43128* All rights reserved.
43129*
43130* This source code is licensed under the MIT license found in the
43131* LICENSE file in the root directory of this source tree.
43132*/
43133
43134'use strict';
43135
43136module.exports = {
43137 moduleType: 'locale',
43138 name: 'en-US',
43139 dictionary: {
43140 'Click to enter Colorscale title': 'Click to enter Colorscale title'
43141 },
43142 format: {
43143 date: '%m/%d/%Y'
43144 }
43145};
43146
43147},{}],203:[function(_dereq_,module,exports){
43148/**
43149* Copyright 2012-2020, Plotly, Inc.
43150* All rights reserved.
43151*
43152* This source code is licensed under the MIT license found in the
43153* LICENSE file in the root directory of this source tree.
43154*/
43155
43156'use strict';
43157
43158module.exports = {
43159 moduleType: 'locale',
43160 name: 'en',
43161 dictionary: {
43162 'Click to enter Colorscale title': 'Click to enter Colourscale title'
43163 },
43164 format: {
43165 days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
43166 shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
43167 months: [
43168 'January', 'February', 'March', 'April', 'May', 'June',
43169 'July', 'August', 'September', 'October', 'November', 'December'
43170 ],
43171 shortMonths: [
43172 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
43173 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
43174 ],
43175 periods: ['AM', 'PM'],
43176 dateTime: '%a %b %e %X %Y',
43177 date: '%d/%m/%Y',
43178 time: '%H:%M:%S',
43179 decimal: '.',
43180 thousands: ',',
43181 grouping: [3],
43182 currency: ['$', ''],
43183 year: '%Y',
43184 month: '%b %Y',
43185 dayMonth: '%b %-d',
43186 dayMonthYear: '%b %-d, %Y'
43187 }
43188};
43189
43190},{}],204:[function(_dereq_,module,exports){
43191/**
43192* Copyright 2012-2020, Plotly, Inc.
43193* All rights reserved.
43194*
43195* This source code is licensed under the MIT license found in the
43196* LICENSE file in the root directory of this source tree.
43197*/
43198
43199
43200'use strict';
43201
43202var Registry = _dereq_('../registry');
43203
43204/*
43205 * containerArrayMatch: does this attribute string point into a
43206 * layout container array?
43207 *
43208 * @param {String} astr: an attribute string, like *annotations[2].text*
43209 *
43210 * @returns {Object | false} Returns false if `astr` doesn't match a container
43211 * array. If it does, returns:
43212 * {array: {String}, index: {Number}, property: {String}}
43213 * ie the attribute string for the array, the index within the array (or ''
43214 * if the whole array) and the property within that (or '' if the whole array
43215 * or the whole object)
43216 */
43217module.exports = function containerArrayMatch(astr) {
43218 var rootContainers = Registry.layoutArrayContainers;
43219 var regexpContainers = Registry.layoutArrayRegexes;
43220 var rootPart = astr.split('[')[0];
43221 var arrayStr;
43222 var match;
43223
43224 // look for regexp matches first, because they may be nested inside root matches
43225 // eg updatemenus[i].buttons is nested inside updatemenus
43226 for(var i = 0; i < regexpContainers.length; i++) {
43227 match = astr.match(regexpContainers[i]);
43228 if(match && match.index === 0) {
43229 arrayStr = match[0];
43230 break;
43231 }
43232 }
43233
43234 // now look for root matches
43235 if(!arrayStr) arrayStr = rootContainers[rootContainers.indexOf(rootPart)];
43236
43237 if(!arrayStr) return false;
43238
43239 var tail = astr.substr(arrayStr.length);
43240 if(!tail) return {array: arrayStr, index: '', property: ''};
43241
43242 match = tail.match(/^\[(0|[1-9][0-9]*)\](\.(.+))?$/);
43243 if(!match) return false;
43244
43245 return {array: arrayStr, index: Number(match[1]), property: match[3] || ''};
43246};
43247
43248},{"../registry":269}],205:[function(_dereq_,module,exports){
43249/**
43250* Copyright 2012-2020, Plotly, Inc.
43251* All rights reserved.
43252*
43253* This source code is licensed under the MIT license found in the
43254* LICENSE file in the root directory of this source tree.
43255*/
43256
43257'use strict';
43258
43259var Lib = _dereq_('../lib');
43260var extendFlat = Lib.extendFlat;
43261var isPlainObject = Lib.isPlainObject;
43262
43263var traceOpts = {
43264 valType: 'flaglist',
43265 extras: ['none'],
43266 flags: ['calc', 'clearAxisTypes', 'plot', 'style', 'markerSize', 'colorbars'],
43267
43268};
43269
43270var layoutOpts = {
43271 valType: 'flaglist',
43272 extras: ['none'],
43273 flags: [
43274 'calc', 'plot', 'legend', 'ticks', 'axrange',
43275 'layoutstyle', 'modebar', 'camera', 'arraydraw', 'colorbars'
43276 ],
43277
43278};
43279
43280// flags for inside restyle/relayout include a few extras
43281// that shouldn't be used in attributes, to deal with certain
43282// combinations and conditionals efficiently
43283var traceEditTypeFlags = traceOpts.flags.slice()
43284 .concat(['fullReplot']);
43285
43286var layoutEditTypeFlags = layoutOpts.flags.slice()
43287 .concat('layoutReplot');
43288
43289module.exports = {
43290 traces: traceOpts,
43291 layout: layoutOpts,
43292 /*
43293 * default (all false) edit flags for restyle (traces)
43294 * creates a new object each call, so the caller can mutate freely
43295 */
43296 traceFlags: function() { return falseObj(traceEditTypeFlags); },
43297
43298 /*
43299 * default (all false) edit flags for relayout
43300 * creates a new object each call, so the caller can mutate freely
43301 */
43302 layoutFlags: function() { return falseObj(layoutEditTypeFlags); },
43303
43304 /*
43305 * update `flags` with the `editType` values found in `attr`
43306 */
43307 update: function(flags, attr) {
43308 var editType = attr.editType;
43309 if(editType && editType !== 'none') {
43310 var editTypeParts = editType.split('+');
43311 for(var i = 0; i < editTypeParts.length; i++) {
43312 flags[editTypeParts[i]] = true;
43313 }
43314 }
43315 },
43316
43317 overrideAll: overrideAll
43318};
43319
43320function falseObj(keys) {
43321 var out = {};
43322 for(var i = 0; i < keys.length; i++) out[keys[i]] = false;
43323 return out;
43324}
43325
43326/**
43327 * For attributes that are largely copied from elsewhere into a plot type that doesn't
43328 * support partial redraws - overrides the editType field of all attributes in the object
43329 *
43330 * @param {object} attrs: the attributes to override. Will not be mutated.
43331 * @param {string} editTypeOverride: the new editType to use
43332 * @param {'nested'|'from-root'} overrideContainers:
43333 * - 'nested' will override editType for nested containers but not the root.
43334 * - 'from-root' will also override editType of the root container.
43335 * Containers below the absolute top level (trace or layout root) DO need an
43336 * editType even if they are not `valObject`s themselves (eg `scatter.marker`)
43337 * to handle the case where you edit the whole container.
43338 *
43339 * @return {object} a new attributes object with `editType` modified as directed
43340 */
43341function overrideAll(attrs, editTypeOverride, overrideContainers) {
43342 var out = extendFlat({}, attrs);
43343 for(var key in out) {
43344 var attr = out[key];
43345 if(isPlainObject(attr)) {
43346 out[key] = overrideOne(attr, editTypeOverride, overrideContainers, key);
43347 }
43348 }
43349 if(overrideContainers === 'from-root') out.editType = editTypeOverride;
43350
43351 return out;
43352}
43353
43354function overrideOne(attr, editTypeOverride, overrideContainers, key) {
43355 if(attr.valType) {
43356 var out = extendFlat({}, attr);
43357 out.editType = editTypeOverride;
43358
43359 if(Array.isArray(attr.items)) {
43360 out.items = new Array(attr.items.length);
43361 for(var i = 0; i < attr.items.length; i++) {
43362 out.items[i] = overrideOne(attr.items[i], editTypeOverride, 'from-root');
43363 }
43364 }
43365 return out;
43366 } else {
43367 // don't provide an editType for the _deprecated container
43368 return overrideAll(attr, editTypeOverride,
43369 (key.charAt(0) === '_') ? 'nested' : 'from-root');
43370 }
43371}
43372
43373},{"../lib":178}],206:[function(_dereq_,module,exports){
43374/**
43375* Copyright 2012-2020, Plotly, Inc.
43376* All rights reserved.
43377*
43378* This source code is licensed under the MIT license found in the
43379* LICENSE file in the root directory of this source tree.
43380*/
43381
43382'use strict';
43383
43384var isNumeric = _dereq_('fast-isnumeric');
43385var m4FromQuat = _dereq_('gl-mat4/fromQuat');
43386
43387var Registry = _dereq_('../registry');
43388var Lib = _dereq_('../lib');
43389var Plots = _dereq_('../plots/plots');
43390var AxisIds = _dereq_('../plots/cartesian/axis_ids');
43391var Color = _dereq_('../components/color');
43392
43393var cleanId = AxisIds.cleanId;
43394var getFromTrace = AxisIds.getFromTrace;
43395var traceIs = Registry.traceIs;
43396
43397// clear the promise queue if one of them got rejected
43398exports.clearPromiseQueue = function(gd) {
43399 if(Array.isArray(gd._promises) && gd._promises.length > 0) {
43400 Lib.log('Clearing previous rejected promises from queue.');
43401 }
43402
43403 gd._promises = [];
43404};
43405
43406// make a few changes to the layout right away
43407// before it gets used for anything
43408// backward compatibility and cleanup of nonstandard options
43409exports.cleanLayout = function(layout) {
43410 var i, j;
43411
43412 if(!layout) layout = {};
43413
43414 // cannot have (x|y)axis1, numbering goes axis, axis2, axis3...
43415 if(layout.xaxis1) {
43416 if(!layout.xaxis) layout.xaxis = layout.xaxis1;
43417 delete layout.xaxis1;
43418 }
43419 if(layout.yaxis1) {
43420 if(!layout.yaxis) layout.yaxis = layout.yaxis1;
43421 delete layout.yaxis1;
43422 }
43423 if(layout.scene1) {
43424 if(!layout.scene) layout.scene = layout.scene1;
43425 delete layout.scene1;
43426 }
43427
43428 var axisAttrRegex = (Plots.subplotsRegistry.cartesian || {}).attrRegex;
43429 var polarAttrRegex = (Plots.subplotsRegistry.polar || {}).attrRegex;
43430 var ternaryAttrRegex = (Plots.subplotsRegistry.ternary || {}).attrRegex;
43431 var sceneAttrRegex = (Plots.subplotsRegistry.gl3d || {}).attrRegex;
43432
43433 var keys = Object.keys(layout);
43434 for(i = 0; i < keys.length; i++) {
43435 var key = keys[i];
43436
43437 if(axisAttrRegex && axisAttrRegex.test(key)) {
43438 // modifications to cartesian axes
43439
43440 var ax = layout[key];
43441 if(ax.anchor && ax.anchor !== 'free') {
43442 ax.anchor = cleanId(ax.anchor);
43443 }
43444 if(ax.overlaying) ax.overlaying = cleanId(ax.overlaying);
43445
43446 // old method of axis type - isdate and islog (before category existed)
43447 if(!ax.type) {
43448 if(ax.isdate) ax.type = 'date';
43449 else if(ax.islog) ax.type = 'log';
43450 else if(ax.isdate === false && ax.islog === false) ax.type = 'linear';
43451 }
43452 if(ax.autorange === 'withzero' || ax.autorange === 'tozero') {
43453 ax.autorange = true;
43454 ax.rangemode = 'tozero';
43455 }
43456 delete ax.islog;
43457 delete ax.isdate;
43458 delete ax.categories; // replaced by _categories
43459
43460 // prune empty domain arrays made before the new nestedProperty
43461 if(emptyContainer(ax, 'domain')) delete ax.domain;
43462
43463 // autotick -> tickmode
43464 if(ax.autotick !== undefined) {
43465 if(ax.tickmode === undefined) {
43466 ax.tickmode = ax.autotick ? 'auto' : 'linear';
43467 }
43468 delete ax.autotick;
43469 }
43470
43471 cleanTitle(ax);
43472 } else if(polarAttrRegex && polarAttrRegex.test(key)) {
43473 // modifications for polar
43474
43475 var polar = layout[key];
43476 cleanTitle(polar.radialaxis);
43477 } else if(ternaryAttrRegex && ternaryAttrRegex.test(key)) {
43478 // modifications for ternary
43479
43480 var ternary = layout[key];
43481 cleanTitle(ternary.aaxis);
43482 cleanTitle(ternary.baxis);
43483 cleanTitle(ternary.caxis);
43484 } else if(sceneAttrRegex && sceneAttrRegex.test(key)) {
43485 // modifications for 3D scenes
43486
43487 var scene = layout[key];
43488
43489 // clean old Camera coords
43490 var cameraposition = scene.cameraposition;
43491
43492 if(Array.isArray(cameraposition) && cameraposition[0].length === 4) {
43493 var rotation = cameraposition[0];
43494 var center = cameraposition[1];
43495 var radius = cameraposition[2];
43496 var mat = m4FromQuat([], rotation);
43497 var eye = [];
43498
43499 for(j = 0; j < 3; ++j) {
43500 eye[j] = center[j] + radius * mat[2 + 4 * j];
43501 }
43502
43503 scene.camera = {
43504 eye: {x: eye[0], y: eye[1], z: eye[2]},
43505 center: {x: center[0], y: center[1], z: center[2]},
43506 up: {x: 0, y: 0, z: 1} // we just ignore calculating camera z up in this case
43507 };
43508
43509 delete scene.cameraposition;
43510 }
43511
43512 // clean axis titles
43513 cleanTitle(scene.xaxis);
43514 cleanTitle(scene.yaxis);
43515 cleanTitle(scene.zaxis);
43516 }
43517 }
43518
43519 var annotationsLen = Array.isArray(layout.annotations) ? layout.annotations.length : 0;
43520 for(i = 0; i < annotationsLen; i++) {
43521 var ann = layout.annotations[i];
43522
43523 if(!Lib.isPlainObject(ann)) continue;
43524
43525 if(ann.ref) {
43526 if(ann.ref === 'paper') {
43527 ann.xref = 'paper';
43528 ann.yref = 'paper';
43529 } else if(ann.ref === 'data') {
43530 ann.xref = 'x';
43531 ann.yref = 'y';
43532 }
43533 delete ann.ref;
43534 }
43535
43536 cleanAxRef(ann, 'xref');
43537 cleanAxRef(ann, 'yref');
43538 }
43539
43540 var shapesLen = Array.isArray(layout.shapes) ? layout.shapes.length : 0;
43541 for(i = 0; i < shapesLen; i++) {
43542 var shape = layout.shapes[i];
43543
43544 if(!Lib.isPlainObject(shape)) continue;
43545
43546 cleanAxRef(shape, 'xref');
43547 cleanAxRef(shape, 'yref');
43548 }
43549
43550 var legend = layout.legend;
43551 if(legend) {
43552 // check for old-style legend positioning (x or y is +/- 100)
43553 if(legend.x > 3) {
43554 legend.x = 1.02;
43555 legend.xanchor = 'left';
43556 } else if(legend.x < -2) {
43557 legend.x = -0.02;
43558 legend.xanchor = 'right';
43559 }
43560
43561 if(legend.y > 3) {
43562 legend.y = 1.02;
43563 legend.yanchor = 'bottom';
43564 } else if(legend.y < -2) {
43565 legend.y = -0.02;
43566 legend.yanchor = 'top';
43567 }
43568 }
43569
43570 // clean plot title
43571 cleanTitle(layout);
43572
43573 /*
43574 * Moved from rotate -> orbit for dragmode
43575 */
43576 if(layout.dragmode === 'rotate') layout.dragmode = 'orbit';
43577
43578 // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
43579 // supported, but new tinycolor does not because they're not valid css
43580 Color.clean(layout);
43581
43582 // clean the layout container in layout.template
43583 if(layout.template && layout.template.layout) {
43584 exports.cleanLayout(layout.template.layout);
43585 }
43586
43587 return layout;
43588};
43589
43590function cleanAxRef(container, attr) {
43591 var valIn = container[attr];
43592 var axLetter = attr.charAt(0);
43593 if(valIn && valIn !== 'paper') {
43594 container[attr] = cleanId(valIn, axLetter);
43595 }
43596}
43597
43598/**
43599 * Cleans up old title attribute structure (flat) in favor of the new one (nested).
43600 *
43601 * @param {Object} titleContainer - an object potentially including deprecated title attributes
43602 */
43603function cleanTitle(titleContainer) {
43604 if(titleContainer) {
43605 // title -> title.text
43606 // (although title used to be a string attribute,
43607 // numbers are accepted as well)
43608 if(typeof titleContainer.title === 'string' || typeof titleContainer.title === 'number') {
43609 titleContainer.title = {
43610 text: titleContainer.title
43611 };
43612 }
43613
43614 rewireAttr('titlefont', 'font');
43615 rewireAttr('titleposition', 'position');
43616 rewireAttr('titleside', 'side');
43617 rewireAttr('titleoffset', 'offset');
43618 }
43619
43620 function rewireAttr(oldAttrName, newAttrName) {
43621 var oldAttrSet = titleContainer[oldAttrName];
43622 var newAttrSet = titleContainer.title && titleContainer.title[newAttrName];
43623
43624 if(oldAttrSet && !newAttrSet) {
43625 // Ensure title object exists
43626 if(!titleContainer.title) {
43627 titleContainer.title = {};
43628 }
43629
43630 titleContainer.title[newAttrName] = titleContainer[oldAttrName];
43631 delete titleContainer[oldAttrName];
43632 }
43633 }
43634}
43635
43636/*
43637 * cleanData: Make a few changes to the data for backward compatibility
43638 * before it gets used for anything. Modifies the data traces users provide.
43639 *
43640 * Important: if you're going to add something here that modifies a data array,
43641 * update it in place so the new array === the old one.
43642 */
43643exports.cleanData = function(data) {
43644 for(var tracei = 0; tracei < data.length; tracei++) {
43645 var trace = data[tracei];
43646 var i;
43647
43648 // use xbins to bin data in x, and ybins to bin data in y
43649 if(trace.type === 'histogramy' && 'xbins' in trace && !('ybins' in trace)) {
43650 trace.ybins = trace.xbins;
43651 delete trace.xbins;
43652 }
43653
43654 // error_y.opacity is obsolete - merge into color
43655 if(trace.error_y && 'opacity' in trace.error_y) {
43656 var dc = Color.defaults;
43657 var yeColor = trace.error_y.color || (traceIs(trace, 'bar') ?
43658 Color.defaultLine :
43659 dc[tracei % dc.length]);
43660 trace.error_y.color = Color.addOpacity(
43661 Color.rgb(yeColor),
43662 Color.opacity(yeColor) * trace.error_y.opacity);
43663 delete trace.error_y.opacity;
43664 }
43665
43666 // convert bardir to orientation, and put the data into
43667 // the axes it's eventually going to be used with
43668 if('bardir' in trace) {
43669 if(trace.bardir === 'h' && (traceIs(trace, 'bar') ||
43670 trace.type.substr(0, 9) === 'histogram')) {
43671 trace.orientation = 'h';
43672 exports.swapXYData(trace);
43673 }
43674 delete trace.bardir;
43675 }
43676
43677 // now we have only one 1D histogram type, and whether
43678 // it uses x or y data depends on trace.orientation
43679 if(trace.type === 'histogramy') exports.swapXYData(trace);
43680 if(trace.type === 'histogramx' || trace.type === 'histogramy') {
43681 trace.type = 'histogram';
43682 }
43683
43684 // scl->scale, reversescl->reversescale
43685 if('scl' in trace && !('colorscale' in trace)) {
43686 trace.colorscale = trace.scl;
43687 delete trace.scl;
43688 }
43689 if('reversescl' in trace && !('reversescale' in trace)) {
43690 trace.reversescale = trace.reversescl;
43691 delete trace.reversescl;
43692 }
43693
43694 // axis ids x1 -> x, y1-> y
43695 if(trace.xaxis) trace.xaxis = cleanId(trace.xaxis, 'x');
43696 if(trace.yaxis) trace.yaxis = cleanId(trace.yaxis, 'y');
43697
43698 // scene ids scene1 -> scene
43699 if(traceIs(trace, 'gl3d') && trace.scene) {
43700 trace.scene = Plots.subplotsRegistry.gl3d.cleanId(trace.scene);
43701 }
43702
43703 if(!traceIs(trace, 'pie-like') && !traceIs(trace, 'bar-like')) {
43704 if(Array.isArray(trace.textposition)) {
43705 for(i = 0; i < trace.textposition.length; i++) {
43706 trace.textposition[i] = cleanTextPosition(trace.textposition[i]);
43707 }
43708 } else if(trace.textposition) {
43709 trace.textposition = cleanTextPosition(trace.textposition);
43710 }
43711 }
43712
43713 // fix typo in colorscale definition
43714 var _module = Registry.getModule(trace);
43715 if(_module && _module.colorbar) {
43716 var containerName = _module.colorbar.container;
43717 var container = containerName ? trace[containerName] : trace;
43718 if(container && container.colorscale) {
43719 if(container.colorscale === 'YIGnBu') container.colorscale = 'YlGnBu';
43720 if(container.colorscale === 'YIOrRd') container.colorscale = 'YlOrRd';
43721 }
43722 }
43723
43724 // fix typo in surface 'highlight*' definitions
43725 if(trace.type === 'surface' && Lib.isPlainObject(trace.contours)) {
43726 var dims = ['x', 'y', 'z'];
43727
43728 for(i = 0; i < dims.length; i++) {
43729 var opts = trace.contours[dims[i]];
43730
43731 if(!Lib.isPlainObject(opts)) continue;
43732
43733 if(opts.highlightColor) {
43734 opts.highlightcolor = opts.highlightColor;
43735 delete opts.highlightColor;
43736 }
43737
43738 if(opts.highlightWidth) {
43739 opts.highlightwidth = opts.highlightWidth;
43740 delete opts.highlightWidth;
43741 }
43742 }
43743 }
43744
43745 // fixes from converting finance from transforms to real trace types
43746 if(trace.type === 'candlestick' || trace.type === 'ohlc') {
43747 var increasingShowlegend = (trace.increasing || {}).showlegend !== false;
43748 var decreasingShowlegend = (trace.decreasing || {}).showlegend !== false;
43749 var increasingName = cleanFinanceDir(trace.increasing);
43750 var decreasingName = cleanFinanceDir(trace.decreasing);
43751
43752 // now figure out something smart to do with the separate direction
43753 // names we removed
43754 if((increasingName !== false) && (decreasingName !== false)) {
43755 // both sub-names existed: base name previously had no effect
43756 // so ignore it and try to find a shared part of the sub-names
43757
43758 var newName = commonPrefix(
43759 increasingName, decreasingName,
43760 increasingShowlegend, decreasingShowlegend
43761 );
43762 // if no common part, leave whatever name was (or wasn't) there
43763 if(newName) trace.name = newName;
43764 } else if((increasingName || decreasingName) && !trace.name) {
43765 // one sub-name existed but not the base name - just use the sub-name
43766 trace.name = increasingName || decreasingName;
43767 }
43768 }
43769
43770 // transforms backward compatibility fixes
43771 if(Array.isArray(trace.transforms)) {
43772 var transforms = trace.transforms;
43773
43774 for(i = 0; i < transforms.length; i++) {
43775 var transform = transforms[i];
43776
43777 if(!Lib.isPlainObject(transform)) continue;
43778
43779 switch(transform.type) {
43780 case 'filter':
43781 if(transform.filtersrc) {
43782 transform.target = transform.filtersrc;
43783 delete transform.filtersrc;
43784 }
43785
43786 if(transform.calendar) {
43787 if(!transform.valuecalendar) {
43788 transform.valuecalendar = transform.calendar;
43789 }
43790 delete transform.calendar;
43791 }
43792 break;
43793
43794 case 'groupby':
43795 // Name has changed from `style` to `styles`, so use `style` but prefer `styles`:
43796 transform.styles = transform.styles || transform.style;
43797
43798 if(transform.styles && !Array.isArray(transform.styles)) {
43799 var prevStyles = transform.styles;
43800 var styleKeys = Object.keys(prevStyles);
43801
43802 transform.styles = [];
43803 for(var j = 0; j < styleKeys.length; j++) {
43804 transform.styles.push({
43805 target: styleKeys[j],
43806 value: prevStyles[styleKeys[j]]
43807 });
43808 }
43809 }
43810 break;
43811 }
43812 }
43813 }
43814
43815 // prune empty containers made before the new nestedProperty
43816 if(emptyContainer(trace, 'line')) delete trace.line;
43817 if('marker' in trace) {
43818 if(emptyContainer(trace.marker, 'line')) delete trace.marker.line;
43819 if(emptyContainer(trace, 'marker')) delete trace.marker;
43820 }
43821
43822 // sanitize rgb(fractions) and rgba(fractions) that old tinycolor
43823 // supported, but new tinycolor does not because they're not valid css
43824 Color.clean(trace);
43825
43826 // remove obsolete autobin(x|y) attributes, but only if true
43827 // if false, this needs to happen in Histogram.calc because it
43828 // can be a one-time autobin so we need to know the results before
43829 // we can push them back into the trace.
43830 if(trace.autobinx) {
43831 delete trace.autobinx;
43832 delete trace.xbins;
43833 }
43834 if(trace.autobiny) {
43835 delete trace.autobiny;
43836 delete trace.ybins;
43837 }
43838
43839 cleanTitle(trace);
43840 if(trace.colorbar) cleanTitle(trace.colorbar);
43841 if(trace.marker && trace.marker.colorbar) cleanTitle(trace.marker.colorbar);
43842 if(trace.line && trace.line.colorbar) cleanTitle(trace.line.colorbar);
43843 if(trace.aaxis) cleanTitle(trace.aaxis);
43844 if(trace.baxis) cleanTitle(trace.baxis);
43845 }
43846};
43847
43848function cleanFinanceDir(dirContainer) {
43849 if(!Lib.isPlainObject(dirContainer)) return false;
43850
43851 var dirName = dirContainer.name;
43852
43853 delete dirContainer.name;
43854 delete dirContainer.showlegend;
43855
43856 return (typeof dirName === 'string' || typeof dirName === 'number') && String(dirName);
43857}
43858
43859function commonPrefix(name1, name2, show1, show2) {
43860 // if only one is shown in the legend, use that
43861 if(show1 && !show2) return name1;
43862 if(show2 && !show1) return name2;
43863
43864 // if both or neither are in the legend, check if one is blank (or whitespace)
43865 // and use the other one
43866 // note that hover labels can still use the name even if the legend doesn't
43867 if(!name1.trim()) return name2;
43868 if(!name2.trim()) return name1;
43869
43870 var minLen = Math.min(name1.length, name2.length);
43871 var i;
43872 for(i = 0; i < minLen; i++) {
43873 if(name1.charAt(i) !== name2.charAt(i)) break;
43874 }
43875
43876 var out = name1.substr(0, i);
43877 return out.trim();
43878}
43879
43880// textposition - support partial attributes (ie just 'top')
43881// and incorrect use of middle / center etc.
43882function cleanTextPosition(textposition) {
43883 var posY = 'middle';
43884 var posX = 'center';
43885
43886 if(typeof textposition === 'string') {
43887 if(textposition.indexOf('top') !== -1) posY = 'top';
43888 else if(textposition.indexOf('bottom') !== -1) posY = 'bottom';
43889
43890 if(textposition.indexOf('left') !== -1) posX = 'left';
43891 else if(textposition.indexOf('right') !== -1) posX = 'right';
43892 }
43893
43894 return posY + ' ' + posX;
43895}
43896
43897function emptyContainer(outer, innerStr) {
43898 return (innerStr in outer) &&
43899 (typeof outer[innerStr] === 'object') &&
43900 (Object.keys(outer[innerStr]).length === 0);
43901}
43902
43903
43904// swap all the data and data attributes associated with x and y
43905exports.swapXYData = function(trace) {
43906 var i;
43907 Lib.swapAttrs(trace, ['?', '?0', 'd?', '?bins', 'nbins?', 'autobin?', '?src', 'error_?']);
43908 if(Array.isArray(trace.z) && Array.isArray(trace.z[0])) {
43909 if(trace.transpose) delete trace.transpose;
43910 else trace.transpose = true;
43911 }
43912 if(trace.error_x && trace.error_y) {
43913 var errorY = trace.error_y;
43914 var copyYstyle = ('copy_ystyle' in errorY) ?
43915 errorY.copy_ystyle :
43916 !(errorY.color || errorY.thickness || errorY.width);
43917 Lib.swapAttrs(trace, ['error_?.copy_ystyle']);
43918 if(copyYstyle) {
43919 Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']);
43920 }
43921 }
43922 if(typeof trace.hoverinfo === 'string') {
43923 var hoverInfoParts = trace.hoverinfo.split('+');
43924 for(i = 0; i < hoverInfoParts.length; i++) {
43925 if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y';
43926 else if(hoverInfoParts[i] === 'y') hoverInfoParts[i] = 'x';
43927 }
43928 trace.hoverinfo = hoverInfoParts.join('+');
43929 }
43930};
43931
43932// coerce traceIndices input to array of trace indices
43933exports.coerceTraceIndices = function(gd, traceIndices) {
43934 if(isNumeric(traceIndices)) {
43935 return [traceIndices];
43936 } else if(!Array.isArray(traceIndices) || !traceIndices.length) {
43937 return gd.data.map(function(_, i) { return i; });
43938 } else if(Array.isArray(traceIndices)) {
43939 var traceIndicesOut = [];
43940 for(var i = 0; i < traceIndices.length; i++) {
43941 if(Lib.isIndex(traceIndices[i], gd.data.length)) {
43942 traceIndicesOut.push(traceIndices[i]);
43943 } else {
43944 Lib.warn('trace index (', traceIndices[i], ') is not a number or is out of bounds');
43945 }
43946 }
43947 return traceIndicesOut;
43948 }
43949
43950 return traceIndices;
43951};
43952
43953/**
43954 * Manages logic around array container item creation / deletion / update
43955 * that nested property alone can't handle.
43956 *
43957 * @param {Object} np
43958 * nested property of update attribute string about trace or layout object
43959 * @param {*} newVal
43960 * update value passed to restyle / relayout / update
43961 * @param {Object} undoit
43962 * undo hash (N.B. undoit may be mutated here).
43963 *
43964 */
43965exports.manageArrayContainers = function(np, newVal, undoit) {
43966 var obj = np.obj;
43967 var parts = np.parts;
43968 var pLength = parts.length;
43969 var pLast = parts[pLength - 1];
43970
43971 var pLastIsNumber = isNumeric(pLast);
43972
43973 if(pLastIsNumber && newVal === null) {
43974 // delete item
43975
43976 // Clear item in array container when new value is null
43977 var contPath = parts.slice(0, pLength - 1).join('.');
43978 var cont = Lib.nestedProperty(obj, contPath).get();
43979 cont.splice(pLast, 1);
43980
43981 // Note that nested property clears null / undefined at end of
43982 // array container, but not within them.
43983 } else if(pLastIsNumber && np.get() === undefined) {
43984 // create item
43985
43986 // When adding a new item, make sure undo command will remove it
43987 if(np.get() === undefined) undoit[np.astr] = null;
43988
43989 np.set(newVal);
43990 } else {
43991 // update item
43992
43993 // If the last part of attribute string isn't a number,
43994 // np.set is all we need.
43995 np.set(newVal);
43996 }
43997};
43998
43999/*
44000 * Match the part to strip off to turn an attribute into its parent
44001 * really it should be either '.some_characters' or '[number]'
44002 * but we're a little more permissive here and match either
44003 * '.not_brackets_or_dot' or '[not_brackets_or_dot]'
44004 */
44005var ATTR_TAIL_RE = /(\.[^\[\]\.]+|\[[^\[\]\.]+\])$/;
44006
44007function getParent(attr) {
44008 var tail = attr.search(ATTR_TAIL_RE);
44009 if(tail > 0) return attr.substr(0, tail);
44010}
44011
44012/*
44013 * hasParent: does an attribute object contain a parent of the given attribute?
44014 * for example, given 'images[2].x' do we also have 'images' or 'images[2]'?
44015 *
44016 * @param {Object} aobj
44017 * update object, whose keys are attribute strings and values are their new settings
44018 * @param {string} attr
44019 * the attribute string to test against
44020 * @returns {Boolean}
44021 * is a parent of attr present in aobj?
44022 */
44023exports.hasParent = function(aobj, attr) {
44024 var attrParent = getParent(attr);
44025 while(attrParent) {
44026 if(attrParent in aobj) return true;
44027 attrParent = getParent(attrParent);
44028 }
44029 return false;
44030};
44031
44032/**
44033 * Empty out types for all axes containing these traces so we auto-set them again
44034 *
44035 * @param {object} gd
44036 * @param {[integer]} traces: trace indices to search for axes to clear the types of
44037 * @param {object} layoutUpdate: any update being done concurrently to the layout,
44038 * which may supercede clearing the axis types
44039 */
44040var axLetters = ['x', 'y', 'z'];
44041exports.clearAxisTypes = function(gd, traces, layoutUpdate) {
44042 for(var i = 0; i < traces.length; i++) {
44043 var trace = gd._fullData[i];
44044 for(var j = 0; j < 3; j++) {
44045 var ax = getFromTrace(gd, trace, axLetters[j]);
44046
44047 // do not clear log type - that's never an auto result so must have been intentional
44048 if(ax && ax.type !== 'log') {
44049 var axAttr = ax._name;
44050 var sceneName = ax._id.substr(1);
44051 if(sceneName.substr(0, 5) === 'scene') {
44052 if(layoutUpdate[sceneName] !== undefined) continue;
44053 axAttr = sceneName + '.' + axAttr;
44054 }
44055 var typeAttr = axAttr + '.type';
44056
44057 if(layoutUpdate[axAttr] === undefined && layoutUpdate[typeAttr] === undefined) {
44058 Lib.nestedProperty(gd.layout, typeAttr).set(null);
44059 }
44060 }
44061 }
44062 }
44063};
44064
44065},{"../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){
44066/**
44067* Copyright 2012-2020, Plotly, Inc.
44068* All rights reserved.
44069*
44070* This source code is licensed under the MIT license found in the
44071* LICENSE file in the root directory of this source tree.
44072*/
44073
44074'use strict';
44075
44076var main = _dereq_('./plot_api');
44077
44078exports.plot = main.plot;
44079exports.newPlot = main.newPlot;
44080exports.restyle = main.restyle;
44081exports.relayout = main.relayout;
44082exports.redraw = main.redraw;
44083exports.update = main.update;
44084exports._guiRestyle = main._guiRestyle;
44085exports._guiRelayout = main._guiRelayout;
44086exports._guiUpdate = main._guiUpdate;
44087exports._storeDirectGUIEdit = main._storeDirectGUIEdit;
44088exports.react = main.react;
44089exports.extendTraces = main.extendTraces;
44090exports.prependTraces = main.prependTraces;
44091exports.addTraces = main.addTraces;
44092exports.deleteTraces = main.deleteTraces;
44093exports.moveTraces = main.moveTraces;
44094exports.purge = main.purge;
44095exports.addFrames = main.addFrames;
44096exports.deleteFrames = main.deleteFrames;
44097exports.animate = main.animate;
44098exports.setPlotConfig = main.setPlotConfig;
44099
44100exports.toImage = _dereq_('./to_image');
44101exports.validate = _dereq_('./validate');
44102exports.downloadImage = _dereq_('../snapshot/download');
44103
44104var templateApi = _dereq_('./template_api');
44105exports.makeTemplate = templateApi.makeTemplate;
44106exports.validateTemplate = templateApi.validateTemplate;
44107
44108},{"../snapshot/download":271,"./plot_api":209,"./template_api":214,"./to_image":215,"./validate":216}],208:[function(_dereq_,module,exports){
44109/**
44110* Copyright 2012-2020, Plotly, Inc.
44111* All rights reserved.
44112*
44113* This source code is licensed under the MIT license found in the
44114* LICENSE file in the root directory of this source tree.
44115*/
44116
44117
44118'use strict';
44119
44120var isPlainObject = _dereq_('../lib/is_plain_object');
44121var noop = _dereq_('../lib/noop');
44122var Loggers = _dereq_('../lib/loggers');
44123var sorterAsc = _dereq_('../lib/search').sorterAsc;
44124var Registry = _dereq_('../registry');
44125
44126
44127exports.containerArrayMatch = _dereq_('./container_array_match');
44128
44129var isAddVal = exports.isAddVal = function isAddVal(val) {
44130 return val === 'add' || isPlainObject(val);
44131};
44132
44133var isRemoveVal = exports.isRemoveVal = function isRemoveVal(val) {
44134 return val === null || val === 'remove';
44135};
44136
44137/*
44138 * applyContainerArrayChanges: for managing arrays of layout components in relayout
44139 * handles them all with a consistent interface.
44140 *
44141 * Here are the supported actions -> relayout calls -> edits we get here
44142 * (as prepared in _relayout):
44143 *
44144 * add an empty obj -> {'annotations[2]': 'add'} -> {2: {'': 'add'}}
44145 * add a specific obj -> {'annotations[2]': {attrs}} -> {2: {'': {attrs}}}
44146 * delete an obj -> {'annotations[2]': 'remove'} -> {2: {'': 'remove'}}
44147 * -> {'annotations[2]': null} -> {2: {'': null}}
44148 * delete the whole array -> {'annotations': 'remove'} -> {'': {'': 'remove'}}
44149 * -> {'annotations': null} -> {'': {'': null}}
44150 * edit an object -> {'annotations[2].text': 'boo'} -> {2: {'text': 'boo'}}
44151 *
44152 * You can combine many edits to different objects. Objects are added and edited
44153 * in ascending order, then removed in descending order.
44154 * For example, starting with [a, b, c], if you want to:
44155 * - replace b with d:
44156 * {'annotations[1]': d, 'annotations[2]': null} (b is item 2 after adding d)
44157 * - add a new item d between a and b, and edit b:
44158 * {'annotations[1]': d, 'annotations[2].x': newX} (b is item 2 after adding d)
44159 * - delete b and edit c:
44160 * {'annotations[1]': null, 'annotations[2].x': newX} (c is edited before b is removed)
44161 *
44162 * You CANNOT combine adding/deleting an item at index `i` with edits to the same index `i`
44163 * You CANNOT combine replacing/deleting the whole array with anything else (for the same array).
44164 *
44165 * @param {HTMLDivElement} gd
44166 * the DOM element of the graph container div
44167 * @param {Lib.nestedProperty} componentType: the array we are editing
44168 * @param {Object} edits
44169 * the changes to make; keys are indices to edit, values are themselves objects:
44170 * {attr: newValue} of changes to make to that index (with add/remove behavior
44171 * in special values of the empty attr)
44172 * @param {Object} flags
44173 * the flags for which actions we're going to perform to display these (and
44174 * any other) changes. If we're already `recalc`ing, we don't need to redraw
44175 * individual items
44176 * @param {function} _nestedProperty
44177 * a (possibly modified for gui edits) nestedProperty constructor
44178 * The modified version takes a 3rd argument, for a prefix to the attribute
44179 * string necessary for storing GUI edits
44180 *
44181 * @returns {bool} `true` if it managed to complete drawing of the changes
44182 * `false` would mean the parent should replot.
44183 */
44184exports.applyContainerArrayChanges = function applyContainerArrayChanges(gd, np, edits, flags, _nestedProperty) {
44185 var componentType = np.astr;
44186 var supplyComponentDefaults = Registry.getComponentMethod(componentType, 'supplyLayoutDefaults');
44187 var draw = Registry.getComponentMethod(componentType, 'draw');
44188 var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
44189 var replotLater = flags.replot || flags.recalc || (supplyComponentDefaults === noop) || (draw === noop);
44190 var layout = gd.layout;
44191 var fullLayout = gd._fullLayout;
44192
44193 if(edits['']) {
44194 if(Object.keys(edits).length > 1) {
44195 Loggers.warn('Full array edits are incompatible with other edits',
44196 componentType);
44197 }
44198
44199 var fullVal = edits[''][''];
44200
44201 if(isRemoveVal(fullVal)) np.set(null);
44202 else if(Array.isArray(fullVal)) np.set(fullVal);
44203 else {
44204 Loggers.warn('Unrecognized full array edit value', componentType, fullVal);
44205 return true;
44206 }
44207
44208 if(replotLater) return false;
44209
44210 supplyComponentDefaults(layout, fullLayout);
44211 draw(gd);
44212 return true;
44213 }
44214
44215 var componentNums = Object.keys(edits).map(Number).sort(sorterAsc);
44216 var componentArrayIn = np.get();
44217 var componentArray = componentArrayIn || [];
44218 // componentArrayFull is used just to keep splices in line between
44219 // full and input arrays, so private keys can be copied over after
44220 // redoing supplyDefaults
44221 // TODO: this assumes componentArray is in gd.layout - which will not be
44222 // true after we extend this to restyle
44223 var componentArrayFull = _nestedProperty(fullLayout, componentType).get();
44224
44225 var deletes = [];
44226 var firstIndexChange = -1;
44227 var maxIndex = componentArray.length;
44228 var i;
44229 var j;
44230 var componentNum;
44231 var objEdits;
44232 var objKeys;
44233 var objVal;
44234 var adding, prefix;
44235
44236 // first make the add and edit changes
44237 for(i = 0; i < componentNums.length; i++) {
44238 componentNum = componentNums[i];
44239 objEdits = edits[componentNum];
44240 objKeys = Object.keys(objEdits);
44241 objVal = objEdits[''],
44242 adding = isAddVal(objVal);
44243
44244 if(componentNum < 0 || componentNum > componentArray.length - (adding ? 0 : 1)) {
44245 Loggers.warn('index out of range', componentType, componentNum);
44246 continue;
44247 }
44248
44249 if(objVal !== undefined) {
44250 if(objKeys.length > 1) {
44251 Loggers.warn(
44252 'Insertion & removal are incompatible with edits to the same index.',
44253 componentType, componentNum);
44254 }
44255
44256 if(isRemoveVal(objVal)) {
44257 deletes.push(componentNum);
44258 } else if(adding) {
44259 if(objVal === 'add') objVal = {};
44260 componentArray.splice(componentNum, 0, objVal);
44261 if(componentArrayFull) componentArrayFull.splice(componentNum, 0, {});
44262 } else {
44263 Loggers.warn('Unrecognized full object edit value',
44264 componentType, componentNum, objVal);
44265 }
44266
44267 if(firstIndexChange === -1) firstIndexChange = componentNum;
44268 } else {
44269 for(j = 0; j < objKeys.length; j++) {
44270 prefix = componentType + '[' + componentNum + '].';
44271 _nestedProperty(componentArray[componentNum], objKeys[j], prefix)
44272 .set(objEdits[objKeys[j]]);
44273 }
44274 }
44275 }
44276
44277 // now do deletes
44278 for(i = deletes.length - 1; i >= 0; i--) {
44279 componentArray.splice(deletes[i], 1);
44280 // TODO: this drops private keys that had been stored in componentArrayFull
44281 // does this have any ill effects?
44282 if(componentArrayFull) componentArrayFull.splice(deletes[i], 1);
44283 }
44284
44285 if(!componentArray.length) np.set(null);
44286 else if(!componentArrayIn) np.set(componentArray);
44287
44288 if(replotLater) return false;
44289
44290 supplyComponentDefaults(layout, fullLayout);
44291
44292 // finally draw all the components we need to
44293 // if we added or removed any, redraw all after it
44294 if(drawOne !== noop) {
44295 var indicesToDraw;
44296 if(firstIndexChange === -1) {
44297 // there's no re-indexing to do, so only redraw components that changed
44298 indicesToDraw = componentNums;
44299 } else {
44300 // in case the component array was shortened, we still need do call
44301 // drawOne on the latter items so they get properly removed
44302 maxIndex = Math.max(componentArray.length, maxIndex);
44303 indicesToDraw = [];
44304 for(i = 0; i < componentNums.length; i++) {
44305 componentNum = componentNums[i];
44306 if(componentNum >= firstIndexChange) break;
44307 indicesToDraw.push(componentNum);
44308 }
44309 for(i = firstIndexChange; i < maxIndex; i++) {
44310 indicesToDraw.push(i);
44311 }
44312 }
44313 for(i = 0; i < indicesToDraw.length; i++) {
44314 drawOne(gd, indicesToDraw[i]);
44315 }
44316 } else draw(gd);
44317
44318 return true;
44319};
44320
44321},{"../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){
44322/**
44323* Copyright 2012-2020, Plotly, Inc.
44324* All rights reserved.
44325*
44326* This source code is licensed under the MIT license found in the
44327* LICENSE file in the root directory of this source tree.
44328*/
44329
44330'use strict';
44331
44332var d3 = _dereq_('d3');
44333var isNumeric = _dereq_('fast-isnumeric');
44334var hasHover = _dereq_('has-hover');
44335
44336var Lib = _dereq_('../lib');
44337var nestedProperty = Lib.nestedProperty;
44338
44339var Events = _dereq_('../lib/events');
44340var Queue = _dereq_('../lib/queue');
44341
44342var Registry = _dereq_('../registry');
44343var PlotSchema = _dereq_('./plot_schema');
44344var Plots = _dereq_('../plots/plots');
44345var Polar = _dereq_('../plots/polar/legacy');
44346
44347var Axes = _dereq_('../plots/cartesian/axes');
44348var Drawing = _dereq_('../components/drawing');
44349var Color = _dereq_('../components/color');
44350var initInteractions = _dereq_('../plots/cartesian/graph_interact').initInteractions;
44351var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
44352var svgTextUtils = _dereq_('../lib/svg_text_utils');
44353var clearSelect = _dereq_('../plots/cartesian/select').clearSelect;
44354
44355var dfltConfig = _dereq_('./plot_config').dfltConfig;
44356var manageArrays = _dereq_('./manage_arrays');
44357var helpers = _dereq_('./helpers');
44358var subroutines = _dereq_('./subroutines');
44359var editTypes = _dereq_('./edit_types');
44360
44361var AX_NAME_PATTERN = _dereq_('../plots/cartesian/constants').AX_NAME_PATTERN;
44362
44363var numericNameWarningCount = 0;
44364var numericNameWarningCountLimit = 5;
44365
44366/**
44367 * Main plot-creation function
44368 *
44369 * @param {string id or DOM element} gd
44370 * the id or DOM element of the graph container div
44371 * @param {array of objects} data
44372 * array of traces, containing the data and display information for each trace
44373 * @param {object} layout
44374 * object describing the overall display of the plot,
44375 * all the stuff that doesn't pertain to any individual trace
44376 * @param {object} config
44377 * configuration options (see ./plot_config.js for more info)
44378 *
44379 * OR
44380 *
44381 * @param {string id or DOM element} gd
44382 * the id or DOM element of the graph container div
44383 * @param {object} figure
44384 * object containing `data`, `layout`, `config`, and `frames` members
44385 *
44386 */
44387function plot(gd, data, layout, config) {
44388 var frames;
44389
44390 gd = Lib.getGraphDiv(gd);
44391
44392 // Events.init is idempotent and bails early if gd has already been init'd
44393 Events.init(gd);
44394
44395 if(Lib.isPlainObject(data)) {
44396 var obj = data;
44397 data = obj.data;
44398 layout = obj.layout;
44399 config = obj.config;
44400 frames = obj.frames;
44401 }
44402
44403 var okToPlot = Events.triggerHandler(gd, 'plotly_beforeplot', [data, layout, config]);
44404 if(okToPlot === false) return Promise.reject();
44405
44406 // if there's no data or layout, and this isn't yet a plotly plot
44407 // container, log a warning to help plotly.js users debug
44408 if(!data && !layout && !Lib.isPlotDiv(gd)) {
44409 Lib.warn('Calling Plotly.plot as if redrawing ' +
44410 'but this container doesn\'t yet have a plot.', gd);
44411 }
44412
44413 function addFrames() {
44414 if(frames) {
44415 return exports.addFrames(gd, frames);
44416 }
44417 }
44418
44419 // transfer configuration options to gd until we move over to
44420 // a more OO like model
44421 setPlotContext(gd, config);
44422
44423 if(!layout) layout = {};
44424
44425 // hook class for plots main container (in case of plotly.js
44426 // this won't be #embedded-graph or .js-tab-contents)
44427 d3.select(gd).classed('js-plotly-plot', true);
44428
44429 // off-screen getBoundingClientRect testing space,
44430 // in #js-plotly-tester (and stored as Drawing.tester)
44431 // so we can share cached text across tabs
44432 Drawing.makeTester();
44433
44434 // collect promises for any async actions during plotting
44435 // any part of the plotting code can push to gd._promises, then
44436 // before we move to the next step, we check that they're all
44437 // complete, and empty out the promise list again.
44438 if(!Array.isArray(gd._promises)) gd._promises = [];
44439
44440 var graphWasEmpty = ((gd.data || []).length === 0 && Array.isArray(data));
44441
44442 // if there is already data on the graph, append the new data
44443 // if you only want to redraw, pass a non-array for data
44444 if(Array.isArray(data)) {
44445 helpers.cleanData(data);
44446
44447 if(graphWasEmpty) gd.data = data;
44448 else gd.data.push.apply(gd.data, data);
44449
44450 // for routines outside graph_obj that want a clean tab
44451 // (rather than appending to an existing one) gd.empty
44452 // is used to determine whether to make a new tab
44453 gd.empty = false;
44454 }
44455
44456 if(!gd.layout || graphWasEmpty) {
44457 gd.layout = helpers.cleanLayout(layout);
44458 }
44459
44460 Plots.supplyDefaults(gd);
44461
44462 var fullLayout = gd._fullLayout;
44463 var hasCartesian = fullLayout._has('cartesian');
44464
44465 // Legacy polar plots
44466 if(!fullLayout._has('polar') && data && data[0] && data[0].r) {
44467 Lib.log('Legacy polar charts are deprecated!');
44468 return plotLegacyPolar(gd, data, layout);
44469 }
44470
44471 // so we don't try to re-call Plotly.plot from inside
44472 // legend and colorbar, if margins changed
44473 fullLayout._replotting = true;
44474
44475 // make or remake the framework if we need to
44476 if(graphWasEmpty || fullLayout._shouldCreateBgLayer) {
44477 makePlotFramework(gd);
44478
44479 if(fullLayout._shouldCreateBgLayer) {
44480 delete fullLayout._shouldCreateBgLayer;
44481 }
44482 }
44483
44484 // polar need a different framework
44485 if(gd.framework !== makePlotFramework) {
44486 gd.framework = makePlotFramework;
44487 makePlotFramework(gd);
44488 }
44489
44490 // clear gradient defs on each .plot call, because we know we'll loop through all traces
44491 Drawing.initGradients(gd);
44492
44493 // save initial show spikes once per graph
44494 if(graphWasEmpty) Axes.saveShowSpikeInitial(gd);
44495
44496 // prepare the data and find the autorange
44497
44498 // generate calcdata, if we need to
44499 // to force redoing calcdata, just delete it before calling Plotly.plot
44500 var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
44501 if(recalc) Plots.doCalcdata(gd);
44502
44503 // in case it has changed, attach fullData traces to calcdata
44504 for(var i = 0; i < gd.calcdata.length; i++) {
44505 gd.calcdata[i][0].trace = gd._fullData[i];
44506 }
44507
44508 // make the figure responsive
44509 if(gd._context.responsive) {
44510 if(!gd._responsiveChartHandler) {
44511 // Keep a reference to the resize handler to purge it down the road
44512 gd._responsiveChartHandler = function() { if(!Lib.isHidden(gd)) Plots.resize(gd); };
44513
44514 // Listen to window resize
44515 window.addEventListener('resize', gd._responsiveChartHandler);
44516 }
44517 } else {
44518 Lib.clearResponsive(gd);
44519 }
44520
44521 /*
44522 * start async-friendly code - now we're actually drawing things
44523 */
44524
44525 var oldMargins = Lib.extendFlat({}, fullLayout._size);
44526
44527 // draw framework first so that margin-pushing
44528 // components can position themselves correctly
44529 var drawFrameworkCalls = 0;
44530 function drawFramework() {
44531 var basePlotModules = fullLayout._basePlotModules;
44532
44533 for(var i = 0; i < basePlotModules.length; i++) {
44534 if(basePlotModules[i].drawFramework) {
44535 basePlotModules[i].drawFramework(gd);
44536 }
44537 }
44538
44539 if(!fullLayout._glcanvas && fullLayout._has('gl')) {
44540 fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data([{
44541 key: 'contextLayer',
44542 context: true,
44543 pick: false
44544 }, {
44545 key: 'focusLayer',
44546 context: false,
44547 pick: false
44548 }, {
44549 key: 'pickLayer',
44550 context: false,
44551 pick: true
44552 }], function(d) { return d.key; });
44553
44554 fullLayout._glcanvas.enter().append('canvas')
44555 .attr('class', function(d) {
44556 return 'gl-canvas gl-canvas-' + d.key.replace('Layer', '');
44557 })
44558 .style({
44559 position: 'absolute',
44560 top: 0,
44561 left: 0,
44562 overflow: 'visible',
44563 'pointer-events': 'none'
44564 });
44565 }
44566
44567 if(fullLayout._glcanvas) {
44568 fullLayout._glcanvas
44569 .attr('width', fullLayout.width)
44570 .attr('height', fullLayout.height);
44571
44572 var regl = fullLayout._glcanvas.data()[0].regl;
44573 if(regl) {
44574 // Unfortunately, this can happen when relayouting to large
44575 // width/height on some browsers.
44576 if(Math.floor(fullLayout.width) !== regl._gl.drawingBufferWidth ||
44577 Math.floor(fullLayout.height) !== regl._gl.drawingBufferHeight
44578 ) {
44579 var msg = 'WebGL context buffer and canvas dimensions do not match due to browser/WebGL bug.';
44580 if(drawFrameworkCalls) {
44581 Lib.error(msg);
44582 } else {
44583 Lib.log(msg + ' Clearing graph and plotting again.');
44584 Plots.cleanPlot([], {}, gd._fullData, fullLayout);
44585 Plots.supplyDefaults(gd);
44586 fullLayout = gd._fullLayout;
44587 Plots.doCalcdata(gd);
44588 drawFrameworkCalls++;
44589 return drawFramework();
44590 }
44591 }
44592 }
44593 }
44594
44595 if(fullLayout.modebar.orientation === 'h') {
44596 fullLayout._modebardiv
44597 .style('height', null)
44598 .style('width', '100%');
44599 } else {
44600 fullLayout._modebardiv
44601 .style('width', null)
44602 .style('height', fullLayout.height + 'px');
44603 }
44604
44605 return Plots.previousPromises(gd);
44606 }
44607
44608 // draw anything that can affect margins.
44609 function marginPushers() {
44610 // First reset the list of things that are allowed to change the margins
44611 // So any deleted traces or components will be wiped out of the
44612 // automargin calculation.
44613 // This means *every* margin pusher must be listed here, even if it
44614 // doesn't actually try to push the margins until later.
44615 Plots.clearAutoMarginIds(gd);
44616
44617 subroutines.drawMarginPushers(gd);
44618 Axes.allowAutoMargin(gd);
44619
44620 // TODO can this be moved elsewhere?
44621 if(fullLayout._has('pie')) {
44622 var fullData = gd._fullData;
44623 for(var i = 0; i < fullData.length; i++) {
44624 var trace = fullData[i];
44625 if(trace.type === 'pie' && trace.automargin) {
44626 Plots.allowAutoMargin(gd, 'pie.' + trace.uid + '.automargin');
44627 }
44628 }
44629 }
44630
44631 Plots.doAutoMargin(gd);
44632 return Plots.previousPromises(gd);
44633 }
44634
44635 // in case the margins changed, draw margin pushers again
44636 function marginPushersAgain() {
44637 if(!Plots.didMarginChange(oldMargins, fullLayout._size)) return;
44638
44639 return Lib.syncOrAsync([
44640 marginPushers,
44641 subroutines.layoutStyles
44642 ], gd);
44643 }
44644
44645 function positionAndAutorange() {
44646 if(!recalc) {
44647 doAutoRangeAndConstraints();
44648 return;
44649 }
44650
44651 // TODO: autosize extra for text markers and images
44652 // see https://github.com/plotly/plotly.js/issues/1111
44653 return Lib.syncOrAsync([
44654 Registry.getComponentMethod('shapes', 'calcAutorange'),
44655 Registry.getComponentMethod('annotations', 'calcAutorange'),
44656 doAutoRangeAndConstraints
44657 ], gd);
44658 }
44659
44660 function doAutoRangeAndConstraints() {
44661 if(gd._transitioning) return;
44662
44663 subroutines.doAutoRangeAndConstraints(gd);
44664
44665 // store initial ranges *after* enforcing constraints, otherwise
44666 // we will never look like we're at the initial ranges
44667 if(graphWasEmpty) Axes.saveRangeInitial(gd);
44668
44669 // this one is different from shapes/annotations calcAutorange
44670 // the others incorporate those components into ax._extremes,
44671 // this one actually sets the ranges in rangesliders.
44672 Registry.getComponentMethod('rangeslider', 'calcAutorange')(gd);
44673 }
44674
44675 // draw ticks, titles, and calculate axis scaling (._b, ._m)
44676 function drawAxes() {
44677 return Axes.draw(gd, graphWasEmpty ? '' : 'redraw');
44678 }
44679
44680 var seq = [
44681 Plots.previousPromises,
44682 addFrames,
44683 drawFramework,
44684 marginPushers,
44685 marginPushersAgain
44686 ];
44687
44688 if(hasCartesian) seq.push(positionAndAutorange);
44689
44690 seq.push(subroutines.layoutStyles);
44691 if(hasCartesian) seq.push(drawAxes);
44692
44693 seq.push(
44694 subroutines.drawData,
44695 subroutines.finalDraw,
44696 initInteractions,
44697 Plots.addLinks,
44698 Plots.rehover,
44699 Plots.redrag,
44700 // TODO: doAutoMargin is only needed here for axis automargin, which
44701 // happens outside of marginPushers where all the other automargins are
44702 // calculated. Would be much better to separate margin calculations from
44703 // component drawing - see https://github.com/plotly/plotly.js/issues/2704
44704 Plots.doAutoMargin,
44705 Plots.previousPromises
44706 );
44707
44708 // even if everything we did was synchronous, return a promise
44709 // so that the caller doesn't care which route we took
44710 var plotDone = Lib.syncOrAsync(seq, gd);
44711 if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
44712
44713 return plotDone.then(function() {
44714 emitAfterPlot(gd);
44715 return gd;
44716 });
44717}
44718
44719function emitAfterPlot(gd) {
44720 var fullLayout = gd._fullLayout;
44721
44722 if(fullLayout._redrawFromAutoMarginCount) {
44723 fullLayout._redrawFromAutoMarginCount--;
44724 } else {
44725 gd.emit('plotly_afterplot');
44726 }
44727}
44728
44729function setPlotConfig(obj) {
44730 return Lib.extendFlat(dfltConfig, obj);
44731}
44732
44733function setBackground(gd, bgColor) {
44734 try {
44735 gd._fullLayout._paper.style('background', bgColor);
44736 } catch(e) {
44737 Lib.error(e);
44738 }
44739}
44740
44741function opaqueSetBackground(gd, bgColor) {
44742 var blend = Color.combine(bgColor, 'white');
44743 setBackground(gd, blend);
44744}
44745
44746function setPlotContext(gd, config) {
44747 if(!gd._context) {
44748 gd._context = Lib.extendDeep({}, dfltConfig);
44749
44750 // stash <base> href, used to make robust clipPath URLs
44751 var base = d3.select('base');
44752 gd._context._baseUrl = base.size() && base.attr('href') ?
44753 window.location.href.split('#')[0] :
44754 '';
44755 }
44756
44757 var context = gd._context;
44758
44759 var i, keys, key;
44760
44761 if(config) {
44762 keys = Object.keys(config);
44763 for(i = 0; i < keys.length; i++) {
44764 key = keys[i];
44765 if(key === 'editable' || key === 'edits') continue;
44766 if(key in context) {
44767 if(key === 'setBackground' && config[key] === 'opaque') {
44768 context[key] = opaqueSetBackground;
44769 } else {
44770 context[key] = config[key];
44771 }
44772 }
44773 }
44774
44775 // map plot3dPixelRatio to plotGlPixelRatio for backward compatibility
44776 if(config.plot3dPixelRatio && !context.plotGlPixelRatio) {
44777 context.plotGlPixelRatio = context.plot3dPixelRatio;
44778 }
44779
44780 // now deal with editable and edits - first editable overrides
44781 // everything, then edits refines
44782 var editable = config.editable;
44783 if(editable !== undefined) {
44784 // we're not going to *use* context.editable, we're only going to
44785 // use context.edits... but keep it for the record
44786 context.editable = editable;
44787
44788 keys = Object.keys(context.edits);
44789 for(i = 0; i < keys.length; i++) {
44790 context.edits[keys[i]] = editable;
44791 }
44792 }
44793 if(config.edits) {
44794 keys = Object.keys(config.edits);
44795 for(i = 0; i < keys.length; i++) {
44796 key = keys[i];
44797 if(key in context.edits) {
44798 context.edits[key] = config.edits[key];
44799 }
44800 }
44801 }
44802
44803 // not part of the user-facing config options
44804 context._exportedPlot = config._exportedPlot;
44805 }
44806
44807 // staticPlot forces a bunch of others:
44808 if(context.staticPlot) {
44809 context.editable = false;
44810 context.edits = {};
44811 context.autosizable = false;
44812 context.scrollZoom = false;
44813 context.doubleClick = false;
44814 context.showTips = false;
44815 context.showLink = false;
44816 context.displayModeBar = false;
44817 }
44818
44819 // make sure hover-only devices have mode bar visible
44820 if(context.displayModeBar === 'hover' && !hasHover) {
44821 context.displayModeBar = true;
44822 }
44823
44824 // default and fallback for setBackground
44825 if(context.setBackground === 'transparent' || typeof context.setBackground !== 'function') {
44826 context.setBackground = setBackground;
44827 }
44828
44829 // Check if gd has a specified widht/height to begin with
44830 context._hasZeroHeight = context._hasZeroHeight || gd.clientHeight === 0;
44831 context._hasZeroWidth = context._hasZeroWidth || gd.clientWidth === 0;
44832
44833 // fill context._scrollZoom helper to help manage scrollZoom flaglist
44834 var szIn = context.scrollZoom;
44835 var szOut = context._scrollZoom = {};
44836 if(szIn === true) {
44837 szOut.cartesian = 1;
44838 szOut.gl3d = 1;
44839 szOut.geo = 1;
44840 szOut.mapbox = 1;
44841 } else if(typeof szIn === 'string') {
44842 var parts = szIn.split('+');
44843 for(i = 0; i < parts.length; i++) {
44844 szOut[parts[i]] = 1;
44845 }
44846 } else if(szIn !== false) {
44847 szOut.gl3d = 1;
44848 szOut.geo = 1;
44849 szOut.mapbox = 1;
44850 }
44851}
44852
44853function plotLegacyPolar(gd, data, layout) {
44854 // build or reuse the container skeleton
44855 var plotContainer = d3.select(gd).selectAll('.plot-container')
44856 .data([0]);
44857 plotContainer.enter()
44858 .insert('div', ':first-child')
44859 .classed('plot-container plotly', true);
44860 var paperDiv = plotContainer.selectAll('.svg-container')
44861 .data([0]);
44862 paperDiv.enter().append('div')
44863 .classed('svg-container', true)
44864 .style('position', 'relative');
44865
44866 // empty it everytime for now
44867 paperDiv.html('');
44868
44869 // fulfill gd requirements
44870 if(data) gd.data = data;
44871 if(layout) gd.layout = layout;
44872 Polar.manager.fillLayout(gd);
44873
44874 // resize canvas
44875 paperDiv.style({
44876 width: gd._fullLayout.width + 'px',
44877 height: gd._fullLayout.height + 'px'
44878 });
44879
44880 // instantiate framework
44881 gd.framework = Polar.manager.framework(gd);
44882
44883 // plot
44884 gd.framework({data: gd.data, layout: gd.layout}, paperDiv.node());
44885
44886 // set undo point
44887 gd.framework.setUndoPoint();
44888
44889 // get the resulting svg for extending it
44890 var polarPlotSVG = gd.framework.svg();
44891
44892 // editable title
44893 var opacity = 1;
44894 var txt = gd._fullLayout.title ? gd._fullLayout.title.text : '';
44895 if(txt === '' || !txt) opacity = 0;
44896
44897 var titleLayout = function() {
44898 this.call(svgTextUtils.convertToTspans, gd);
44899 // TODO: html/mathjax
44900 // TODO: center title
44901 };
44902
44903 var title = polarPlotSVG.select('.title-group text')
44904 .call(titleLayout);
44905
44906 if(gd._context.edits.titleText) {
44907 var placeholderText = Lib._(gd, 'Click to enter Plot title');
44908 if(!txt || txt === placeholderText) {
44909 opacity = 0.2;
44910 // placeholder is not going through convertToTspans
44911 // so needs explicit data-unformatted
44912 title.attr({'data-unformatted': placeholderText})
44913 .text(placeholderText)
44914 .style({opacity: opacity})
44915 .on('mouseover.opacity', function() {
44916 d3.select(this).transition().duration(100)
44917 .style('opacity', 1);
44918 })
44919 .on('mouseout.opacity', function() {
44920 d3.select(this).transition().duration(1000)
44921 .style('opacity', 0);
44922 });
44923 }
44924
44925 var setContenteditable = function() {
44926 this.call(svgTextUtils.makeEditable, {gd: gd})
44927 .on('edit', function(text) {
44928 gd.framework({layout: {title: {text: text}}});
44929 this.text(text)
44930 .call(titleLayout);
44931 this.call(setContenteditable);
44932 })
44933 .on('cancel', function() {
44934 var txt = this.attr('data-unformatted');
44935 this.text(txt).call(titleLayout);
44936 });
44937 };
44938 title.call(setContenteditable);
44939 }
44940
44941 gd._context.setBackground(gd, gd._fullLayout.paper_bgcolor);
44942 Plots.addLinks(gd);
44943
44944 return Promise.resolve();
44945}
44946
44947// convenience function to force a full redraw, mostly for use by plotly.js
44948function redraw(gd) {
44949 gd = Lib.getGraphDiv(gd);
44950
44951 if(!Lib.isPlotDiv(gd)) {
44952 throw new Error('This element is not a Plotly plot: ' + gd);
44953 }
44954
44955 helpers.cleanData(gd.data);
44956 helpers.cleanLayout(gd.layout);
44957
44958 gd.calcdata = undefined;
44959 return exports.plot(gd).then(function() {
44960 gd.emit('plotly_redraw');
44961 return gd;
44962 });
44963}
44964
44965/**
44966 * Convenience function to make idempotent plot option obvious to users.
44967 *
44968 * @param gd
44969 * @param {Object[]} data
44970 * @param {Object} layout
44971 * @param {Object} config
44972 */
44973function newPlot(gd, data, layout, config) {
44974 gd = Lib.getGraphDiv(gd);
44975
44976 // remove gl contexts
44977 Plots.cleanPlot([], {}, gd._fullData || [], gd._fullLayout || {});
44978
44979 Plots.purge(gd);
44980 return exports.plot(gd, data, layout, config);
44981}
44982
44983/**
44984 * Wrap negative indicies to their positive counterparts.
44985 *
44986 * @param {Number[]} indices An array of indices
44987 * @param {Number} maxIndex The maximum index allowable (arr.length - 1)
44988 */
44989function positivifyIndices(indices, maxIndex) {
44990 var parentLength = maxIndex + 1;
44991 var positiveIndices = [];
44992 var i;
44993 var index;
44994
44995 for(i = 0; i < indices.length; i++) {
44996 index = indices[i];
44997 if(index < 0) {
44998 positiveIndices.push(parentLength + index);
44999 } else {
45000 positiveIndices.push(index);
45001 }
45002 }
45003 return positiveIndices;
45004}
45005
45006/**
45007 * Ensures that an index array for manipulating gd.data is valid.
45008 *
45009 * Intended for use with addTraces, deleteTraces, and moveTraces.
45010 *
45011 * @param gd
45012 * @param indices
45013 * @param arrayName
45014 */
45015function assertIndexArray(gd, indices, arrayName) {
45016 var i,
45017 index;
45018
45019 for(i = 0; i < indices.length; i++) {
45020 index = indices[i];
45021
45022 // validate that indices are indeed integers
45023 if(index !== parseInt(index, 10)) {
45024 throw new Error('all values in ' + arrayName + ' must be integers');
45025 }
45026
45027 // check that all indices are in bounds for given gd.data array length
45028 if(index >= gd.data.length || index < -gd.data.length) {
45029 throw new Error(arrayName + ' must be valid indices for gd.data.');
45030 }
45031
45032 // check that indices aren't repeated
45033 if(indices.indexOf(index, i + 1) > -1 ||
45034 index >= 0 && indices.indexOf(-gd.data.length + index) > -1 ||
45035 index < 0 && indices.indexOf(gd.data.length + index) > -1) {
45036 throw new Error('each index in ' + arrayName + ' must be unique.');
45037 }
45038 }
45039}
45040
45041/**
45042 * Private function used by Plotly.moveTraces to check input args
45043 *
45044 * @param gd
45045 * @param currentIndices
45046 * @param newIndices
45047 */
45048function checkMoveTracesArgs(gd, currentIndices, newIndices) {
45049 // check that gd has attribute 'data' and 'data' is array
45050 if(!Array.isArray(gd.data)) {
45051 throw new Error('gd.data must be an array.');
45052 }
45053
45054 // validate currentIndices array
45055 if(typeof currentIndices === 'undefined') {
45056 throw new Error('currentIndices is a required argument.');
45057 } else if(!Array.isArray(currentIndices)) {
45058 currentIndices = [currentIndices];
45059 }
45060 assertIndexArray(gd, currentIndices, 'currentIndices');
45061
45062 // validate newIndices array if it exists
45063 if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
45064 newIndices = [newIndices];
45065 }
45066 if(typeof newIndices !== 'undefined') {
45067 assertIndexArray(gd, newIndices, 'newIndices');
45068 }
45069
45070 // check currentIndices and newIndices are the same length if newIdices exists
45071 if(typeof newIndices !== 'undefined' && currentIndices.length !== newIndices.length) {
45072 throw new Error('current and new indices must be of equal length.');
45073 }
45074}
45075/**
45076 * A private function to reduce the type checking clutter in addTraces.
45077 *
45078 * @param gd
45079 * @param traces
45080 * @param newIndices
45081 */
45082function checkAddTracesArgs(gd, traces, newIndices) {
45083 var i, value;
45084
45085 // check that gd has attribute 'data' and 'data' is array
45086 if(!Array.isArray(gd.data)) {
45087 throw new Error('gd.data must be an array.');
45088 }
45089
45090 // make sure traces exists
45091 if(typeof traces === 'undefined') {
45092 throw new Error('traces must be defined.');
45093 }
45094
45095 // make sure traces is an array
45096 if(!Array.isArray(traces)) {
45097 traces = [traces];
45098 }
45099
45100 // make sure each value in traces is an object
45101 for(i = 0; i < traces.length; i++) {
45102 value = traces[i];
45103 if(typeof value !== 'object' || (Array.isArray(value) || value === null)) {
45104 throw new Error('all values in traces array must be non-array objects');
45105 }
45106 }
45107
45108 // make sure we have an index for each trace
45109 if(typeof newIndices !== 'undefined' && !Array.isArray(newIndices)) {
45110 newIndices = [newIndices];
45111 }
45112 if(typeof newIndices !== 'undefined' && newIndices.length !== traces.length) {
45113 throw new Error(
45114 'if indices is specified, traces.length must equal indices.length'
45115 );
45116 }
45117}
45118
45119/**
45120 * A private function to reduce the type checking clutter in spliceTraces.
45121 * Get all update Properties from gd.data. Validate inputs and outputs.
45122 * Used by prependTrace and extendTraces
45123 *
45124 * @param gd
45125 * @param update
45126 * @param indices
45127 * @param maxPoints
45128 */
45129function assertExtendTracesArgs(gd, update, indices, maxPoints) {
45130 var maxPointsIsObject = Lib.isPlainObject(maxPoints);
45131
45132 if(!Array.isArray(gd.data)) {
45133 throw new Error('gd.data must be an array');
45134 }
45135 if(!Lib.isPlainObject(update)) {
45136 throw new Error('update must be a key:value object');
45137 }
45138
45139 if(typeof indices === 'undefined') {
45140 throw new Error('indices must be an integer or array of integers');
45141 }
45142
45143 assertIndexArray(gd, indices, 'indices');
45144
45145 for(var key in update) {
45146 /*
45147 * Verify that the attribute to be updated contains as many trace updates
45148 * as indices. Failure must result in throw and no-op
45149 */
45150 if(!Array.isArray(update[key]) || update[key].length !== indices.length) {
45151 throw new Error('attribute ' + key + ' must be an array of length equal to indices array length');
45152 }
45153
45154 /*
45155 * if maxPoints is an object it must match keys and array lengths of 'update' 1:1
45156 */
45157 if(maxPointsIsObject &&
45158 (!(key in maxPoints) || !Array.isArray(maxPoints[key]) ||
45159 maxPoints[key].length !== update[key].length)) {
45160 throw new Error('when maxPoints is set as a key:value object it must contain a 1:1 ' +
45161 'corrispondence with the keys and number of traces in the update object');
45162 }
45163 }
45164}
45165
45166/**
45167 * A private function to reduce the type checking clutter in spliceTraces.
45168 *
45169 * @param {Object|HTMLDivElement} gd
45170 * @param {Object} update
45171 * @param {Number[]} indices
45172 * @param {Number||Object} maxPoints
45173 * @return {Object[]}
45174 */
45175function getExtendProperties(gd, update, indices, maxPoints) {
45176 var maxPointsIsObject = Lib.isPlainObject(maxPoints);
45177 var updateProps = [];
45178 var trace, target, prop, insert, maxp;
45179
45180 // allow scalar index to represent a single trace position
45181 if(!Array.isArray(indices)) indices = [indices];
45182
45183 // negative indices are wrapped around to their positive value. Equivalent to python indexing.
45184 indices = positivifyIndices(indices, gd.data.length - 1);
45185
45186 // loop through all update keys and traces and harvest validated data.
45187 for(var key in update) {
45188 for(var j = 0; j < indices.length; j++) {
45189 /*
45190 * Choose the trace indexed by the indices map argument and get the prop setter-getter
45191 * instance that references the key and value for this particular trace.
45192 */
45193 trace = gd.data[indices[j]];
45194 prop = nestedProperty(trace, key);
45195
45196 /*
45197 * Target is the existing gd.data.trace.dataArray value like "x" or "marker.size"
45198 * Target must exist as an Array to allow the extend operation to be performed.
45199 */
45200 target = prop.get();
45201 insert = update[key][j];
45202
45203 if(!Lib.isArrayOrTypedArray(insert)) {
45204 throw new Error('attribute: ' + key + ' index: ' + j + ' must be an array');
45205 }
45206 if(!Lib.isArrayOrTypedArray(target)) {
45207 throw new Error('cannot extend missing or non-array attribute: ' + key);
45208 }
45209 if(target.constructor !== insert.constructor) {
45210 throw new Error('cannot extend array with an array of a different type: ' + key);
45211 }
45212
45213 /*
45214 * maxPoints may be an object map or a scalar. If object select the key:value, else
45215 * Use the scalar maxPoints for all key and trace combinations.
45216 */
45217 maxp = maxPointsIsObject ? maxPoints[key][j] : maxPoints;
45218
45219 // could have chosen null here, -1 just tells us to not take a window
45220 if(!isNumeric(maxp)) maxp = -1;
45221
45222 /*
45223 * Wrap the nestedProperty in an object containing required data
45224 * for lengthening and windowing this particular trace - key combination.
45225 * Flooring maxp mirrors the behaviour of floats in the Array.slice JSnative function.
45226 */
45227 updateProps.push({
45228 prop: prop,
45229 target: target,
45230 insert: insert,
45231 maxp: Math.floor(maxp)
45232 });
45233 }
45234 }
45235
45236 // all target and insertion data now validated
45237 return updateProps;
45238}
45239
45240/**
45241 * A private function to key Extend and Prepend traces DRY
45242 *
45243 * @param {Object|HTMLDivElement} gd
45244 * @param {Object} update
45245 * @param {Number[]} indices
45246 * @param {Number||Object} maxPoints
45247 * @param {Function} updateArray
45248 * @return {Object}
45249 */
45250function spliceTraces(gd, update, indices, maxPoints, updateArray) {
45251 assertExtendTracesArgs(gd, update, indices, maxPoints);
45252
45253 var updateProps = getExtendProperties(gd, update, indices, maxPoints);
45254 var undoUpdate = {};
45255 var undoPoints = {};
45256
45257 for(var i = 0; i < updateProps.length; i++) {
45258 var prop = updateProps[i].prop;
45259 var maxp = updateProps[i].maxp;
45260
45261 // return new array and remainder
45262 var out = updateArray(updateProps[i].target, updateProps[i].insert, maxp);
45263 prop.set(out[0]);
45264
45265 // build the inverse update object for the undo operation
45266 if(!Array.isArray(undoUpdate[prop.astr])) undoUpdate[prop.astr] = [];
45267 undoUpdate[prop.astr].push(out[1]);
45268
45269 // build the matching maxPoints undo object containing original trace lengths
45270 if(!Array.isArray(undoPoints[prop.astr])) undoPoints[prop.astr] = [];
45271 undoPoints[prop.astr].push(updateProps[i].target.length);
45272 }
45273
45274 return {update: undoUpdate, maxPoints: undoPoints};
45275}
45276
45277function concatTypedArray(arr0, arr1) {
45278 var arr2 = new arr0.constructor(arr0.length + arr1.length);
45279 arr2.set(arr0);
45280 arr2.set(arr1, arr0.length);
45281 return arr2;
45282}
45283
45284/**
45285 * extend && prepend traces at indices with update arrays, window trace lengths to maxPoints
45286 *
45287 * Extend and Prepend have identical APIs. Prepend inserts an array at the head while Extend
45288 * inserts an array off the tail. Prepend truncates the tail of the array - counting maxPoints
45289 * from the head, whereas Extend truncates the head of the array, counting backward maxPoints
45290 * from the tail.
45291 *
45292 * If maxPoints is undefined, nonNumeric, negative or greater than extended trace length no
45293 * truncation / windowing will be performed. If its zero, well the whole trace is truncated.
45294 *
45295 * @param {Object|HTMLDivElement} gd The graph div
45296 * @param {Object} update The key:array map of target attributes to extend
45297 * @param {Number|Number[]} indices The locations of traces to be extended
45298 * @param {Number|Object} [maxPoints] Number of points for trace window after lengthening.
45299 *
45300 */
45301function extendTraces(gd, update, indices, maxPoints) {
45302 gd = Lib.getGraphDiv(gd);
45303
45304 function updateArray(target, insert, maxp) {
45305 var newArray, remainder;
45306
45307 if(Lib.isTypedArray(target)) {
45308 if(maxp < 0) {
45309 var none = new target.constructor(0);
45310 var both = concatTypedArray(target, insert);
45311
45312 if(maxp < 0) {
45313 newArray = both;
45314 remainder = none;
45315 } else {
45316 newArray = none;
45317 remainder = both;
45318 }
45319 } else {
45320 newArray = new target.constructor(maxp);
45321 remainder = new target.constructor(target.length + insert.length - maxp);
45322
45323 if(maxp === insert.length) {
45324 newArray.set(insert);
45325 remainder.set(target);
45326 } else if(maxp < insert.length) {
45327 var numberOfItemsFromInsert = insert.length - maxp;
45328
45329 newArray.set(insert.subarray(numberOfItemsFromInsert));
45330 remainder.set(target);
45331 remainder.set(insert.subarray(0, numberOfItemsFromInsert), target.length);
45332 } else {
45333 var numberOfItemsFromTarget = maxp - insert.length;
45334 var targetBegin = target.length - numberOfItemsFromTarget;
45335
45336 newArray.set(target.subarray(targetBegin));
45337 newArray.set(insert, numberOfItemsFromTarget);
45338 remainder.set(target.subarray(0, targetBegin));
45339 }
45340 }
45341 } else {
45342 newArray = target.concat(insert);
45343 remainder = (maxp >= 0 && maxp < newArray.length) ?
45344 newArray.splice(0, newArray.length - maxp) :
45345 [];
45346 }
45347
45348 return [newArray, remainder];
45349 }
45350
45351 var undo = spliceTraces(gd, update, indices, maxPoints, updateArray);
45352 var promise = exports.redraw(gd);
45353 var undoArgs = [gd, undo.update, indices, undo.maxPoints];
45354 Queue.add(gd, exports.prependTraces, undoArgs, extendTraces, arguments);
45355
45356 return promise;
45357}
45358
45359function prependTraces(gd, update, indices, maxPoints) {
45360 gd = Lib.getGraphDiv(gd);
45361
45362 function updateArray(target, insert, maxp) {
45363 var newArray, remainder;
45364
45365 if(Lib.isTypedArray(target)) {
45366 if(maxp <= 0) {
45367 var none = new target.constructor(0);
45368 var both = concatTypedArray(insert, target);
45369
45370 if(maxp < 0) {
45371 newArray = both;
45372 remainder = none;
45373 } else {
45374 newArray = none;
45375 remainder = both;
45376 }
45377 } else {
45378 newArray = new target.constructor(maxp);
45379 remainder = new target.constructor(target.length + insert.length - maxp);
45380
45381 if(maxp === insert.length) {
45382 newArray.set(insert);
45383 remainder.set(target);
45384 } else if(maxp < insert.length) {
45385 var numberOfItemsFromInsert = insert.length - maxp;
45386
45387 newArray.set(insert.subarray(0, numberOfItemsFromInsert));
45388 remainder.set(insert.subarray(numberOfItemsFromInsert));
45389 remainder.set(target, numberOfItemsFromInsert);
45390 } else {
45391 var numberOfItemsFromTarget = maxp - insert.length;
45392
45393 newArray.set(insert);
45394 newArray.set(target.subarray(0, numberOfItemsFromTarget), insert.length);
45395 remainder.set(target.subarray(numberOfItemsFromTarget));
45396 }
45397 }
45398 } else {
45399 newArray = insert.concat(target);
45400 remainder = (maxp >= 0 && maxp < newArray.length) ?
45401 newArray.splice(maxp, newArray.length) :
45402 [];
45403 }
45404
45405 return [newArray, remainder];
45406 }
45407
45408 var undo = spliceTraces(gd, update, indices, maxPoints, updateArray);
45409 var promise = exports.redraw(gd);
45410 var undoArgs = [gd, undo.update, indices, undo.maxPoints];
45411 Queue.add(gd, exports.extendTraces, undoArgs, prependTraces, arguments);
45412
45413 return promise;
45414}
45415
45416/**
45417 * Add data traces to an existing graph div.
45418 *
45419 * @param {Object|HTMLDivElement} gd The graph div
45420 * @param {Object[]} gd.data The array of traces we're adding to
45421 * @param {Object[]|Object} traces The object or array of objects to add
45422 * @param {Number[]|Number} [newIndices=[gd.data.length]] Locations to add traces
45423 *
45424 */
45425function addTraces(gd, traces, newIndices) {
45426 gd = Lib.getGraphDiv(gd);
45427
45428 var currentIndices = [];
45429 var undoFunc = exports.deleteTraces;
45430 var redoFunc = addTraces;
45431 var undoArgs = [gd, currentIndices];
45432 var redoArgs = [gd, traces]; // no newIndices here
45433 var i;
45434 var promise;
45435
45436 // all validation is done elsewhere to remove clutter here
45437 checkAddTracesArgs(gd, traces, newIndices);
45438
45439 // make sure traces is an array
45440 if(!Array.isArray(traces)) {
45441 traces = [traces];
45442 }
45443
45444 // make sure traces do not repeat existing ones
45445 traces = traces.map(function(trace) {
45446 return Lib.extendFlat({}, trace);
45447 });
45448
45449 helpers.cleanData(traces);
45450
45451 // add the traces to gd.data (no redrawing yet!)
45452 for(i = 0; i < traces.length; i++) {
45453 gd.data.push(traces[i]);
45454 }
45455
45456 // to continue, we need to call moveTraces which requires currentIndices
45457 for(i = 0; i < traces.length; i++) {
45458 currentIndices.push(-traces.length + i);
45459 }
45460
45461 // if the user didn't define newIndices, they just want the traces appended
45462 // i.e., we can simply redraw and be done
45463 if(typeof newIndices === 'undefined') {
45464 promise = exports.redraw(gd);
45465 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45466 return promise;
45467 }
45468
45469 // make sure indices is property defined
45470 if(!Array.isArray(newIndices)) {
45471 newIndices = [newIndices];
45472 }
45473
45474 try {
45475 // this is redundant, but necessary to not catch later possible errors!
45476 checkMoveTracesArgs(gd, currentIndices, newIndices);
45477 } catch(error) {
45478 // something went wrong, reset gd to be safe and rethrow error
45479 gd.data.splice(gd.data.length - traces.length, traces.length);
45480 throw error;
45481 }
45482
45483 // if we're here, the user has defined specific places to place the new traces
45484 // this requires some extra work that moveTraces will do
45485 Queue.startSequence(gd);
45486 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45487 promise = exports.moveTraces(gd, currentIndices, newIndices);
45488 Queue.stopSequence(gd);
45489 return promise;
45490}
45491
45492/**
45493 * Delete traces at `indices` from gd.data array.
45494 *
45495 * @param {Object|HTMLDivElement} gd The graph div
45496 * @param {Object[]} gd.data The array of traces we're removing from
45497 * @param {Number|Number[]} indices The indices
45498 */
45499function deleteTraces(gd, indices) {
45500 gd = Lib.getGraphDiv(gd);
45501
45502 var traces = [];
45503 var undoFunc = exports.addTraces;
45504 var redoFunc = deleteTraces;
45505 var undoArgs = [gd, traces, indices];
45506 var redoArgs = [gd, indices];
45507 var i;
45508 var deletedTrace;
45509
45510 // make sure indices are defined
45511 if(typeof indices === 'undefined') {
45512 throw new Error('indices must be an integer or array of integers.');
45513 } else if(!Array.isArray(indices)) {
45514 indices = [indices];
45515 }
45516 assertIndexArray(gd, indices, 'indices');
45517
45518 // convert negative indices to positive indices
45519 indices = positivifyIndices(indices, gd.data.length - 1);
45520
45521 // we want descending here so that splicing later doesn't affect indexing
45522 indices.sort(Lib.sorterDes);
45523 for(i = 0; i < indices.length; i += 1) {
45524 deletedTrace = gd.data.splice(indices[i], 1)[0];
45525 traces.push(deletedTrace);
45526 }
45527
45528 var promise = exports.redraw(gd);
45529 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45530
45531 return promise;
45532}
45533
45534/**
45535 * Move traces at currentIndices array to locations in newIndices array.
45536 *
45537 * If newIndices is omitted, currentIndices will be moved to the end. E.g.,
45538 * these are equivalent:
45539 *
45540 * Plotly.moveTraces(gd, [1, 2, 3], [-3, -2, -1])
45541 * Plotly.moveTraces(gd, [1, 2, 3])
45542 *
45543 * @param {Object|HTMLDivElement} gd The graph div
45544 * @param {Object[]} gd.data The array of traces we're removing from
45545 * @param {Number|Number[]} currentIndices The locations of traces to be moved
45546 * @param {Number|Number[]} [newIndices] The locations to move traces to
45547 *
45548 * Example calls:
45549 *
45550 * // move trace i to location x
45551 * Plotly.moveTraces(gd, i, x)
45552 *
45553 * // move trace i to end of array
45554 * Plotly.moveTraces(gd, i)
45555 *
45556 * // move traces i, j, k to end of array (i != j != k)
45557 * Plotly.moveTraces(gd, [i, j, k])
45558 *
45559 * // move traces [i, j, k] to [x, y, z] (i != j != k) (x != y != z)
45560 * Plotly.moveTraces(gd, [i, j, k], [x, y, z])
45561 *
45562 * // reorder all traces (assume there are 5--a, b, c, d, e)
45563 * Plotly.moveTraces(gd, [b, d, e, a, c]) // same as 'move to end'
45564 */
45565function moveTraces(gd, currentIndices, newIndices) {
45566 gd = Lib.getGraphDiv(gd);
45567
45568 var newData = [];
45569 var movingTraceMap = [];
45570 var undoFunc = moveTraces;
45571 var redoFunc = moveTraces;
45572 var undoArgs = [gd, newIndices, currentIndices];
45573 var redoArgs = [gd, currentIndices, newIndices];
45574 var i;
45575
45576 // to reduce complexity here, check args elsewhere
45577 // this throws errors where appropriate
45578 checkMoveTracesArgs(gd, currentIndices, newIndices);
45579
45580 // make sure currentIndices is an array
45581 currentIndices = Array.isArray(currentIndices) ? currentIndices : [currentIndices];
45582
45583 // if undefined, define newIndices to point to the end of gd.data array
45584 if(typeof newIndices === 'undefined') {
45585 newIndices = [];
45586 for(i = 0; i < currentIndices.length; i++) {
45587 newIndices.push(-currentIndices.length + i);
45588 }
45589 }
45590
45591 // make sure newIndices is an array if it's user-defined
45592 newIndices = Array.isArray(newIndices) ? newIndices : [newIndices];
45593
45594 // convert negative indices to positive indices (they're the same length)
45595 currentIndices = positivifyIndices(currentIndices, gd.data.length - 1);
45596 newIndices = positivifyIndices(newIndices, gd.data.length - 1);
45597
45598 // at this point, we've coerced the index arrays into predictable forms
45599
45600 // get the traces that aren't being moved around
45601 for(i = 0; i < gd.data.length; i++) {
45602 // if index isn't in currentIndices, include it in ignored!
45603 if(currentIndices.indexOf(i) === -1) {
45604 newData.push(gd.data[i]);
45605 }
45606 }
45607
45608 // get a mapping of indices to moving traces
45609 for(i = 0; i < currentIndices.length; i++) {
45610 movingTraceMap.push({newIndex: newIndices[i], trace: gd.data[currentIndices[i]]});
45611 }
45612
45613 // reorder this mapping by newIndex, ascending
45614 movingTraceMap.sort(function(a, b) {
45615 return a.newIndex - b.newIndex;
45616 });
45617
45618 // now, add the moving traces back in, in order!
45619 for(i = 0; i < movingTraceMap.length; i += 1) {
45620 newData.splice(movingTraceMap[i].newIndex, 0, movingTraceMap[i].trace);
45621 }
45622
45623 gd.data = newData;
45624
45625 var promise = exports.redraw(gd);
45626 Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
45627
45628 return promise;
45629}
45630
45631/**
45632 * restyle: update trace attributes of an existing plot
45633 *
45634 * Can be called two ways.
45635 *
45636 * Signature 1:
45637 * @param {String | HTMLDivElement} gd
45638 * the id or DOM element of the graph container div
45639 * @param {String} astr
45640 * attribute string (like `'marker.symbol'`) to update
45641 * @param {*} val
45642 * value to give this attribute
45643 * @param {Number[] | Number} [traces]
45644 * integer or array of integers for the traces to alter (all if omitted)
45645 *
45646 * Signature 2:
45647 * @param {String | HTMLDivElement} gd
45648 * (as in signature 1)
45649 * @param {Object} aobj
45650 * attribute object `{astr1: val1, astr2: val2 ...}`
45651 * allows setting multiple attributes simultaneously
45652 * @param {Number[] | Number} [traces]
45653 * (as in signature 1)
45654 *
45655 * `val` (or `val1`, `val2` ... in the object form) can be an array,
45656 * to apply different values to each trace.
45657 *
45658 * If the array is too short, it will wrap around (useful for
45659 * style files that want to specify cyclical default values).
45660 */
45661function restyle(gd, astr, val, _traces) {
45662 gd = Lib.getGraphDiv(gd);
45663 helpers.clearPromiseQueue(gd);
45664
45665 var aobj = {};
45666 if(typeof astr === 'string') aobj[astr] = val;
45667 else if(Lib.isPlainObject(astr)) {
45668 // the 3-arg form
45669 aobj = Lib.extendFlat({}, astr);
45670 if(_traces === undefined) _traces = val;
45671 } else {
45672 Lib.warn('Restyle fail.', astr, val, _traces);
45673 return Promise.reject();
45674 }
45675
45676 if(Object.keys(aobj).length) gd.changed = true;
45677
45678 var traces = helpers.coerceTraceIndices(gd, _traces);
45679
45680 var specs = _restyle(gd, aobj, traces);
45681 var flags = specs.flags;
45682
45683 // clear calcdata and/or axis types if required so they get regenerated
45684 if(flags.calc) gd.calcdata = undefined;
45685 if(flags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, {});
45686
45687 // fill in redraw sequence
45688 var seq = [];
45689
45690 if(flags.fullReplot) {
45691 seq.push(exports.plot);
45692 } else {
45693 seq.push(Plots.previousPromises);
45694
45695 // maybe only call Plots.supplyDataDefaults in the splom case,
45696 // to skip over long and slow axes defaults
45697 Plots.supplyDefaults(gd);
45698
45699 if(flags.markerSize) {
45700 Plots.doCalcdata(gd);
45701 addAxRangeSequence(seq);
45702
45703 // TODO
45704 // if all axes have autorange:false, then
45705 // proceed to subroutines.doTraceStyle(),
45706 // otherwise we must go through addAxRangeSequence,
45707 // which in general must redraws 'all' axes
45708 }
45709
45710 if(flags.style) seq.push(subroutines.doTraceStyle);
45711 if(flags.colorbars) seq.push(subroutines.doColorBars);
45712
45713 seq.push(emitAfterPlot);
45714 }
45715
45716 seq.push(Plots.rehover, Plots.redrag);
45717
45718 Queue.add(gd,
45719 restyle, [gd, specs.undoit, specs.traces],
45720 restyle, [gd, specs.redoit, specs.traces]
45721 );
45722
45723 var plotDone = Lib.syncOrAsync(seq, gd);
45724 if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
45725
45726 return plotDone.then(function() {
45727 gd.emit('plotly_restyle', specs.eventData);
45728 return gd;
45729 });
45730}
45731
45732// for undo: undefined initial vals must be turned into nulls
45733// so that we unset rather than ignore them
45734function undefinedToNull(val) {
45735 if(val === undefined) return null;
45736 return val;
45737}
45738
45739/**
45740 * Factory function to wrap nestedProperty with GUI edits if necessary
45741 * with GUI edits we add an optional prefix to the nestedProperty constructor
45742 * to prepend to the attribute string in the preGUI store.
45743 */
45744function makeNP(preGUI, guiEditFlag) {
45745 if(!guiEditFlag) return nestedProperty;
45746
45747 return function(container, attr, prefix) {
45748 var np = nestedProperty(container, attr);
45749 var npSet = np.set;
45750 np.set = function(val) {
45751 var fullAttr = (prefix || '') + attr;
45752 storeCurrent(fullAttr, np.get(), val, preGUI);
45753 npSet(val);
45754 };
45755 return np;
45756 };
45757}
45758
45759function storeCurrent(attr, val, newVal, preGUI) {
45760 if(Array.isArray(val) || Array.isArray(newVal)) {
45761 var arrayVal = Array.isArray(val) ? val : [];
45762 var arrayNew = Array.isArray(newVal) ? newVal : [];
45763 var maxLen = Math.max(arrayVal.length, arrayNew.length);
45764 for(var i = 0; i < maxLen; i++) {
45765 storeCurrent(attr + '[' + i + ']', arrayVal[i], arrayNew[i], preGUI);
45766 }
45767 } else if(Lib.isPlainObject(val) || Lib.isPlainObject(newVal)) {
45768 var objVal = Lib.isPlainObject(val) ? val : {};
45769 var objNew = Lib.isPlainObject(newVal) ? newVal : {};
45770 var objBoth = Lib.extendFlat({}, objVal, objNew);
45771 for(var key in objBoth) {
45772 storeCurrent(attr + '.' + key, objVal[key], objNew[key], preGUI);
45773 }
45774 } else if(preGUI[attr] === undefined) {
45775 preGUI[attr] = undefinedToNull(val);
45776 }
45777}
45778
45779/**
45780 * storeDirectGUIEdit: for routines that skip restyle/relayout and mock it
45781 * by emitting a plotly_restyle or plotly_relayout event, this routine
45782 * keeps track of the initial state in _preGUI for use by uirevision
45783 * Does *not* apply these changes to data/layout - that's the responsibility
45784 * of the calling routine.
45785 *
45786 * @param {object} container: the input attributes container (eg `layout` or a `trace`)
45787 * @param {object} preGUI: where original values should be stored, either
45788 * `layout._preGUI` or `layout._tracePreGUI[uid]`
45789 * @param {object} edits: the {attr: val} object as normally passed to `relayout` etc
45790 */
45791function _storeDirectGUIEdit(container, preGUI, edits) {
45792 for(var attr in edits) {
45793 var np = nestedProperty(container, attr);
45794 storeCurrent(attr, np.get(), edits[attr], preGUI);
45795 }
45796}
45797
45798function _restyle(gd, aobj, traces) {
45799 var fullLayout = gd._fullLayout;
45800 var fullData = gd._fullData;
45801 var data = gd.data;
45802 var guiEditFlag = fullLayout._guiEditing;
45803 var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag);
45804 var eventData = Lib.extendDeepAll({}, aobj);
45805 var i;
45806
45807 cleanDeprecatedAttributeKeys(aobj);
45808
45809 // initialize flags
45810 var flags = editTypes.traceFlags();
45811
45812 // copies of the change (and previous values of anything affected)
45813 // for the undo / redo queue
45814 var redoit = {};
45815 var undoit = {};
45816 var axlist;
45817
45818 // make a new empty vals array for undoit
45819 function a0() { return traces.map(function() { return undefined; }); }
45820
45821 // for autoranging multiple axes
45822 function addToAxlist(axid) {
45823 var axName = Axes.id2name(axid);
45824 if(axlist.indexOf(axName) === -1) axlist.push(axName);
45825 }
45826
45827 function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
45828
45829 function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
45830
45831 function getFullTrace(traceIndex) {
45832 // usually fullData maps 1:1 onto data, but with groupby transforms
45833 // the fullData index can be greater. Take the *first* matching trace.
45834 for(var j = traceIndex; j < fullData.length; j++) {
45835 if(fullData[j]._input === data[traceIndex]) return fullData[j];
45836 }
45837 // should never get here - and if we *do* it should cause an error
45838 // later on undefined fullTrace is passed to nestedProperty.
45839 }
45840
45841 // for attrs that interact (like scales & autoscales), save the
45842 // old vals before making the change
45843 // val=undefined will not set a value, just record what the value was.
45844 // val=null will delete the attribute
45845 // attr can be an array to set several at once (all to the same val)
45846 function doextra(attr, val, i) {
45847 if(Array.isArray(attr)) {
45848 attr.forEach(function(a) { doextra(a, val, i); });
45849 return;
45850 }
45851 // quit if explicitly setting this elsewhere
45852 if(attr in aobj || helpers.hasParent(aobj, attr)) return;
45853
45854 var extraparam;
45855 if(attr.substr(0, 6) === 'LAYOUT') {
45856 extraparam = layoutNP(gd.layout, attr.replace('LAYOUT', ''));
45857 } else {
45858 var tracei = traces[i];
45859 var preGUI = fullLayout._tracePreGUI[getFullTrace(tracei)._fullInput.uid];
45860 extraparam = makeNP(preGUI, guiEditFlag)(data[tracei], attr);
45861 }
45862
45863 if(!(attr in undoit)) {
45864 undoit[attr] = a0();
45865 }
45866 if(undoit[attr][i] === undefined) {
45867 undoit[attr][i] = undefinedToNull(extraparam.get());
45868 }
45869 if(val !== undefined) {
45870 extraparam.set(val);
45871 }
45872 }
45873
45874 function allBins(binAttr) {
45875 return function(j) {
45876 return fullData[j][binAttr];
45877 };
45878 }
45879
45880 function arrayBins(binAttr) {
45881 return function(vij, j) {
45882 return vij === false ? fullData[traces[j]][binAttr] : null;
45883 };
45884 }
45885
45886 // now make the changes to gd.data (and occasionally gd.layout)
45887 // and figure out what kind of graphics update we need to do
45888 for(var ai in aobj) {
45889 if(helpers.hasParent(aobj, ai)) {
45890 throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously');
45891 }
45892
45893 var vi = aobj[ai];
45894 var cont;
45895 var contFull;
45896 var param;
45897 var oldVal;
45898 var newVal;
45899 var valObject;
45900
45901 // Backward compatibility shim for turning histogram autobin on,
45902 // or freezing previous autobinned values.
45903 // Replace obsolete `autobin(x|y): true` with `(x|y)bins: null`
45904 // and `autobin(x|y): false` with the `(x|y)bins` in `fullData`
45905 if(ai === 'autobinx' || ai === 'autobiny') {
45906 ai = ai.charAt(ai.length - 1) + 'bins';
45907 if(Array.isArray(vi)) vi = vi.map(arrayBins(ai));
45908 else if(vi === false) vi = traces.map(allBins(ai));
45909 else vi = null;
45910 }
45911
45912 redoit[ai] = vi;
45913
45914 if(ai.substr(0, 6) === 'LAYOUT') {
45915 param = layoutNP(gd.layout, ai.replace('LAYOUT', ''));
45916 undoit[ai] = [undefinedToNull(param.get())];
45917 // since we're allowing val to be an array, allow it here too,
45918 // even though that's meaningless
45919 param.set(Array.isArray(vi) ? vi[0] : vi);
45920 // ironically, the layout attrs in restyle only require replot,
45921 // not relayout
45922 flags.calc = true;
45923 continue;
45924 }
45925
45926 // set attribute in gd.data
45927 undoit[ai] = a0();
45928 for(i = 0; i < traces.length; i++) {
45929 cont = data[traces[i]];
45930 contFull = getFullTrace(traces[i]);
45931 var preGUI = fullLayout._tracePreGUI[contFull._fullInput.uid];
45932 param = makeNP(preGUI, guiEditFlag)(cont, ai);
45933 oldVal = param.get();
45934 newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
45935
45936 if(newVal === undefined) continue;
45937
45938 var finalPart = param.parts[param.parts.length - 1];
45939 var prefix = ai.substr(0, ai.length - finalPart.length - 1);
45940 var prefixDot = prefix ? prefix + '.' : '';
45941 var innerContFull = prefix ?
45942 nestedProperty(contFull, prefix).get() : contFull;
45943
45944 valObject = PlotSchema.getTraceValObject(contFull, param.parts);
45945
45946 if(valObject && valObject.impliedEdits && newVal !== null) {
45947 for(var impliedKey in valObject.impliedEdits) {
45948 doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey], i);
45949 }
45950 } else if((finalPart === 'thicknessmode' || finalPart === 'lenmode') &&
45951 oldVal !== newVal &&
45952 (newVal === 'fraction' || newVal === 'pixels') &&
45953 innerContFull
45954 ) {
45955 // changing colorbar size modes,
45956 // make the resulting size not change
45957 // note that colorbar fractional sizing is based on the
45958 // original plot size, before anything (like a colorbar)
45959 // increases the margins
45960
45961 var gs = fullLayout._size;
45962 var orient = innerContFull.orient;
45963 var topOrBottom = (orient === 'top') || (orient === 'bottom');
45964 if(finalPart === 'thicknessmode') {
45965 var thicknorm = topOrBottom ? gs.h : gs.w;
45966 doextra(prefixDot + 'thickness', innerContFull.thickness *
45967 (newVal === 'fraction' ? 1 / thicknorm : thicknorm), i);
45968 } else {
45969 var lennorm = topOrBottom ? gs.w : gs.h;
45970 doextra(prefixDot + 'len', innerContFull.len *
45971 (newVal === 'fraction' ? 1 / lennorm : lennorm), i);
45972 }
45973 } else if(ai === 'type' && (
45974 (newVal === 'pie') !== (oldVal === 'pie') ||
45975 (newVal === 'funnelarea') !== (oldVal === 'funnelarea')
45976 )) {
45977 var labelsTo = 'x';
45978 var valuesTo = 'y';
45979 if((newVal === 'bar' || oldVal === 'bar') && cont.orientation === 'h') {
45980 labelsTo = 'y';
45981 valuesTo = 'x';
45982 }
45983 Lib.swapAttrs(cont, ['?', '?src'], 'labels', labelsTo);
45984 Lib.swapAttrs(cont, ['d?', '?0'], 'label', labelsTo);
45985 Lib.swapAttrs(cont, ['?', '?src'], 'values', valuesTo);
45986
45987 if(oldVal === 'pie' || oldVal === 'funnelarea') {
45988 nestedProperty(cont, 'marker.color')
45989 .set(nestedProperty(cont, 'marker.colors').get());
45990
45991 // super kludgy - but if all pies are gone we won't remove them otherwise
45992 fullLayout._pielayer.selectAll('g.trace').remove();
45993 } else if(Registry.traceIs(cont, 'cartesian')) {
45994 nestedProperty(cont, 'marker.colors')
45995 .set(nestedProperty(cont, 'marker.color').get());
45996 }
45997 }
45998
45999 undoit[ai][i] = undefinedToNull(oldVal);
46000 // set the new value - if val is an array, it's one el per trace
46001 // first check for attributes that get more complex alterations
46002 var swapAttrs = [
46003 'swapxy', 'swapxyaxes', 'orientation', 'orientationaxes'
46004 ];
46005 if(swapAttrs.indexOf(ai) !== -1) {
46006 // setting an orientation: make sure it's changing
46007 // before we swap everything else
46008 if(ai === 'orientation') {
46009 param.set(newVal);
46010 // obnoxious that we need this level of coupling... but in order to
46011 // properly handle setting orientation to `null` we need to mimic
46012 // the logic inside Bars.supplyDefaults for default orientation
46013 var defaultOrientation = (cont.x && !cont.y) ? 'h' : 'v';
46014 if((param.get() || defaultOrientation) === contFull.orientation) {
46015 continue;
46016 }
46017 } else if(ai === 'orientationaxes') {
46018 // orientationaxes has no value,
46019 // it flips everything and the axes
46020
46021 cont.orientation =
46022 {v: 'h', h: 'v'}[contFull.orientation];
46023 }
46024 helpers.swapXYData(cont);
46025 flags.calc = flags.clearAxisTypes = true;
46026 } else if(Plots.dataArrayContainers.indexOf(param.parts[0]) !== -1) {
46027 // TODO: use manageArrays.applyContainerArrayChanges here too
46028 helpers.manageArrayContainers(param, newVal, undoit);
46029 flags.calc = true;
46030 } else {
46031 if(valObject) {
46032 // must redo calcdata when restyling array values of arrayOk attributes
46033 // ... but no need to this for regl-based traces
46034 if(valObject.arrayOk &&
46035 !Registry.traceIs(contFull, 'regl') &&
46036 (Lib.isArrayOrTypedArray(newVal) || Lib.isArrayOrTypedArray(oldVal))
46037 ) {
46038 flags.calc = true;
46039 } else editTypes.update(flags, valObject);
46040 } else {
46041 /*
46042 * if we couldn't find valObject, assume a full recalc.
46043 * This can happen if you're changing type and making
46044 * some other edits too, so the modules we're
46045 * looking at don't have these attributes in them.
46046 */
46047 flags.calc = true;
46048 }
46049
46050 // all the other ones, just modify that one attribute
46051 param.set(newVal);
46052 }
46053 }
46054
46055 // swap the data attributes of the relevant x and y axes?
46056 if(['swapxyaxes', 'orientationaxes'].indexOf(ai) !== -1) {
46057 Axes.swap(gd, traces);
46058 }
46059
46060 // swap hovermode if set to "compare x/y data"
46061 if(ai === 'orientationaxes') {
46062 var hovermode = nestedProperty(gd.layout, 'hovermode');
46063 if(hovermode.get() === 'x') {
46064 hovermode.set('y');
46065 } else if(hovermode.get() === 'y') {
46066 hovermode.set('x');
46067 } else if(hovermode.get() === 'x unified') {
46068 hovermode.set('y unified');
46069 } else if(hovermode.get() === 'y unified') {
46070 hovermode.set('x unified');
46071 }
46072 }
46073
46074 // Major enough changes deserve autoscale and
46075 // non-reversed axes so people don't get confused
46076 //
46077 // Note: autobin (or its new analog bin clearing) is not included here
46078 // since we're not pushing bins back to gd.data, so if we have bin
46079 // info it was explicitly provided by the user.
46080 if(['orientation', 'type'].indexOf(ai) !== -1) {
46081 axlist = [];
46082 for(i = 0; i < traces.length; i++) {
46083 var trace = data[traces[i]];
46084
46085 if(Registry.traceIs(trace, 'cartesian')) {
46086 addToAxlist(trace.xaxis || 'x');
46087 addToAxlist(trace.yaxis || 'y');
46088 }
46089 }
46090
46091 doextra(axlist.map(autorangeAttr), true, 0);
46092 doextra(axlist.map(rangeAttr), [0, 1], 0);
46093 }
46094 }
46095
46096 if(flags.calc || flags.plot) {
46097 flags.fullReplot = true;
46098 }
46099
46100 return {
46101 flags: flags,
46102 undoit: undoit,
46103 redoit: redoit,
46104 traces: traces,
46105 eventData: Lib.extendDeepNoArrays([], [eventData, traces])
46106 };
46107}
46108
46109/**
46110 * Converts deprecated attribute keys to
46111 * the current API to ensure backwards compatibility.
46112 *
46113 * This is needed for the update mechanism to determine which
46114 * subroutines to run based on the actual attribute
46115 * definitions (that don't include the deprecated ones).
46116 *
46117 * E.g. Maps {'xaxis.title': 'A chart'} to {'xaxis.title.text': 'A chart'}
46118 * and {titlefont: {...}} to {'title.font': {...}}.
46119 *
46120 * @param aobj
46121 */
46122function cleanDeprecatedAttributeKeys(aobj) {
46123 var oldAxisTitleRegex = Lib.counterRegex('axis', '\.title', false, false);
46124 var colorbarRegex = /colorbar\.title$/;
46125 var keys = Object.keys(aobj);
46126 var i, key, value;
46127
46128 for(i = 0; i < keys.length; i++) {
46129 key = keys[i];
46130 value = aobj[key];
46131
46132 if((key === 'title' || oldAxisTitleRegex.test(key) || colorbarRegex.test(key)) &&
46133 (typeof value === 'string' || typeof value === 'number')) {
46134 replace(key, key.replace('title', 'title.text'));
46135 } else if(key.indexOf('titlefont') > -1) {
46136 replace(key, key.replace('titlefont', 'title.font'));
46137 } else if(key.indexOf('titleposition') > -1) {
46138 replace(key, key.replace('titleposition', 'title.position'));
46139 } else if(key.indexOf('titleside') > -1) {
46140 replace(key, key.replace('titleside', 'title.side'));
46141 } else if(key.indexOf('titleoffset') > -1) {
46142 replace(key, key.replace('titleoffset', 'title.offset'));
46143 }
46144 }
46145
46146 function replace(oldAttrStr, newAttrStr) {
46147 aobj[newAttrStr] = aobj[oldAttrStr];
46148 delete aobj[oldAttrStr];
46149 }
46150}
46151
46152/**
46153 * relayout: update layout attributes of an existing plot
46154 *
46155 * Can be called two ways:
46156 *
46157 * Signature 1:
46158 * @param {String | HTMLDivElement} gd
46159 * the id or dom element of the graph container div
46160 * @param {String} astr
46161 * attribute string (like `'xaxis.range[0]'`) to update
46162 * @param {*} val
46163 * value to give this attribute
46164 *
46165 * Signature 2:
46166 * @param {String | HTMLDivElement} gd
46167 * (as in signature 1)
46168 * @param {Object} aobj
46169 * attribute object `{astr1: val1, astr2: val2 ...}`
46170 * allows setting multiple attributes simultaneously
46171 */
46172function relayout(gd, astr, val) {
46173 gd = Lib.getGraphDiv(gd);
46174 helpers.clearPromiseQueue(gd);
46175
46176 if(gd.framework && gd.framework.isPolar) {
46177 return Promise.resolve(gd);
46178 }
46179
46180 var aobj = {};
46181 if(typeof astr === 'string') {
46182 aobj[astr] = val;
46183 } else if(Lib.isPlainObject(astr)) {
46184 aobj = Lib.extendFlat({}, astr);
46185 } else {
46186 Lib.warn('Relayout fail.', astr, val);
46187 return Promise.reject();
46188 }
46189
46190 if(Object.keys(aobj).length) gd.changed = true;
46191
46192 var specs = _relayout(gd, aobj);
46193 var flags = specs.flags;
46194
46195 // clear calcdata if required
46196 if(flags.calc) gd.calcdata = undefined;
46197
46198 // fill in redraw sequence
46199
46200 // even if we don't have anything left in aobj,
46201 // something may have happened within relayout that we
46202 // need to wait for
46203 var seq = [Plots.previousPromises];
46204
46205 if(flags.layoutReplot) {
46206 seq.push(subroutines.layoutReplot);
46207 } else if(Object.keys(aobj).length) {
46208 axRangeSupplyDefaultsByPass(gd, flags, specs) || Plots.supplyDefaults(gd);
46209
46210 if(flags.legend) seq.push(subroutines.doLegend);
46211 if(flags.layoutstyle) seq.push(subroutines.layoutStyles);
46212 if(flags.axrange) addAxRangeSequence(seq, specs.rangesAltered);
46213 if(flags.ticks) seq.push(subroutines.doTicksRelayout);
46214 if(flags.modebar) seq.push(subroutines.doModeBar);
46215 if(flags.camera) seq.push(subroutines.doCamera);
46216 if(flags.colorbars) seq.push(subroutines.doColorBars);
46217
46218 seq.push(emitAfterPlot);
46219 }
46220
46221 seq.push(Plots.rehover, Plots.redrag);
46222
46223 Queue.add(gd,
46224 relayout, [gd, specs.undoit],
46225 relayout, [gd, specs.redoit]
46226 );
46227
46228 var plotDone = Lib.syncOrAsync(seq, gd);
46229 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
46230
46231 return plotDone.then(function() {
46232 gd.emit('plotly_relayout', specs.eventData);
46233 return gd;
46234 });
46235}
46236
46237// Optimization mostly for large splom traces where
46238// Plots.supplyDefaults can take > 100ms
46239function axRangeSupplyDefaultsByPass(gd, flags, specs) {
46240 var fullLayout = gd._fullLayout;
46241
46242 if(!flags.axrange) return false;
46243
46244 for(var k in flags) {
46245 if(k !== 'axrange' && flags[k]) return false;
46246 }
46247
46248 for(var axId in specs.rangesAltered) {
46249 var axName = Axes.id2name(axId);
46250 var axIn = gd.layout[axName];
46251 var axOut = fullLayout[axName];
46252 axOut.autorange = axIn.autorange;
46253 axOut.range = axIn.range.slice();
46254 axOut.cleanRange();
46255
46256 if(axOut._matchGroup) {
46257 for(var axId2 in axOut._matchGroup) {
46258 if(axId2 !== axId) {
46259 var ax2 = fullLayout[Axes.id2name(axId2)];
46260 ax2.autorange = axOut.autorange;
46261 ax2.range = axOut.range.slice();
46262 ax2._input.range = axOut.range.slice();
46263 }
46264 }
46265 }
46266 }
46267
46268 return true;
46269}
46270
46271function addAxRangeSequence(seq, rangesAltered) {
46272 // N.B. leave as sequence of subroutines (for now) instead of
46273 // subroutine of its own so that finalDraw always gets
46274 // executed after drawData
46275 var drawAxes = rangesAltered ?
46276 function(gd) {
46277 var axIds = [];
46278 var skipTitle = true;
46279
46280 for(var id in rangesAltered) {
46281 var ax = Axes.getFromId(gd, id);
46282 axIds.push(id);
46283
46284 if(ax._matchGroup) {
46285 for(var id2 in ax._matchGroup) {
46286 if(!rangesAltered[id2]) {
46287 axIds.push(id2);
46288 }
46289 }
46290 }
46291
46292 if(ax.automargin) skipTitle = false;
46293 }
46294
46295 return Axes.draw(gd, axIds, {skipTitle: skipTitle});
46296 } :
46297 function(gd) {
46298 return Axes.draw(gd, 'redraw');
46299 };
46300
46301 seq.push(
46302 clearSelect,
46303 subroutines.doAutoRangeAndConstraints,
46304 drawAxes,
46305 subroutines.drawData,
46306 subroutines.finalDraw
46307 );
46308}
46309
46310var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/;
46311var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/;
46312var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/;
46313
46314function _relayout(gd, aobj) {
46315 var layout = gd.layout;
46316 var fullLayout = gd._fullLayout;
46317 var guiEditFlag = fullLayout._guiEditing;
46318 var layoutNP = makeNP(fullLayout._preGUI, guiEditFlag);
46319 var keys = Object.keys(aobj);
46320 var axes = Axes.list(gd);
46321 var eventData = Lib.extendDeepAll({}, aobj);
46322 var arrayEdits = {};
46323
46324 var arrayStr, i, j;
46325
46326 cleanDeprecatedAttributeKeys(aobj);
46327 keys = Object.keys(aobj);
46328
46329 // look for 'allaxes', split out into all axes
46330 // in case of 3D the axis are nested within a scene which is held in _id
46331 for(i = 0; i < keys.length; i++) {
46332 if(keys[i].indexOf('allaxes') === 0) {
46333 for(j = 0; j < axes.length; j++) {
46334 var scene = axes[j]._id.substr(1);
46335 var axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '';
46336 var newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name);
46337
46338 if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]];
46339 }
46340
46341 delete aobj[keys[i]];
46342 }
46343 }
46344
46345 // initialize flags
46346 var flags = editTypes.layoutFlags();
46347
46348 // copies of the change (and previous values of anything affected)
46349 // for the undo / redo queue
46350 var redoit = {};
46351 var undoit = {};
46352
46353 // for attrs that interact (like scales & autoscales), save the
46354 // old vals before making the change
46355 // val=undefined will not set a value, just record what the value was.
46356 // attr can be an array to set several at once (all to the same val)
46357 function doextra(attr, val) {
46358 if(Array.isArray(attr)) {
46359 attr.forEach(function(a) { doextra(a, val); });
46360 return;
46361 }
46362
46363 // if we have another value for this attribute (explicitly or
46364 // via a parent) do not override with this auto-generated extra
46365 if(attr in aobj || helpers.hasParent(aobj, attr)) return;
46366
46367 var p = layoutNP(layout, attr);
46368 if(!(attr in undoit)) {
46369 undoit[attr] = undefinedToNull(p.get());
46370 }
46371 if(val !== undefined) p.set(val);
46372 }
46373
46374 // for constraint enforcement: keep track of all axes (as {id: name})
46375 // we're editing the (auto)range of, so we can tell the others constrained
46376 // to scale with them that it's OK for them to shrink
46377 var rangesAltered = {};
46378 var axId;
46379
46380 function recordAlteredAxis(pleafPlus) {
46381 var axId = Axes.name2id(pleafPlus.split('.')[0]);
46382 rangesAltered[axId] = 1;
46383 return axId;
46384 }
46385
46386 // alter gd.layout
46387 for(var ai in aobj) {
46388 if(helpers.hasParent(aobj, ai)) {
46389 throw new Error('cannot set ' + ai + ' and a parent attribute simultaneously');
46390 }
46391
46392 var p = layoutNP(layout, ai);
46393 var vi = aobj[ai];
46394 var plen = p.parts.length;
46395 // p.parts may end with an index integer if the property is an array
46396 var pend = plen - 1;
46397 while(pend > 0 && typeof p.parts[pend] !== 'string') pend--;
46398 // last property in chain (leaf node)
46399 var pleaf = p.parts[pend];
46400 // leaf plus immediate parent
46401 var pleafPlus = p.parts[pend - 1] + '.' + pleaf;
46402 // trunk nodes (everything except the leaf)
46403 var ptrunk = p.parts.slice(0, pend).join('.');
46404 var parentIn = nestedProperty(gd.layout, ptrunk).get();
46405 var parentFull = nestedProperty(fullLayout, ptrunk).get();
46406 var vOld = p.get();
46407
46408 if(vi === undefined) continue;
46409
46410 redoit[ai] = vi;
46411
46412 // axis reverse is special - it is its own inverse
46413 // op and has no flag.
46414 undoit[ai] = (pleaf === 'reverse') ? vi : undefinedToNull(vOld);
46415
46416 var valObject = PlotSchema.getLayoutValObject(fullLayout, p.parts);
46417
46418 if(valObject && valObject.impliedEdits && vi !== null) {
46419 for(var impliedKey in valObject.impliedEdits) {
46420 doextra(Lib.relativeAttr(ai, impliedKey), valObject.impliedEdits[impliedKey]);
46421 }
46422 }
46423
46424 // Setting width or height to null must reset the graph's width / height
46425 // back to its initial value as computed during the first pass in Plots.plotAutoSize.
46426 //
46427 // To do so, we must manually set them back here using the _initialAutoSize cache.
46428 // can't use impliedEdits for this because behavior depends on vi
46429 if(['width', 'height'].indexOf(ai) !== -1) {
46430 if(vi) {
46431 doextra('autosize', null);
46432 // currently we don't support autosize one dim only - so
46433 // explicitly set the other one. Note that doextra will
46434 // ignore this if the same relayout call also provides oppositeAttr
46435 var oppositeAttr = ai === 'height' ? 'width' : 'height';
46436 doextra(oppositeAttr, fullLayout[oppositeAttr]);
46437 } else {
46438 fullLayout[ai] = gd._initialAutoSize[ai];
46439 }
46440 } else if(ai === 'autosize') {
46441 // depends on vi here too, so again can't use impliedEdits
46442 doextra('width', vi ? null : fullLayout.width);
46443 doextra('height', vi ? null : fullLayout.height);
46444 } else if(pleafPlus.match(AX_RANGE_RE)) {
46445 // check autorange vs range
46446
46447 recordAlteredAxis(pleafPlus);
46448 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46449 } else if(pleafPlus.match(AX_AUTORANGE_RE)) {
46450 recordAlteredAxis(pleafPlus);
46451 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46452 var axFull = nestedProperty(fullLayout, ptrunk).get();
46453 if(axFull._inputDomain) {
46454 // if we're autoranging and this axis has a constrained domain,
46455 // reset it so we don't get locked into a shrunken size
46456 axFull._input.domain = axFull._inputDomain.slice();
46457 }
46458 } else if(pleafPlus.match(AX_DOMAIN_RE)) {
46459 nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null);
46460 }
46461
46462 // toggling axis type between log and linear: we need to convert
46463 // positions for components that are still using linearized values,
46464 // not data values like newer components.
46465 // previously we did this for log <-> not-log, but now only do it
46466 // for log <-> linear
46467 if(pleaf === 'type') {
46468 var ax = parentIn;
46469 var toLog = parentFull.type === 'linear' && vi === 'log';
46470 var fromLog = parentFull.type === 'log' && vi === 'linear';
46471
46472 if(toLog || fromLog) {
46473 if(!ax || !ax.range) {
46474 // 2D never gets here, but 3D does
46475 // I don't think this is needed, but left here in case there
46476 // are edge cases I'm not thinking of.
46477 doextra(ptrunk + '.autorange', true);
46478 } else if(!parentFull.autorange) {
46479 // toggling log without autorange: need to also recalculate ranges
46480 // because log axes use linearized values for range endpoints
46481 var r0 = ax.range[0];
46482 var r1 = ax.range[1];
46483 if(toLog) {
46484 // if both limits are negative, autorange
46485 if(r0 <= 0 && r1 <= 0) {
46486 doextra(ptrunk + '.autorange', true);
46487 }
46488 // if one is negative, set it 6 orders below the other.
46489 if(r0 <= 0) r0 = r1 / 1e6;
46490 else if(r1 <= 0) r1 = r0 / 1e6;
46491 // now set the range values as appropriate
46492 doextra(ptrunk + '.range[0]', Math.log(r0) / Math.LN10);
46493 doextra(ptrunk + '.range[1]', Math.log(r1) / Math.LN10);
46494 } else {
46495 doextra(ptrunk + '.range[0]', Math.pow(10, r0));
46496 doextra(ptrunk + '.range[1]', Math.pow(10, r1));
46497 }
46498 } else if(toLog) {
46499 // just make sure the range is positive and in the right
46500 // order, it'll get recalculated later
46501 ax.range = (ax.range[1] > ax.range[0]) ? [1, 2] : [2, 1];
46502 }
46503
46504 // clear polar view initial stash for radial range so that
46505 // value get recomputed in correct units
46506 if(Array.isArray(fullLayout._subplots.polar) &&
46507 fullLayout._subplots.polar.length &&
46508 fullLayout[p.parts[0]] &&
46509 p.parts[1] === 'radialaxis'
46510 ) {
46511 delete fullLayout[p.parts[0]]._subplot.viewInitial['radialaxis.range'];
46512 }
46513
46514 // Annotations and images also need to convert to/from linearized coords
46515 // Shapes do not need this :)
46516 Registry.getComponentMethod('annotations', 'convertCoords')(gd, parentFull, vi, doextra);
46517 Registry.getComponentMethod('images', 'convertCoords')(gd, parentFull, vi, doextra);
46518 } else {
46519 // any other type changes: the range from the previous type
46520 // will not make sense, so autorange it.
46521 doextra(ptrunk + '.autorange', true);
46522 doextra(ptrunk + '.range', null);
46523 }
46524 nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
46525 } else if(pleaf.match(AX_NAME_PATTERN)) {
46526 var fullProp = nestedProperty(fullLayout, ai).get();
46527 var newType = (vi || {}).type;
46528
46529 // This can potentially cause strange behavior if the autotype is not
46530 // numeric (linear, because we don't auto-log) but the previous type
46531 // was log. That's a very strange edge case though
46532 if(!newType || newType === '-') newType = 'linear';
46533 Registry.getComponentMethod('annotations', 'convertCoords')(gd, fullProp, newType, doextra);
46534 Registry.getComponentMethod('images', 'convertCoords')(gd, fullProp, newType, doextra);
46535 }
46536
46537 // alter gd.layout
46538
46539 // collect array component edits for execution all together
46540 // so we can ensure consistent behavior adding/removing items
46541 // and order-independence for add/remove/edit all together in
46542 // one relayout call
46543 var containerArrayMatch = manageArrays.containerArrayMatch(ai);
46544 if(containerArrayMatch) {
46545 arrayStr = containerArrayMatch.array;
46546 i = containerArrayMatch.index;
46547 var propStr = containerArrayMatch.property;
46548 var updateValObject = valObject || {editType: 'calc'};
46549
46550 if(i !== '' && propStr === '') {
46551 // special handling of undoit if we're adding or removing an element
46552 // ie 'annotations[2]' which can be {...} (add) or null,
46553 // does not work when replacing the entire array
46554 if(manageArrays.isAddVal(vi)) {
46555 undoit[ai] = null;
46556 } else if(manageArrays.isRemoveVal(vi)) {
46557 undoit[ai] = (nestedProperty(layout, arrayStr).get() || [])[i];
46558 } else {
46559 Lib.warn('unrecognized full object value', aobj);
46560 }
46561 }
46562 editTypes.update(flags, updateValObject);
46563
46564 // prepare the edits object we'll send to applyContainerArrayChanges
46565 if(!arrayEdits[arrayStr]) arrayEdits[arrayStr] = {};
46566 var objEdits = arrayEdits[arrayStr][i];
46567 if(!objEdits) objEdits = arrayEdits[arrayStr][i] = {};
46568 objEdits[propStr] = vi;
46569
46570 delete aobj[ai];
46571 } else if(pleaf === 'reverse') {
46572 // handle axis reversal explicitly, as there's no 'reverse' attribute
46573
46574 if(parentIn.range) parentIn.range.reverse();
46575 else {
46576 doextra(ptrunk + '.autorange', true);
46577 parentIn.range = [1, 0];
46578 }
46579
46580 if(parentFull.autorange) flags.calc = true;
46581 else flags.plot = true;
46582 } else {
46583 if((fullLayout._has('scatter-like') && fullLayout._has('regl')) &&
46584 (ai === 'dragmode' &&
46585 (vi === 'lasso' || vi === 'select') &&
46586 !(vOld === 'lasso' || vOld === 'select'))
46587 ) {
46588 flags.plot = true;
46589 } else if(fullLayout._has('gl2d')) {
46590 flags.plot = true;
46591 } else if(valObject) editTypes.update(flags, valObject);
46592 else flags.calc = true;
46593
46594 p.set(vi);
46595 }
46596 }
46597
46598 // now we've collected component edits - execute them all together
46599 for(arrayStr in arrayEdits) {
46600 var finished = manageArrays.applyContainerArrayChanges(gd,
46601 layoutNP(layout, arrayStr), arrayEdits[arrayStr], flags, layoutNP);
46602 if(!finished) flags.plot = true;
46603 }
46604
46605 // figure out if we need to recalculate axis constraints
46606 var constraints = fullLayout._axisConstraintGroups || [];
46607 for(axId in rangesAltered) {
46608 for(i = 0; i < constraints.length; i++) {
46609 var group = constraints[i];
46610 if(group[axId]) {
46611 // Always recalc if we're changing constrained ranges.
46612 // Otherwise it's possible to violate the constraints by
46613 // specifying arbitrary ranges for all axes in the group.
46614 // this way some ranges may expand beyond what's specified,
46615 // as they do at first draw, to satisfy the constraints.
46616 flags.calc = true;
46617 for(var groupAxId in group) {
46618 if(!rangesAltered[groupAxId]) {
46619 Axes.getFromId(gd, groupAxId)._constraintShrinkable = true;
46620 }
46621 }
46622 }
46623 }
46624 }
46625
46626 // If the autosize changed or height or width was explicitly specified,
46627 // this triggers a redraw
46628 // TODO: do we really need special aobj.height/width handling here?
46629 // couldn't editType do this?
46630 if(updateAutosize(gd) || aobj.height || aobj.width) flags.plot = true;
46631
46632 if(flags.plot || flags.calc) {
46633 flags.layoutReplot = true;
46634 }
46635
46636 // now all attribute mods are done, as are
46637 // redo and undo so we can save them
46638
46639 return {
46640 flags: flags,
46641 rangesAltered: rangesAltered,
46642 undoit: undoit,
46643 redoit: redoit,
46644 eventData: eventData
46645 };
46646}
46647
46648/*
46649 * updateAutosize: we made a change, does it change the autosize result?
46650 * puts the new size into fullLayout
46651 * returns true if either height or width changed
46652 */
46653function updateAutosize(gd) {
46654 var fullLayout = gd._fullLayout;
46655 var oldWidth = fullLayout.width;
46656 var oldHeight = fullLayout.height;
46657
46658 // calculate autosizing
46659 if(gd.layout.autosize) Plots.plotAutoSize(gd, gd.layout, fullLayout);
46660
46661 return (fullLayout.width !== oldWidth) || (fullLayout.height !== oldHeight);
46662}
46663
46664/**
46665 * update: update trace and layout attributes of an existing plot
46666 *
46667 * @param {String | HTMLDivElement} gd
46668 * the id or DOM element of the graph container div
46669 * @param {Object} traceUpdate
46670 * attribute object `{astr1: val1, astr2: val2 ...}`
46671 * corresponding to updates in the plot's traces
46672 * @param {Object} layoutUpdate
46673 * attribute object `{astr1: val1, astr2: val2 ...}`
46674 * corresponding to updates in the plot's layout
46675 * @param {Number[] | Number} [traces]
46676 * integer or array of integers for the traces to alter (all if omitted)
46677 *
46678 */
46679function update(gd, traceUpdate, layoutUpdate, _traces) {
46680 gd = Lib.getGraphDiv(gd);
46681 helpers.clearPromiseQueue(gd);
46682
46683 if(gd.framework && gd.framework.isPolar) {
46684 return Promise.resolve(gd);
46685 }
46686
46687 if(!Lib.isPlainObject(traceUpdate)) traceUpdate = {};
46688 if(!Lib.isPlainObject(layoutUpdate)) layoutUpdate = {};
46689
46690 if(Object.keys(traceUpdate).length) gd.changed = true;
46691 if(Object.keys(layoutUpdate).length) gd.changed = true;
46692
46693 var traces = helpers.coerceTraceIndices(gd, _traces);
46694
46695 var restyleSpecs = _restyle(gd, Lib.extendFlat({}, traceUpdate), traces);
46696 var restyleFlags = restyleSpecs.flags;
46697
46698 var relayoutSpecs = _relayout(gd, Lib.extendFlat({}, layoutUpdate));
46699 var relayoutFlags = relayoutSpecs.flags;
46700
46701 // clear calcdata and/or axis types if required
46702 if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined;
46703 if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate);
46704
46705 // fill in redraw sequence
46706 var seq = [];
46707
46708 if(relayoutFlags.layoutReplot) {
46709 // N.B. works fine when both
46710 // relayoutFlags.layoutReplot and restyleFlags.fullReplot are true
46711 seq.push(subroutines.layoutReplot);
46712 } else if(restyleFlags.fullReplot) {
46713 seq.push(exports.plot);
46714 } else {
46715 seq.push(Plots.previousPromises);
46716 axRangeSupplyDefaultsByPass(gd, relayoutFlags, relayoutSpecs) || Plots.supplyDefaults(gd);
46717
46718 if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
46719 if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
46720 if(relayoutFlags.legend) seq.push(subroutines.doLegend);
46721 if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
46722 if(relayoutFlags.axrange) addAxRangeSequence(seq, relayoutSpecs.rangesAltered);
46723 if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
46724 if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
46725 if(relayoutFlags.camera) seq.push(subroutines.doCamera);
46726
46727 seq.push(emitAfterPlot);
46728 }
46729
46730 seq.push(Plots.rehover, Plots.redrag);
46731
46732 Queue.add(gd,
46733 update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces],
46734 update, [gd, restyleSpecs.redoit, relayoutSpecs.redoit, restyleSpecs.traces]
46735 );
46736
46737 var plotDone = Lib.syncOrAsync(seq, gd);
46738 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
46739
46740 return plotDone.then(function() {
46741 gd.emit('plotly_update', {
46742 data: restyleSpecs.eventData,
46743 layout: relayoutSpecs.eventData
46744 });
46745
46746 return gd;
46747 });
46748}
46749
46750/*
46751 * internal-use-only restyle/relayout/update variants that record the initial
46752 * values in (fullLayout|fullTrace)._preGUI so changes can be persisted across
46753 * Plotly.react data updates, dependent on uirevision attributes
46754 */
46755function guiEdit(func) {
46756 return function wrappedEdit(gd) {
46757 gd._fullLayout._guiEditing = true;
46758 var p = func.apply(null, arguments);
46759 gd._fullLayout._guiEditing = false;
46760 return p;
46761 };
46762}
46763
46764// For connecting edited layout attributes to uirevision attrs
46765// If no `attr` we use `match[1] + '.uirevision'`
46766// Ordered by most common edits first, to minimize our search time
46767var layoutUIControlPatterns = [
46768 {pattern: /^hiddenlabels/, attr: 'legend.uirevision'},
46769 {pattern: /^((x|y)axis\d*)\.((auto)?range|title\.text)/},
46770
46771 // showspikes and modes include those nested inside scenes
46772 {pattern: /axis\d*\.showspikes$/, attr: 'modebar.uirevision'},
46773 {pattern: /(hover|drag)mode$/, attr: 'modebar.uirevision'},
46774
46775 {pattern: /^(scene\d*)\.camera/},
46776 {pattern: /^(geo\d*)\.(projection|center|fitbounds)/},
46777 {pattern: /^(ternary\d*\.[abc]axis)\.(min|title\.text)$/},
46778 {pattern: /^(polar\d*\.radialaxis)\.((auto)?range|angle|title\.text)/},
46779 {pattern: /^(polar\d*\.angularaxis)\.rotation/},
46780 {pattern: /^(mapbox\d*)\.(center|zoom|bearing|pitch)/},
46781
46782 {pattern: /^legend\.(x|y)$/, attr: 'editrevision'},
46783 {pattern: /^(shapes|annotations)/, attr: 'editrevision'},
46784 {pattern: /^title\.text$/, attr: 'editrevision'}
46785];
46786
46787// same for trace attributes: if `attr` is given it's in layout,
46788// or with no `attr` we use `trace.uirevision`
46789var traceUIControlPatterns = [
46790 {pattern: /^selectedpoints$/, attr: 'selectionrevision'},
46791 // "visible" includes trace.transforms[i].styles[j].value.visible
46792 {pattern: /(^|value\.)visible$/, attr: 'legend.uirevision'},
46793 {pattern: /^dimensions\[\d+\]\.constraintrange/},
46794 {pattern: /^node\.(x|y|groups)/}, // for Sankey nodes
46795 {pattern: /^level$/}, // for Sunburst & Treemap traces
46796
46797 // below this you must be in editable: true mode
46798 // TODO: I still put name and title with `trace.uirevision`
46799 // reasonable or should these be `editrevision`?
46800 // Also applies to axis titles up in the layout section
46801
46802 // "name" also includes transform.styles
46803 {pattern: /(^|value\.)name$/},
46804 // including nested colorbar attributes (ie marker.colorbar)
46805 {pattern: /colorbar\.title\.text$/},
46806 {pattern: /colorbar\.(x|y)$/, attr: 'editrevision'}
46807];
46808
46809function findUIPattern(key, patternSpecs) {
46810 for(var i = 0; i < patternSpecs.length; i++) {
46811 var spec = patternSpecs[i];
46812 var match = key.match(spec.pattern);
46813 if(match) {
46814 return {head: match[1], attr: spec.attr};
46815 }
46816 }
46817}
46818
46819// We're finding the new uirevision before supplyDefaults, so do the
46820// inheritance manually. Note that only `undefined` inherits - other
46821// falsy values are returned.
46822function getNewRev(revAttr, container) {
46823 var newRev = nestedProperty(container, revAttr).get();
46824 if(newRev !== undefined) return newRev;
46825
46826 var parts = revAttr.split('.');
46827 parts.pop();
46828 while(parts.length > 1) {
46829 parts.pop();
46830 newRev = nestedProperty(container, parts.join('.') + '.uirevision').get();
46831 if(newRev !== undefined) return newRev;
46832 }
46833
46834 return container.uirevision;
46835}
46836
46837function getFullTraceIndexFromUid(uid, fullData) {
46838 for(var i = 0; i < fullData.length; i++) {
46839 if(fullData[i]._fullInput.uid === uid) return i;
46840 }
46841 return -1;
46842}
46843
46844function getTraceIndexFromUid(uid, data, tracei) {
46845 for(var i = 0; i < data.length; i++) {
46846 if(data[i].uid === uid) return i;
46847 }
46848 // fall back on trace order, but only if user didn't provide a uid for that trace
46849 return (!data[tracei] || data[tracei].uid) ? -1 : tracei;
46850}
46851
46852function valsMatch(v1, v2) {
46853 var v1IsObj = Lib.isPlainObject(v1);
46854 var v1IsArray = Array.isArray(v1);
46855 if(v1IsObj || v1IsArray) {
46856 return (
46857 (v1IsObj && Lib.isPlainObject(v2)) ||
46858 (v1IsArray && Array.isArray(v2))
46859 ) && JSON.stringify(v1) === JSON.stringify(v2);
46860 }
46861 return v1 === v2;
46862}
46863
46864function applyUIRevisions(data, layout, oldFullData, oldFullLayout) {
46865 var layoutPreGUI = oldFullLayout._preGUI;
46866 var key, revAttr, oldRev, newRev, match, preGUIVal, newNP, newVal;
46867 var bothInheritAutorange = [];
46868 var newRangeAccepted = {};
46869 for(key in layoutPreGUI) {
46870 match = findUIPattern(key, layoutUIControlPatterns);
46871 if(match) {
46872 revAttr = match.attr || (match.head + '.uirevision');
46873 oldRev = nestedProperty(oldFullLayout, revAttr).get();
46874 newRev = oldRev && getNewRev(revAttr, layout);
46875 if(newRev && (newRev === oldRev)) {
46876 preGUIVal = layoutPreGUI[key];
46877 if(preGUIVal === null) preGUIVal = undefined;
46878 newNP = nestedProperty(layout, key);
46879 newVal = newNP.get();
46880 if(valsMatch(newVal, preGUIVal)) {
46881 if(newVal === undefined && key.substr(key.length - 9) === 'autorange') {
46882 bothInheritAutorange.push(key.substr(0, key.length - 10));
46883 }
46884 newNP.set(undefinedToNull(nestedProperty(oldFullLayout, key).get()));
46885 continue;
46886 }
46887 }
46888 } else {
46889 Lib.warn('unrecognized GUI edit: ' + key);
46890 }
46891 // if we got this far, the new value was accepted as the new starting
46892 // point (either because it changed or revision changed)
46893 // so remove it from _preGUI for next time.
46894 delete layoutPreGUI[key];
46895
46896 if(key.substr(key.length - 8, 6) === 'range[') {
46897 newRangeAccepted[key.substr(0, key.length - 9)] = 1;
46898 }
46899 }
46900
46901 // Special logic for `autorange`, since it interacts with `range`:
46902 // If the new figure's matching `range` was kept, and `autorange`
46903 // wasn't supplied explicitly in either the original or the new figure,
46904 // we shouldn't alter that - but we may just have done that, so fix it.
46905 for(var i = 0; i < bothInheritAutorange.length; i++) {
46906 var axAttr = bothInheritAutorange[i];
46907 if(newRangeAccepted[axAttr]) {
46908 var newAx = nestedProperty(layout, axAttr).get();
46909 if(newAx) delete newAx.autorange;
46910 }
46911 }
46912
46913 // Now traces - try to match them up by uid (in case we added/deleted in
46914 // the middle), then fall back on index.
46915 var allTracePreGUI = oldFullLayout._tracePreGUI;
46916 for(var uid in allTracePreGUI) {
46917 var tracePreGUI = allTracePreGUI[uid];
46918 var newTrace = null;
46919 var fullInput;
46920 for(key in tracePreGUI) {
46921 // wait until we know we have preGUI values to look for traces
46922 // but if we don't find both, stop looking at this uid
46923 if(!newTrace) {
46924 var fulli = getFullTraceIndexFromUid(uid, oldFullData);
46925 if(fulli < 0) {
46926 // Somehow we didn't even have this trace in oldFullData...
46927 // I guess this could happen with `deleteTraces` or something
46928 delete allTracePreGUI[uid];
46929 break;
46930 }
46931 var fullTrace = oldFullData[fulli];
46932 fullInput = fullTrace._fullInput;
46933
46934 var newTracei = getTraceIndexFromUid(uid, data, fullInput.index);
46935 if(newTracei < 0) {
46936 // No match in new data
46937 delete allTracePreGUI[uid];
46938 break;
46939 }
46940 newTrace = data[newTracei];
46941 }
46942
46943 match = findUIPattern(key, traceUIControlPatterns);
46944 if(match) {
46945 if(match.attr) {
46946 oldRev = nestedProperty(oldFullLayout, match.attr).get();
46947 newRev = oldRev && getNewRev(match.attr, layout);
46948 } else {
46949 oldRev = fullInput.uirevision;
46950 // inheritance for trace.uirevision is simple, just layout.uirevision
46951 newRev = newTrace.uirevision;
46952 if(newRev === undefined) newRev = layout.uirevision;
46953 }
46954
46955 if(newRev && newRev === oldRev) {
46956 preGUIVal = tracePreGUI[key];
46957 if(preGUIVal === null) preGUIVal = undefined;
46958 newNP = nestedProperty(newTrace, key);
46959 newVal = newNP.get();
46960 if(valsMatch(newVal, preGUIVal)) {
46961 newNP.set(undefinedToNull(nestedProperty(fullInput, key).get()));
46962 continue;
46963 }
46964 }
46965 } else {
46966 Lib.warn('unrecognized GUI edit: ' + key + ' in trace uid ' + uid);
46967 }
46968 delete tracePreGUI[key];
46969 }
46970 }
46971}
46972
46973/**
46974 * Plotly.react:
46975 * A plot/update method that takes the full plot state (same API as plot/newPlot)
46976 * and diffs to determine the minimal update pathway
46977 *
46978 * @param {string id or DOM element} gd
46979 * the id or DOM element of the graph container div
46980 * @param {array of objects} data
46981 * array of traces, containing the data and display information for each trace
46982 * @param {object} layout
46983 * object describing the overall display of the plot,
46984 * all the stuff that doesn't pertain to any individual trace
46985 * @param {object} config
46986 * configuration options (see ./plot_config.js for more info)
46987 *
46988 * OR
46989 *
46990 * @param {string id or DOM element} gd
46991 * the id or DOM element of the graph container div
46992 * @param {object} figure
46993 * object containing `data`, `layout`, `config`, and `frames` members
46994 *
46995 */
46996function react(gd, data, layout, config) {
46997 var frames, plotDone;
46998
46999 function addFrames() { return exports.addFrames(gd, frames); }
47000
47001 gd = Lib.getGraphDiv(gd);
47002 helpers.clearPromiseQueue(gd);
47003
47004 var oldFullData = gd._fullData;
47005 var oldFullLayout = gd._fullLayout;
47006
47007 // you can use this as the initial draw as well as to update
47008 if(!Lib.isPlotDiv(gd) || !oldFullData || !oldFullLayout) {
47009 plotDone = exports.newPlot(gd, data, layout, config);
47010 } else {
47011 if(Lib.isPlainObject(data)) {
47012 var obj = data;
47013 data = obj.data;
47014 layout = obj.layout;
47015 config = obj.config;
47016 frames = obj.frames;
47017 }
47018
47019 var configChanged = false;
47020 // assume that if there's a config at all, we're reacting to it too,
47021 // and completely replace the previous config
47022 if(config) {
47023 var oldConfig = Lib.extendDeep({}, gd._context);
47024 gd._context = undefined;
47025 setPlotContext(gd, config);
47026 configChanged = diffConfig(oldConfig, gd._context);
47027 }
47028
47029 gd.data = data || [];
47030 helpers.cleanData(gd.data);
47031 gd.layout = layout || {};
47032 helpers.cleanLayout(gd.layout);
47033
47034 applyUIRevisions(gd.data, gd.layout, oldFullData, oldFullLayout);
47035
47036 // "true" skips updating calcdata and remapping arrays from calcTransforms,
47037 // which supplyDefaults usually does at the end, but we may need to NOT do
47038 // if the diff (which we haven't determined yet) says we'll recalc
47039 Plots.supplyDefaults(gd, {skipUpdateCalc: true});
47040
47041 var newFullData = gd._fullData;
47042 var newFullLayout = gd._fullLayout;
47043 var immutable = newFullLayout.datarevision === undefined;
47044 var transition = newFullLayout.transition;
47045
47046 var relayoutFlags = diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition);
47047 var newDataRevision = relayoutFlags.newDataRevision;
47048 var restyleFlags = diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision);
47049
47050 // TODO: how to translate this part of relayout to Plotly.react?
47051 // // Setting width or height to null must reset the graph's width / height
47052 // // back to its initial value as computed during the first pass in Plots.plotAutoSize.
47053 // //
47054 // // To do so, we must manually set them back here using the _initialAutoSize cache.
47055 // if(['width', 'height'].indexOf(ai) !== -1 && vi === null) {
47056 // fullLayout[ai] = gd._initialAutoSize[ai];
47057 // }
47058
47059 if(updateAutosize(gd)) relayoutFlags.layoutReplot = true;
47060
47061 // clear calcdata if required
47062 if(restyleFlags.calc || relayoutFlags.calc) gd.calcdata = undefined;
47063 // otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
47064 else Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
47065
47066 // Note: what restyle/relayout use impliedEdits and clearAxisTypes for
47067 // must be handled by the user when using Plotly.react.
47068
47069 // fill in redraw sequence
47070 var seq = [];
47071
47072 if(frames) {
47073 gd._transitionData = {};
47074 Plots.createTransitionData(gd);
47075 seq.push(addFrames);
47076 }
47077
47078 // Transition pathway,
47079 // only used when 'transition' is set by user and
47080 // when at least one animatable attribute has changed,
47081 // N.B. config changed aren't animatable
47082 if(newFullLayout.transition && !configChanged && (restyleFlags.anim || relayoutFlags.anim)) {
47083 Plots.doCalcdata(gd);
47084 subroutines.doAutoRangeAndConstraints(gd);
47085
47086 seq.push(function() {
47087 return Plots.transitionFromReact(gd, restyleFlags, relayoutFlags, oldFullLayout);
47088 });
47089 } else if(restyleFlags.fullReplot || relayoutFlags.layoutReplot || configChanged) {
47090 gd._fullLayout._skipDefaults = true;
47091 seq.push(exports.plot);
47092 } else {
47093 for(var componentType in relayoutFlags.arrays) {
47094 var indices = relayoutFlags.arrays[componentType];
47095 if(indices.length) {
47096 var drawOne = Registry.getComponentMethod(componentType, 'drawOne');
47097 if(drawOne !== Lib.noop) {
47098 for(var i = 0; i < indices.length; i++) {
47099 drawOne(gd, indices[i]);
47100 }
47101 } else {
47102 var draw = Registry.getComponentMethod(componentType, 'draw');
47103 if(draw === Lib.noop) {
47104 throw new Error('cannot draw components: ' + componentType);
47105 }
47106 draw(gd);
47107 }
47108 }
47109 }
47110
47111 seq.push(Plots.previousPromises);
47112 if(restyleFlags.style) seq.push(subroutines.doTraceStyle);
47113 if(restyleFlags.colorbars || relayoutFlags.colorbars) seq.push(subroutines.doColorBars);
47114 if(relayoutFlags.legend) seq.push(subroutines.doLegend);
47115 if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
47116 if(relayoutFlags.axrange) addAxRangeSequence(seq);
47117 if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
47118 if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
47119 if(relayoutFlags.camera) seq.push(subroutines.doCamera);
47120 seq.push(emitAfterPlot);
47121 }
47122
47123 seq.push(Plots.rehover, Plots.redrag);
47124
47125 plotDone = Lib.syncOrAsync(seq, gd);
47126 if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
47127 }
47128
47129 return plotDone.then(function() {
47130 gd.emit('plotly_react', {
47131 data: data,
47132 layout: layout
47133 });
47134
47135 return gd;
47136 });
47137}
47138
47139function diffData(gd, oldFullData, newFullData, immutable, transition, newDataRevision) {
47140 var sameTraceLength = oldFullData.length === newFullData.length;
47141
47142 if(!transition && !sameTraceLength) {
47143 return {
47144 fullReplot: true,
47145 calc: true
47146 };
47147 }
47148
47149 var flags = editTypes.traceFlags();
47150 flags.arrays = {};
47151 flags.nChanges = 0;
47152 flags.nChangesAnim = 0;
47153
47154 var i, trace;
47155
47156 function getTraceValObject(parts) {
47157 var out = PlotSchema.getTraceValObject(trace, parts);
47158 if(!trace._module.animatable && out.anim) {
47159 out.anim = false;
47160 }
47161 return out;
47162 }
47163
47164 var diffOpts = {
47165 getValObject: getTraceValObject,
47166 flags: flags,
47167 immutable: immutable,
47168 transition: transition,
47169 newDataRevision: newDataRevision,
47170 gd: gd
47171 };
47172
47173 var seenUIDs = {};
47174
47175 for(i = 0; i < oldFullData.length; i++) {
47176 if(newFullData[i]) {
47177 trace = newFullData[i]._fullInput;
47178 if(Plots.hasMakesDataTransform(trace)) trace = newFullData[i];
47179 if(seenUIDs[trace.uid]) continue;
47180 seenUIDs[trace.uid] = 1;
47181
47182 getDiffFlags(oldFullData[i]._fullInput, trace, [], diffOpts);
47183 }
47184 }
47185
47186 if(flags.calc || flags.plot) {
47187 flags.fullReplot = true;
47188 }
47189
47190 if(transition && flags.nChanges && flags.nChangesAnim) {
47191 flags.anim = (flags.nChanges === flags.nChangesAnim) && sameTraceLength ? 'all' : 'some';
47192 }
47193
47194 return flags;
47195}
47196
47197function diffLayout(gd, oldFullLayout, newFullLayout, immutable, transition) {
47198 var flags = editTypes.layoutFlags();
47199 flags.arrays = {};
47200 flags.rangesAltered = {};
47201 flags.nChanges = 0;
47202 flags.nChangesAnim = 0;
47203
47204 function getLayoutValObject(parts) {
47205 return PlotSchema.getLayoutValObject(newFullLayout, parts);
47206 }
47207
47208 var diffOpts = {
47209 getValObject: getLayoutValObject,
47210 flags: flags,
47211 immutable: immutable,
47212 transition: transition,
47213 gd: gd
47214 };
47215
47216 getDiffFlags(oldFullLayout, newFullLayout, [], diffOpts);
47217
47218 if(flags.plot || flags.calc) {
47219 flags.layoutReplot = true;
47220 }
47221
47222 if(transition && flags.nChanges && flags.nChangesAnim) {
47223 flags.anim = flags.nChanges === flags.nChangesAnim ? 'all' : 'some';
47224 }
47225
47226 return flags;
47227}
47228
47229function getDiffFlags(oldContainer, newContainer, outerparts, opts) {
47230 var valObject, key, astr;
47231
47232 var getValObject = opts.getValObject;
47233 var flags = opts.flags;
47234 var immutable = opts.immutable;
47235 var inArray = opts.inArray;
47236 var arrayIndex = opts.arrayIndex;
47237
47238 function changed() {
47239 var editType = valObject.editType;
47240 if(inArray && editType.indexOf('arraydraw') !== -1) {
47241 Lib.pushUnique(flags.arrays[inArray], arrayIndex);
47242 return;
47243 }
47244 editTypes.update(flags, valObject);
47245
47246 if(editType !== 'none') {
47247 flags.nChanges++;
47248 }
47249
47250 // track animatable changes
47251 if(opts.transition && valObject.anim) {
47252 flags.nChangesAnim++;
47253 }
47254
47255 // track cartesian axes with altered ranges
47256 if(AX_RANGE_RE.test(astr) || AX_AUTORANGE_RE.test(astr)) {
47257 flags.rangesAltered[outerparts[0]] = 1;
47258 }
47259
47260 // clear _inputDomain on cartesian axes with altered domains
47261 if(AX_DOMAIN_RE.test(astr)) {
47262 nestedProperty(newContainer, '_inputDomain').set(null);
47263 }
47264
47265 // track datarevision changes
47266 if(key === 'datarevision') {
47267 flags.newDataRevision = 1;
47268 }
47269 }
47270
47271 function valObjectCanBeDataArray(valObject) {
47272 return valObject.valType === 'data_array' || valObject.arrayOk;
47273 }
47274
47275 for(key in oldContainer) {
47276 // short-circuit based on previous calls or previous keys that already maximized the pathway
47277 if(flags.calc && !opts.transition) return;
47278
47279 var oldVal = oldContainer[key];
47280 var newVal = newContainer[key];
47281 var parts = outerparts.concat(key);
47282 astr = parts.join('.');
47283
47284 if(key.charAt(0) === '_' || typeof oldVal === 'function' || oldVal === newVal) continue;
47285
47286 // FIXME: ax.tick0 and dtick get filled in during plotting (except for geo subplots),
47287 // and unlike other auto values they don't make it back into the input,
47288 // so newContainer won't have them.
47289 if((key === 'tick0' || key === 'dtick') && outerparts[0] !== 'geo') {
47290 var tickMode = newContainer.tickmode;
47291 if(tickMode === 'auto' || tickMode === 'array' || !tickMode) continue;
47292 }
47293 // FIXME: Similarly for axis ranges for 3D
47294 // contourcarpet doesn't HAVE zmin/zmax, they're just auto-added. It needs them.
47295 if(key === 'range' && newContainer.autorange) continue;
47296 if((key === 'zmin' || key === 'zmax') && newContainer.type === 'contourcarpet') continue;
47297
47298 valObject = getValObject(parts);
47299
47300 // in case type changed, we may not even *have* a valObject.
47301 if(!valObject) continue;
47302
47303 if(valObject._compareAsJSON && JSON.stringify(oldVal) === JSON.stringify(newVal)) continue;
47304
47305 var valType = valObject.valType;
47306 var i;
47307
47308 var canBeDataArray = valObjectCanBeDataArray(valObject);
47309 var wasArray = Array.isArray(oldVal);
47310 var nowArray = Array.isArray(newVal);
47311
47312 // hack for traces that modify the data in supplyDefaults, like
47313 // converting 1D to 2D arrays, which will always create new objects
47314 if(wasArray && nowArray) {
47315 var inputKey = '_input_' + key;
47316 var oldValIn = oldContainer[inputKey];
47317 var newValIn = newContainer[inputKey];
47318 if(Array.isArray(oldValIn) && oldValIn === newValIn) continue;
47319 }
47320
47321 if(newVal === undefined) {
47322 if(canBeDataArray && wasArray) flags.calc = true;
47323 else changed();
47324 } else if(valObject._isLinkedToArray) {
47325 var arrayEditIndices = [];
47326 var extraIndices = false;
47327 if(!inArray) flags.arrays[key] = arrayEditIndices;
47328
47329 var minLen = Math.min(oldVal.length, newVal.length);
47330 var maxLen = Math.max(oldVal.length, newVal.length);
47331 if(minLen !== maxLen) {
47332 if(valObject.editType === 'arraydraw') {
47333 extraIndices = true;
47334 } else {
47335 changed();
47336 continue;
47337 }
47338 }
47339
47340 for(i = 0; i < minLen; i++) {
47341 getDiffFlags(oldVal[i], newVal[i], parts.concat(i),
47342 // add array indices, but not if we're already in an array
47343 Lib.extendFlat({inArray: key, arrayIndex: i}, opts));
47344 }
47345
47346 // put this at the end so that we know our collected array indices are sorted
47347 // but the check for length changes happens up front so we can short-circuit
47348 // diffing if appropriate
47349 if(extraIndices) {
47350 for(i = minLen; i < maxLen; i++) {
47351 arrayEditIndices.push(i);
47352 }
47353 }
47354 } else if(!valType && Lib.isPlainObject(oldVal)) {
47355 getDiffFlags(oldVal, newVal, parts, opts);
47356 } else if(canBeDataArray) {
47357 if(wasArray && nowArray) {
47358 // don't try to diff two data arrays. If immutable we know the data changed,
47359 // if not, assume it didn't and let `layout.datarevision` tell us if it did
47360 if(immutable) {
47361 flags.calc = true;
47362 }
47363
47364 // look for animatable attributes when the data changed
47365 if(immutable || opts.newDataRevision) {
47366 changed();
47367 }
47368 } else if(wasArray !== nowArray) {
47369 flags.calc = true;
47370 } else changed();
47371 } else if(wasArray && nowArray) {
47372 // info array, colorscale, 'any' - these are short, just stringify.
47373 // I don't *think* that covers up any real differences post-validation, does it?
47374 // otherwise we need to dive in 1 (info_array) or 2 (colorscale) levels and compare
47375 // all elements.
47376 if(oldVal.length !== newVal.length || String(oldVal) !== String(newVal)) {
47377 changed();
47378 }
47379 } else {
47380 changed();
47381 }
47382 }
47383
47384 for(key in newContainer) {
47385 if(!(key in oldContainer || key.charAt(0) === '_' || typeof newContainer[key] === 'function')) {
47386 valObject = getValObject(outerparts.concat(key));
47387
47388 if(valObjectCanBeDataArray(valObject) && Array.isArray(newContainer[key])) {
47389 flags.calc = true;
47390 return;
47391 } else changed();
47392 }
47393 }
47394}
47395
47396/*
47397 * simple diff for config - for now, just treat all changes as equivalent
47398 */
47399function diffConfig(oldConfig, newConfig) {
47400 var key;
47401
47402 for(key in oldConfig) {
47403 if(key.charAt(0) === '_') continue;
47404 var oldVal = oldConfig[key];
47405 var newVal = newConfig[key];
47406 if(oldVal !== newVal) {
47407 if(Lib.isPlainObject(oldVal) && Lib.isPlainObject(newVal)) {
47408 if(diffConfig(oldVal, newVal)) {
47409 return true;
47410 }
47411 } else if(Array.isArray(oldVal) && Array.isArray(newVal)) {
47412 if(oldVal.length !== newVal.length) {
47413 return true;
47414 }
47415 for(var i = 0; i < oldVal.length; i++) {
47416 if(oldVal[i] !== newVal[i]) {
47417 if(Lib.isPlainObject(oldVal[i]) && Lib.isPlainObject(newVal[i])) {
47418 if(diffConfig(oldVal[i], newVal[i])) {
47419 return true;
47420 }
47421 } else {
47422 return true;
47423 }
47424 }
47425 }
47426 } else {
47427 return true;
47428 }
47429 }
47430 }
47431}
47432
47433/**
47434 * Animate to a frame, sequence of frame, frame group, or frame definition
47435 *
47436 * @param {string id or DOM element} gd
47437 * the id or DOM element of the graph container div
47438 *
47439 * @param {string or object or array of strings or array of objects} frameOrGroupNameOrFrameList
47440 * a single frame, array of frames, or group to which to animate. The intent is
47441 * inferred by the type of the input. Valid inputs are:
47442 *
47443 * - string, e.g. 'groupname': animate all frames of a given `group` in the order
47444 * in which they are defined via `Plotly.addFrames`.
47445 *
47446 * - array of strings, e.g. ['frame1', frame2']: a list of frames by name to which
47447 * to animate in sequence
47448 *
47449 * - object: {data: ...}: a frame definition to which to animate. The frame is not
47450 * and does not need to be added via `Plotly.addFrames`. It may contain any of
47451 * the properties of a frame, including `data`, `layout`, and `traces`. The
47452 * frame is used as provided and does not use the `baseframe` property.
47453 *
47454 * - array of objects, e.g. [{data: ...}, {data: ...}]: a list of frame objects,
47455 * each following the same rules as a single `object`.
47456 *
47457 * @param {object} animationOpts
47458 * configuration for the animation
47459 */
47460function animate(gd, frameOrGroupNameOrFrameList, animationOpts) {
47461 gd = Lib.getGraphDiv(gd);
47462
47463 if(!Lib.isPlotDiv(gd)) {
47464 throw new Error(
47465 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
47466 'to create a plot before animating it. For more details, see ' +
47467 'https://plotly.com/javascript/animations/'
47468 );
47469 }
47470
47471 var trans = gd._transitionData;
47472
47473 // This is the queue of frames that will be animated as soon as possible. They
47474 // are popped immediately upon the *start* of a transition:
47475 if(!trans._frameQueue) {
47476 trans._frameQueue = [];
47477 }
47478
47479 animationOpts = Plots.supplyAnimationDefaults(animationOpts);
47480 var transitionOpts = animationOpts.transition;
47481 var frameOpts = animationOpts.frame;
47482
47483 // Since frames are popped immediately, an empty queue only means all frames have
47484 // *started* to transition, not that the animation is complete. To solve that,
47485 // track a separate counter that increments at the same time as frames are added
47486 // to the queue, but decrements only when the transition is complete.
47487 if(trans._frameWaitingCnt === undefined) {
47488 trans._frameWaitingCnt = 0;
47489 }
47490
47491 function getTransitionOpts(i) {
47492 if(Array.isArray(transitionOpts)) {
47493 if(i >= transitionOpts.length) {
47494 return transitionOpts[0];
47495 } else {
47496 return transitionOpts[i];
47497 }
47498 } else {
47499 return transitionOpts;
47500 }
47501 }
47502
47503 function getFrameOpts(i) {
47504 if(Array.isArray(frameOpts)) {
47505 if(i >= frameOpts.length) {
47506 return frameOpts[0];
47507 } else {
47508 return frameOpts[i];
47509 }
47510 } else {
47511 return frameOpts;
47512 }
47513 }
47514
47515 // Execute a callback after the wrapper function has been called n times.
47516 // This is used to defer the resolution until a transition has resovled *and*
47517 // the frame has completed. If it's not done this way, then we get a race
47518 // condition in which the animation might resolve before a transition is complete
47519 // or vice versa.
47520 function callbackOnNthTime(cb, n) {
47521 var cnt = 0;
47522 return function() {
47523 if(cb && ++cnt === n) {
47524 return cb();
47525 }
47526 };
47527 }
47528
47529 return new Promise(function(resolve, reject) {
47530 function discardExistingFrames() {
47531 if(trans._frameQueue.length === 0) {
47532 return;
47533 }
47534
47535 while(trans._frameQueue.length) {
47536 var next = trans._frameQueue.pop();
47537 if(next.onInterrupt) {
47538 next.onInterrupt();
47539 }
47540 }
47541
47542 gd.emit('plotly_animationinterrupted', []);
47543 }
47544
47545 function queueFrames(frameList) {
47546 if(frameList.length === 0) return;
47547
47548 for(var i = 0; i < frameList.length; i++) {
47549 var computedFrame;
47550
47551 if(frameList[i].type === 'byname') {
47552 // If it's a named frame, compute it:
47553 computedFrame = Plots.computeFrame(gd, frameList[i].name);
47554 } else {
47555 // Otherwise we must have been given a simple object, so treat
47556 // the input itself as the computed frame.
47557 computedFrame = frameList[i].data;
47558 }
47559
47560 var frameOpts = getFrameOpts(i);
47561 var transitionOpts = getTransitionOpts(i);
47562
47563 // It doesn't make much sense for the transition duration to be greater than
47564 // the frame duration, so limit it:
47565 transitionOpts.duration = Math.min(transitionOpts.duration, frameOpts.duration);
47566
47567 var nextFrame = {
47568 frame: computedFrame,
47569 name: frameList[i].name,
47570 frameOpts: frameOpts,
47571 transitionOpts: transitionOpts,
47572 };
47573 if(i === frameList.length - 1) {
47574 // The last frame in this .animate call stores the promise resolve
47575 // and reject callbacks. This is how we ensure that the animation
47576 // loop (which may exist as a result of a *different* .animate call)
47577 // still resolves or rejecdts this .animate call's promise. once it's
47578 // complete.
47579 nextFrame.onComplete = callbackOnNthTime(resolve, 2);
47580 nextFrame.onInterrupt = reject;
47581 }
47582
47583 trans._frameQueue.push(nextFrame);
47584 }
47585
47586 // Set it as never having transitioned to a frame. This will cause the animation
47587 // loop to immediately transition to the next frame (which, for immediate mode,
47588 // is the first frame in the list since all others would have been discarded
47589 // below)
47590 if(animationOpts.mode === 'immediate') {
47591 trans._lastFrameAt = -Infinity;
47592 }
47593
47594 // Only it's not already running, start a RAF loop. This could be avoided in the
47595 // case that there's only one frame, but it significantly complicated the logic
47596 // and only sped things up by about 5% or so for a lorenz attractor simulation.
47597 // It would be a fine thing to implement, but the benefit of that optimization
47598 // doesn't seem worth the extra complexity.
47599 if(!trans._animationRaf) {
47600 beginAnimationLoop();
47601 }
47602 }
47603
47604 function stopAnimationLoop() {
47605 gd.emit('plotly_animated');
47606
47607 // Be sure to unset also since it's how we know whether a loop is already running:
47608 window.cancelAnimationFrame(trans._animationRaf);
47609 trans._animationRaf = null;
47610 }
47611
47612 function nextFrame() {
47613 if(trans._currentFrame && trans._currentFrame.onComplete) {
47614 // Execute the callback and unset it to ensure it doesn't
47615 // accidentally get called twice
47616 trans._currentFrame.onComplete();
47617 }
47618
47619 var newFrame = trans._currentFrame = trans._frameQueue.shift();
47620
47621 if(newFrame) {
47622 // Since it's sometimes necessary to do deep digging into frame data,
47623 // we'll consider it not 100% impossible for nulls or numbers to sneak through,
47624 // so check when casting the name, just to be absolutely certain:
47625 var stringName = newFrame.name ? newFrame.name.toString() : null;
47626 gd._fullLayout._currentFrame = stringName;
47627
47628 trans._lastFrameAt = Date.now();
47629 trans._timeToNext = newFrame.frameOpts.duration;
47630
47631 // This is simply called and it's left to .transition to decide how to manage
47632 // interrupting current transitions. That means we don't need to worry about
47633 // how it resolves or what happens after this:
47634 Plots.transition(gd,
47635 newFrame.frame.data,
47636 newFrame.frame.layout,
47637 helpers.coerceTraceIndices(gd, newFrame.frame.traces),
47638 newFrame.frameOpts,
47639 newFrame.transitionOpts
47640 ).then(function() {
47641 if(newFrame.onComplete) {
47642 newFrame.onComplete();
47643 }
47644 });
47645
47646 gd.emit('plotly_animatingframe', {
47647 name: stringName,
47648 frame: newFrame.frame,
47649 animation: {
47650 frame: newFrame.frameOpts,
47651 transition: newFrame.transitionOpts,
47652 }
47653 });
47654 } else {
47655 // If there are no more frames, then stop the RAF loop:
47656 stopAnimationLoop();
47657 }
47658 }
47659
47660 function beginAnimationLoop() {
47661 gd.emit('plotly_animating');
47662
47663 // If no timer is running, then set last frame = long ago so that the next
47664 // frame is immediately transitioned:
47665 trans._lastFrameAt = -Infinity;
47666 trans._timeToNext = 0;
47667 trans._runningTransitions = 0;
47668 trans._currentFrame = null;
47669
47670 var doFrame = function() {
47671 // This *must* be requested before nextFrame since nextFrame may decide
47672 // to cancel it if there's nothing more to animated:
47673 trans._animationRaf = window.requestAnimationFrame(doFrame);
47674
47675 // Check if we're ready for a new frame:
47676 if(Date.now() - trans._lastFrameAt > trans._timeToNext) {
47677 nextFrame();
47678 }
47679 };
47680
47681 doFrame();
47682 }
47683
47684 // This is an animate-local counter that helps match up option input list
47685 // items with the particular frame.
47686 var configCounter = 0;
47687 function setTransitionConfig(frame) {
47688 if(Array.isArray(transitionOpts)) {
47689 if(configCounter >= transitionOpts.length) {
47690 frame.transitionOpts = transitionOpts[configCounter];
47691 } else {
47692 frame.transitionOpts = transitionOpts[0];
47693 }
47694 } else {
47695 frame.transitionOpts = transitionOpts;
47696 }
47697 configCounter++;
47698 return frame;
47699 }
47700
47701 // Disambiguate what's sort of frames have been received
47702 var i, frame;
47703 var frameList = [];
47704 var allFrames = frameOrGroupNameOrFrameList === undefined || frameOrGroupNameOrFrameList === null;
47705 var isFrameArray = Array.isArray(frameOrGroupNameOrFrameList);
47706 var isSingleFrame = !allFrames && !isFrameArray && Lib.isPlainObject(frameOrGroupNameOrFrameList);
47707
47708 if(isSingleFrame) {
47709 // In this case, a simple object has been passed to animate.
47710 frameList.push({
47711 type: 'object',
47712 data: setTransitionConfig(Lib.extendFlat({}, frameOrGroupNameOrFrameList))
47713 });
47714 } else if(allFrames || ['string', 'number'].indexOf(typeof frameOrGroupNameOrFrameList) !== -1) {
47715 // In this case, null or undefined has been passed so that we want to
47716 // animate *all* currently defined frames
47717 for(i = 0; i < trans._frames.length; i++) {
47718 frame = trans._frames[i];
47719
47720 if(!frame) continue;
47721
47722 if(allFrames || String(frame.group) === String(frameOrGroupNameOrFrameList)) {
47723 frameList.push({
47724 type: 'byname',
47725 name: String(frame.name),
47726 data: setTransitionConfig({name: frame.name})
47727 });
47728 }
47729 }
47730 } else if(isFrameArray) {
47731 for(i = 0; i < frameOrGroupNameOrFrameList.length; i++) {
47732 var frameOrName = frameOrGroupNameOrFrameList[i];
47733 if(['number', 'string'].indexOf(typeof frameOrName) !== -1) {
47734 frameOrName = String(frameOrName);
47735 // In this case, there's an array and this frame is a string name:
47736 frameList.push({
47737 type: 'byname',
47738 name: frameOrName,
47739 data: setTransitionConfig({name: frameOrName})
47740 });
47741 } else if(Lib.isPlainObject(frameOrName)) {
47742 frameList.push({
47743 type: 'object',
47744 data: setTransitionConfig(Lib.extendFlat({}, frameOrName))
47745 });
47746 }
47747 }
47748 }
47749
47750 // Verify that all of these frames actually exist; return and reject if not:
47751 for(i = 0; i < frameList.length; i++) {
47752 frame = frameList[i];
47753 if(frame.type === 'byname' && !trans._frameHash[frame.data.name]) {
47754 Lib.warn('animate failure: frame not found: "' + frame.data.name + '"');
47755 reject();
47756 return;
47757 }
47758 }
47759
47760 // If the mode is either next or immediate, then all currently queued frames must
47761 // be dumped and the corresponding .animate promises rejected.
47762 if(['next', 'immediate'].indexOf(animationOpts.mode) !== -1) {
47763 discardExistingFrames();
47764 }
47765
47766 if(animationOpts.direction === 'reverse') {
47767 frameList.reverse();
47768 }
47769
47770 var currentFrame = gd._fullLayout._currentFrame;
47771 if(currentFrame && animationOpts.fromcurrent) {
47772 var idx = -1;
47773 for(i = 0; i < frameList.length; i++) {
47774 frame = frameList[i];
47775 if(frame.type === 'byname' && frame.name === currentFrame) {
47776 idx = i;
47777 break;
47778 }
47779 }
47780
47781 if(idx > 0 && idx < frameList.length - 1) {
47782 var filteredFrameList = [];
47783 for(i = 0; i < frameList.length; i++) {
47784 frame = frameList[i];
47785 if(frameList[i].type !== 'byname' || i > idx) {
47786 filteredFrameList.push(frame);
47787 }
47788 }
47789 frameList = filteredFrameList;
47790 }
47791 }
47792
47793 if(frameList.length > 0) {
47794 queueFrames(frameList);
47795 } else {
47796 // This is the case where there were simply no frames. It's a little strange
47797 // since there's not much to do:
47798 gd.emit('plotly_animated');
47799 resolve();
47800 }
47801 });
47802}
47803
47804/**
47805 * Register new frames
47806 *
47807 * @param {string id or DOM element} gd
47808 * the id or DOM element of the graph container div
47809 *
47810 * @param {array of objects} frameList
47811 * list of frame definitions, in which each object includes any of:
47812 * - name: {string} name of frame to add
47813 * - data: {array of objects} trace data
47814 * - layout {object} layout definition
47815 * - traces {array} trace indices
47816 * - baseframe {string} name of frame from which this frame gets defaults
47817 *
47818 * @param {array of integers} indices
47819 * an array of integer indices matching the respective frames in `frameList`. If not
47820 * provided, an index will be provided in serial order. If already used, the frame
47821 * will be overwritten.
47822 */
47823function addFrames(gd, frameList, indices) {
47824 gd = Lib.getGraphDiv(gd);
47825
47826 if(frameList === null || frameList === undefined) {
47827 return Promise.resolve();
47828 }
47829
47830 if(!Lib.isPlotDiv(gd)) {
47831 throw new Error(
47832 'This element is not a Plotly plot: ' + gd + '. It\'s likely that you\'ve failed ' +
47833 'to create a plot before adding frames. For more details, see ' +
47834 'https://plotly.com/javascript/animations/'
47835 );
47836 }
47837
47838 var i, frame, j, idx;
47839 var _frames = gd._transitionData._frames;
47840 var _frameHash = gd._transitionData._frameHash;
47841
47842
47843 if(!Array.isArray(frameList)) {
47844 throw new Error('addFrames failure: frameList must be an Array of frame definitions' + frameList);
47845 }
47846
47847 // Create a sorted list of insertions since we run into lots of problems if these
47848 // aren't in ascending order of index:
47849 //
47850 // Strictly for sorting. Make sure this is guaranteed to never collide with any
47851 // already-exisisting indices:
47852 var bigIndex = _frames.length + frameList.length * 2;
47853
47854 var insertions = [];
47855 var _frameHashLocal = {};
47856 for(i = frameList.length - 1; i >= 0; i--) {
47857 if(!Lib.isPlainObject(frameList[i])) continue;
47858
47859 // The entire logic for checking for this type of name collision can be removed once we migrate to ES6 and
47860 // use a Map instead of an Object instance, as Map keys aren't converted to strings.
47861 var lookupName = frameList[i].name;
47862 var name = (_frameHash[lookupName] || _frameHashLocal[lookupName] || {}).name;
47863 var newName = frameList[i].name;
47864 var collisionPresent = _frameHash[name] || _frameHashLocal[name];
47865
47866 if(name && newName && typeof newName === 'number' && collisionPresent && numericNameWarningCount < numericNameWarningCountLimit) {
47867 numericNameWarningCount++;
47868
47869 Lib.warn('addFrames: overwriting frame "' + (_frameHash[name] || _frameHashLocal[name]).name +
47870 '" with a frame whose name of type "number" also equates to "' +
47871 name + '". This is valid but may potentially lead to unexpected ' +
47872 'behavior since all plotly.js frame names are stored internally ' +
47873 'as strings.');
47874
47875 if(numericNameWarningCount === numericNameWarningCountLimit) {
47876 Lib.warn('addFrames: This API call has yielded too many of these warnings. ' +
47877 'For the rest of this call, further warnings about numeric frame ' +
47878 'names will be suppressed.');
47879 }
47880 }
47881
47882 _frameHashLocal[lookupName] = {name: lookupName};
47883
47884 insertions.push({
47885 frame: Plots.supplyFrameDefaults(frameList[i]),
47886 index: (indices && indices[i] !== undefined && indices[i] !== null) ? indices[i] : bigIndex + i
47887 });
47888 }
47889
47890 // Sort this, taking note that undefined insertions end up at the end:
47891 insertions.sort(function(a, b) {
47892 if(a.index > b.index) return -1;
47893 if(a.index < b.index) return 1;
47894 return 0;
47895 });
47896
47897 var ops = [];
47898 var revops = [];
47899 var frameCount = _frames.length;
47900
47901 for(i = insertions.length - 1; i >= 0; i--) {
47902 frame = insertions[i].frame;
47903
47904 if(typeof frame.name === 'number') {
47905 Lib.warn('Warning: addFrames accepts frames with numeric names, but the numbers are' +
47906 'implicitly cast to strings');
47907 }
47908
47909 if(!frame.name) {
47910 // Repeatedly assign a default name, incrementing the counter each time until
47911 // we get a name that's not in the hashed lookup table:
47912 while(_frameHash[(frame.name = 'frame ' + gd._transitionData._counter++)]);
47913 }
47914
47915 if(_frameHash[frame.name]) {
47916 // If frame is present, overwrite its definition:
47917 for(j = 0; j < _frames.length; j++) {
47918 if((_frames[j] || {}).name === frame.name) break;
47919 }
47920 ops.push({type: 'replace', index: j, value: frame});
47921 revops.unshift({type: 'replace', index: j, value: _frames[j]});
47922 } else {
47923 // Otherwise insert it at the end of the list:
47924 idx = Math.max(0, Math.min(insertions[i].index, frameCount));
47925
47926 ops.push({type: 'insert', index: idx, value: frame});
47927 revops.unshift({type: 'delete', index: idx});
47928 frameCount++;
47929 }
47930 }
47931
47932 var undoFunc = Plots.modifyFrames;
47933 var redoFunc = Plots.modifyFrames;
47934 var undoArgs = [gd, revops];
47935 var redoArgs = [gd, ops];
47936
47937 if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
47938
47939 return Plots.modifyFrames(gd, ops);
47940}
47941
47942/**
47943 * Delete frame
47944 *
47945 * @param {string id or DOM element} gd
47946 * the id or DOM element of the graph container div
47947 *
47948 * @param {array of integers} frameList
47949 * list of integer indices of frames to be deleted
47950 */
47951function deleteFrames(gd, frameList) {
47952 gd = Lib.getGraphDiv(gd);
47953
47954 if(!Lib.isPlotDiv(gd)) {
47955 throw new Error('This element is not a Plotly plot: ' + gd);
47956 }
47957
47958 var i, idx;
47959 var _frames = gd._transitionData._frames;
47960 var ops = [];
47961 var revops = [];
47962
47963 if(!frameList) {
47964 frameList = [];
47965 for(i = 0; i < _frames.length; i++) {
47966 frameList.push(i);
47967 }
47968 }
47969
47970 frameList = frameList.slice();
47971 frameList.sort();
47972
47973 for(i = frameList.length - 1; i >= 0; i--) {
47974 idx = frameList[i];
47975 ops.push({type: 'delete', index: idx});
47976 revops.unshift({type: 'insert', index: idx, value: _frames[idx]});
47977 }
47978
47979 var undoFunc = Plots.modifyFrames;
47980 var redoFunc = Plots.modifyFrames;
47981 var undoArgs = [gd, revops];
47982 var redoArgs = [gd, ops];
47983
47984 if(Queue) Queue.add(gd, undoFunc, undoArgs, redoFunc, redoArgs);
47985
47986 return Plots.modifyFrames(gd, ops);
47987}
47988
47989/**
47990 * Purge a graph container div back to its initial pre-Plotly.plot state
47991 *
47992 * @param {string id or DOM element} gd
47993 * the id or DOM element of the graph container div
47994 */
47995function purge(gd) {
47996 gd = Lib.getGraphDiv(gd);
47997
47998 var fullLayout = gd._fullLayout || {};
47999 var fullData = gd._fullData || [];
48000
48001 // remove gl contexts
48002 Plots.cleanPlot([], {}, fullData, fullLayout);
48003
48004 // purge properties
48005 Plots.purge(gd);
48006
48007 // purge event emitter methods
48008 Events.purge(gd);
48009
48010 // remove plot container
48011 if(fullLayout._container) fullLayout._container.remove();
48012
48013 // in contrast to Plotly.Plots.purge which does NOT clear _context!
48014 delete gd._context;
48015
48016 return gd;
48017}
48018
48019// -------------------------------------------------------
48020// makePlotFramework: Create the plot container and axes
48021// -------------------------------------------------------
48022function makePlotFramework(gd) {
48023 var gd3 = d3.select(gd);
48024 var fullLayout = gd._fullLayout;
48025
48026 // Plot container
48027 fullLayout._container = gd3.selectAll('.plot-container').data([0]);
48028 fullLayout._container.enter().insert('div', ':first-child')
48029 .classed('plot-container', true)
48030 .classed('plotly', true);
48031
48032 // Make the svg container
48033 fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]);
48034 fullLayout._paperdiv.enter().append('div')
48035 .classed('svg-container', true)
48036 .style('position', 'relative');
48037
48038 // Make the graph containers
48039 // start fresh each time we get here, so we know the order comes out
48040 // right, rather than enter/exit which can muck up the order
48041 // TODO: sort out all the ordering so we don't have to
48042 // explicitly delete anything
48043 // FIXME: parcoords reuses this object, not the best pattern
48044 fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container')
48045 .data([{}]);
48046
48047 fullLayout._glcontainer.enter().append('div')
48048 .classed('gl-container', true);
48049
48050 fullLayout._paperdiv.selectAll('.main-svg').remove();
48051 fullLayout._paperdiv.select('.modebar-container').remove();
48052
48053 fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child')
48054 .classed('main-svg', true);
48055
48056 fullLayout._toppaper = fullLayout._paperdiv.append('svg')
48057 .classed('main-svg', true);
48058
48059 fullLayout._modebardiv = fullLayout._paperdiv.append('div');
48060
48061 fullLayout._hoverpaper = fullLayout._paperdiv.append('svg')
48062 .classed('main-svg', true);
48063
48064 if(!fullLayout._uid) {
48065 var otherUids = {};
48066 d3.selectAll('defs').each(function() {
48067 if(this.id) otherUids[this.id.split('-')[1]] = 1;
48068 });
48069 fullLayout._uid = Lib.randstr(otherUids);
48070 }
48071
48072 fullLayout._paperdiv.selectAll('.main-svg')
48073 .attr(xmlnsNamespaces.svgAttrs);
48074
48075 fullLayout._defs = fullLayout._paper.append('defs')
48076 .attr('id', 'defs-' + fullLayout._uid);
48077
48078 fullLayout._clips = fullLayout._defs.append('g')
48079 .classed('clips', true);
48080
48081 fullLayout._topdefs = fullLayout._toppaper.append('defs')
48082 .attr('id', 'topdefs-' + fullLayout._uid);
48083
48084 fullLayout._topclips = fullLayout._topdefs.append('g')
48085 .classed('clips', true);
48086
48087 fullLayout._bgLayer = fullLayout._paper.append('g')
48088 .classed('bglayer', true);
48089
48090 fullLayout._draggers = fullLayout._paper.append('g')
48091 .classed('draglayer', true);
48092
48093 // lower shape/image layer - note that this is behind
48094 // all subplots data/grids but above the backgrounds
48095 // except inset subplots, whose backgrounds are drawn
48096 // inside their own group so that they appear above
48097 // the data for the main subplot
48098 // lower shapes and images which are fully referenced to
48099 // a subplot still get drawn within the subplot's group
48100 // so they will work correctly on insets
48101 var layerBelow = fullLayout._paper.append('g')
48102 .classed('layer-below', true);
48103 fullLayout._imageLowerLayer = layerBelow.append('g')
48104 .classed('imagelayer', true);
48105 fullLayout._shapeLowerLayer = layerBelow.append('g')
48106 .classed('shapelayer', true);
48107
48108 // single cartesian layer for the whole plot
48109 fullLayout._cartesianlayer = fullLayout._paper.append('g').classed('cartesianlayer', true);
48110
48111 // single polar layer for the whole plot
48112 fullLayout._polarlayer = fullLayout._paper.append('g').classed('polarlayer', true);
48113
48114 // single ternary layer for the whole plot
48115 fullLayout._ternarylayer = fullLayout._paper.append('g').classed('ternarylayer', true);
48116
48117 // single geo layer for the whole plot
48118 fullLayout._geolayer = fullLayout._paper.append('g').classed('geolayer', true);
48119
48120 // single funnelarea layer for the whole plot
48121 fullLayout._funnelarealayer = fullLayout._paper.append('g').classed('funnelarealayer', true);
48122
48123 // single pie layer for the whole plot
48124 fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true);
48125
48126 // single treemap layer for the whole plot
48127 fullLayout._treemaplayer = fullLayout._paper.append('g').classed('treemaplayer', true);
48128
48129 // single sunburst layer for the whole plot
48130 fullLayout._sunburstlayer = fullLayout._paper.append('g').classed('sunburstlayer', true);
48131
48132 // single indicator layer for the whole plot
48133 fullLayout._indicatorlayer = fullLayout._toppaper.append('g').classed('indicatorlayer', true);
48134
48135 // fill in image server scrape-svg
48136 fullLayout._glimages = fullLayout._paper.append('g').classed('glimages', true);
48137
48138 // lastly upper shapes, info (legend, annotations) and hover layers go on top
48139 // these are in a different svg element normally, but get collapsed into a single
48140 // svg when exporting (after inserting 3D)
48141 // upper shapes/images are only those drawn above the whole plot, including subplots
48142 var layerAbove = fullLayout._toppaper.append('g')
48143 .classed('layer-above', true);
48144 fullLayout._imageUpperLayer = layerAbove.append('g')
48145 .classed('imagelayer', true);
48146 fullLayout._shapeUpperLayer = layerAbove.append('g')
48147 .classed('shapelayer', true);
48148
48149 fullLayout._infolayer = fullLayout._toppaper.append('g').classed('infolayer', true);
48150 fullLayout._menulayer = fullLayout._toppaper.append('g').classed('menulayer', true);
48151 fullLayout._zoomlayer = fullLayout._toppaper.append('g').classed('zoomlayer', true);
48152 fullLayout._hoverlayer = fullLayout._hoverpaper.append('g').classed('hoverlayer', true);
48153
48154 // Make the modebar container
48155 fullLayout._modebardiv
48156 .classed('modebar-container', true)
48157 .style('position', 'absolute')
48158 .style('top', '0px')
48159 .style('right', '0px');
48160
48161 gd.emit('plotly_framework');
48162}
48163
48164exports.animate = animate;
48165exports.addFrames = addFrames;
48166exports.deleteFrames = deleteFrames;
48167
48168exports.addTraces = addTraces;
48169exports.deleteTraces = deleteTraces;
48170exports.extendTraces = extendTraces;
48171exports.moveTraces = moveTraces;
48172exports.prependTraces = prependTraces;
48173
48174exports.newPlot = newPlot;
48175exports.plot = plot;
48176exports.purge = purge;
48177
48178exports.react = react;
48179exports.redraw = redraw;
48180exports.relayout = relayout;
48181exports.restyle = restyle;
48182
48183exports.setPlotConfig = setPlotConfig;
48184
48185exports.update = update;
48186
48187exports._guiRelayout = guiEdit(relayout);
48188exports._guiRestyle = guiEdit(restyle);
48189exports._guiUpdate = guiEdit(update);
48190
48191exports._storeDirectGUIEdit = _storeDirectGUIEdit;
48192
48193},{"../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){
48194/**
48195* Copyright 2012-2020, Plotly, Inc.
48196* All rights reserved.
48197*
48198* This source code is licensed under the MIT license found in the
48199* LICENSE file in the root directory of this source tree.
48200*/
48201
48202'use strict';
48203
48204/**
48205 * This will be transferred over to gd and overridden by
48206 * config args to Plotly.plot.
48207 *
48208 * The defaults are the appropriate settings for plotly.js,
48209 * so we get the right experience without any config argument.
48210 *
48211 * N.B. the config options are not coerced using Lib.coerce so keys
48212 * like `valType` and `values` are only set for documentation purposes
48213 * at the moment.
48214 */
48215
48216var configAttributes = {
48217 staticPlot: {
48218 valType: 'boolean',
48219 dflt: false,
48220
48221 },
48222
48223 plotlyServerURL: {
48224 valType: 'string',
48225 dflt: '',
48226
48227 },
48228
48229 editable: {
48230 valType: 'boolean',
48231 dflt: false,
48232
48233 },
48234 edits: {
48235 annotationPosition: {
48236 valType: 'boolean',
48237 dflt: false,
48238
48239 },
48240 annotationTail: {
48241 valType: 'boolean',
48242 dflt: false,
48243
48244 },
48245 annotationText: {
48246 valType: 'boolean',
48247 dflt: false,
48248
48249 },
48250 axisTitleText: {
48251 valType: 'boolean',
48252 dflt: false,
48253
48254 },
48255 colorbarPosition: {
48256 valType: 'boolean',
48257 dflt: false,
48258
48259 },
48260 colorbarTitleText: {
48261 valType: 'boolean',
48262 dflt: false,
48263
48264 },
48265 legendPosition: {
48266 valType: 'boolean',
48267 dflt: false,
48268
48269 },
48270 legendText: {
48271 valType: 'boolean',
48272 dflt: false,
48273
48274 },
48275 shapePosition: {
48276 valType: 'boolean',
48277 dflt: false,
48278
48279 },
48280 titleText: {
48281 valType: 'boolean',
48282 dflt: false,
48283
48284 }
48285 },
48286
48287 autosizable: {
48288 valType: 'boolean',
48289 dflt: false,
48290
48291 },
48292 responsive: {
48293 valType: 'boolean',
48294 dflt: false,
48295
48296 },
48297 fillFrame: {
48298 valType: 'boolean',
48299 dflt: false,
48300
48301 },
48302 frameMargins: {
48303 valType: 'number',
48304 dflt: 0,
48305 min: 0,
48306 max: 0.5,
48307
48308 },
48309
48310 scrollZoom: {
48311 valType: 'flaglist',
48312 flags: ['cartesian', 'gl3d', 'geo', 'mapbox'],
48313 extras: [true, false],
48314 dflt: 'gl3d+geo+mapbox',
48315
48316 },
48317 doubleClick: {
48318 valType: 'enumerated',
48319 values: [false, 'reset', 'autosize', 'reset+autosize'],
48320 dflt: 'reset+autosize',
48321
48322 },
48323 doubleClickDelay: {
48324 valType: 'number',
48325 dflt: 300,
48326 min: 0,
48327
48328 },
48329
48330 showAxisDragHandles: {
48331 valType: 'boolean',
48332 dflt: true,
48333
48334 },
48335 showAxisRangeEntryBoxes: {
48336 valType: 'boolean',
48337 dflt: true,
48338
48339 },
48340
48341 showTips: {
48342 valType: 'boolean',
48343 dflt: true,
48344
48345 },
48346
48347 showLink: {
48348 valType: 'boolean',
48349 dflt: false,
48350
48351 },
48352 linkText: {
48353 valType: 'string',
48354 dflt: 'Edit chart',
48355 noBlank: true,
48356
48357 },
48358 sendData: {
48359 valType: 'boolean',
48360 dflt: true,
48361
48362 },
48363 showSources: {
48364 valType: 'any',
48365 dflt: false,
48366
48367 },
48368
48369 displayModeBar: {
48370 valType: 'enumerated',
48371 values: ['hover', true, false],
48372 dflt: 'hover',
48373
48374 },
48375 showSendToCloud: {
48376 valType: 'boolean',
48377 dflt: false,
48378
48379 },
48380 showEditInChartStudio: {
48381 valType: 'boolean',
48382 dflt: false,
48383
48384 },
48385 modeBarButtonsToRemove: {
48386 valType: 'any',
48387 dflt: [],
48388
48389 },
48390 modeBarButtonsToAdd: {
48391 valType: 'any',
48392 dflt: [],
48393
48394 },
48395 modeBarButtons: {
48396 valType: 'any',
48397 dflt: false,
48398
48399 },
48400 toImageButtonOptions: {
48401 valType: 'any',
48402 dflt: {},
48403
48404 },
48405 displaylogo: {
48406 valType: 'boolean',
48407 dflt: true,
48408
48409 },
48410 watermark: {
48411 valType: 'boolean',
48412 dflt: false,
48413
48414 },
48415
48416 plotGlPixelRatio: {
48417 valType: 'number',
48418 dflt: 2,
48419 min: 1,
48420 max: 4,
48421
48422 },
48423
48424 setBackground: {
48425 valType: 'any',
48426 dflt: 'transparent',
48427
48428 },
48429
48430 topojsonURL: {
48431 valType: 'string',
48432 noBlank: true,
48433 dflt: 'https://cdn.plot.ly/',
48434
48435 },
48436
48437 mapboxAccessToken: {
48438 valType: 'string',
48439 dflt: null,
48440
48441 },
48442
48443 logging: {
48444 valType: 'integer',
48445 min: 0,
48446 max: 2,
48447 dflt: 1,
48448
48449 },
48450
48451 notifyOnLogging: {
48452 valType: 'integer',
48453 min: 0,
48454 max: 2,
48455 dflt: 0,
48456
48457 },
48458
48459 queueLength: {
48460 valType: 'integer',
48461 min: 0,
48462 dflt: 0,
48463
48464 },
48465
48466 globalTransforms: {
48467 valType: 'any',
48468 dflt: [],
48469
48470 },
48471
48472 locale: {
48473 valType: 'string',
48474 dflt: 'en-US',
48475
48476 },
48477
48478 locales: {
48479 valType: 'any',
48480 dflt: {},
48481
48482 }
48483};
48484
48485var dfltConfig = {};
48486
48487function crawl(src, target) {
48488 for(var k in src) {
48489 var obj = src[k];
48490 if(obj.valType) {
48491 target[k] = obj.dflt;
48492 } else {
48493 if(!target[k]) {
48494 target[k] = {};
48495 }
48496 crawl(obj, target[k]);
48497 }
48498 }
48499}
48500
48501crawl(configAttributes, dfltConfig);
48502
48503module.exports = {
48504 configAttributes: configAttributes,
48505 dfltConfig: dfltConfig
48506};
48507
48508},{}],211:[function(_dereq_,module,exports){
48509/**
48510* Copyright 2012-2020, Plotly, Inc.
48511* All rights reserved.
48512*
48513* This source code is licensed under the MIT license found in the
48514* LICENSE file in the root directory of this source tree.
48515*/
48516
48517'use strict';
48518
48519var Registry = _dereq_('../registry');
48520var Lib = _dereq_('../lib');
48521
48522var baseAttributes = _dereq_('../plots/attributes');
48523var baseLayoutAttributes = _dereq_('../plots/layout_attributes');
48524var frameAttributes = _dereq_('../plots/frame_attributes');
48525var animationAttributes = _dereq_('../plots/animation_attributes');
48526var configAttributes = _dereq_('./plot_config').configAttributes;
48527
48528// polar attributes are not part of the Registry yet
48529var polarAreaAttrs = _dereq_('../plots/polar/legacy/area_attributes');
48530var polarAxisAttrs = _dereq_('../plots/polar/legacy/axis_attributes');
48531
48532var editTypes = _dereq_('./edit_types');
48533
48534var extendFlat = Lib.extendFlat;
48535var extendDeepAll = Lib.extendDeepAll;
48536var isPlainObject = Lib.isPlainObject;
48537var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
48538var nestedProperty = Lib.nestedProperty;
48539var valObjectMeta = Lib.valObjectMeta;
48540
48541var IS_SUBPLOT_OBJ = '_isSubplotObj';
48542var IS_LINKED_TO_ARRAY = '_isLinkedToArray';
48543var ARRAY_ATTR_REGEXPS = '_arrayAttrRegexps';
48544var DEPRECATED = '_deprecated';
48545var UNDERSCORE_ATTRS = [IS_SUBPLOT_OBJ, IS_LINKED_TO_ARRAY, ARRAY_ATTR_REGEXPS, DEPRECATED];
48546
48547exports.IS_SUBPLOT_OBJ = IS_SUBPLOT_OBJ;
48548exports.IS_LINKED_TO_ARRAY = IS_LINKED_TO_ARRAY;
48549exports.DEPRECATED = DEPRECATED;
48550exports.UNDERSCORE_ATTRS = UNDERSCORE_ATTRS;
48551
48552/** Outputs the full plotly.js plot schema
48553 *
48554 * @return {object}
48555 * - defs
48556 * - traces
48557 * - layout
48558 * - transforms
48559 * - frames
48560 * - animations
48561 * - config
48562 */
48563exports.get = function() {
48564 var traces = {};
48565
48566 Registry.allTypes.concat('area').forEach(function(type) {
48567 traces[type] = getTraceAttributes(type);
48568 });
48569
48570 var transforms = {};
48571
48572 Object.keys(Registry.transformsRegistry).forEach(function(type) {
48573 transforms[type] = getTransformAttributes(type);
48574 });
48575
48576 return {
48577 defs: {
48578 valObjects: valObjectMeta,
48579 metaKeys: UNDERSCORE_ATTRS.concat(['description', 'role', 'editType', 'impliedEdits']),
48580 editType: {
48581 traces: editTypes.traces,
48582 layout: editTypes.layout
48583 },
48584 impliedEdits: {
48585
48586 }
48587 },
48588
48589 traces: traces,
48590 layout: getLayoutAttributes(),
48591
48592 transforms: transforms,
48593
48594 frames: getFramesAttributes(),
48595 animation: formatAttributes(animationAttributes),
48596
48597 config: formatAttributes(configAttributes)
48598 };
48599};
48600
48601/**
48602 * Crawl the attribute tree, recursively calling a callback function
48603 *
48604 * @param {object} attrs
48605 * The node of the attribute tree (e.g. the root) from which recursion originates
48606 * @param {Function} callback
48607 * A callback function with the signature:
48608 * @callback callback
48609 * @param {object} attr an attribute
48610 * @param {String} attrName name string
48611 * @param {object[]} attrs all the attributes
48612 * @param {Number} level the recursion level, 0 at the root
48613 * @param {String} fullAttrString full attribute name (ie 'marker.line')
48614 * @param {Number} [specifiedLevel]
48615 * The level in the tree, in order to let the callback function detect descend or backtrack,
48616 * typically unsupplied (implied 0), just used by the self-recursive call.
48617 * The necessity arises because the tree traversal is not controlled by callback return values.
48618 * The decision to not use callback return values for controlling tree pruning arose from
48619 * the goal of keeping the crawler backwards compatible. Observe that one of the pruning conditions
48620 * precedes the callback call.
48621 * @param {string} [attrString]
48622 * the path to the current attribute, as an attribute string (ie 'marker.line')
48623 * typically unsupplied, but you may supply it if you want to disambiguate which attrs tree you
48624 * are starting from
48625 *
48626 * @return {object} transformOut
48627 * copy of transformIn that contains attribute defaults
48628 */
48629exports.crawl = function(attrs, callback, specifiedLevel, attrString) {
48630 var level = specifiedLevel || 0;
48631 attrString = attrString || '';
48632
48633 Object.keys(attrs).forEach(function(attrName) {
48634 var attr = attrs[attrName];
48635
48636 if(UNDERSCORE_ATTRS.indexOf(attrName) !== -1) return;
48637
48638 var fullAttrString = (attrString ? attrString + '.' : '') + attrName;
48639 callback(attr, attrName, attrs, level, fullAttrString);
48640
48641 if(exports.isValObject(attr)) return;
48642
48643 if(isPlainObject(attr) && attrName !== 'impliedEdits') {
48644 exports.crawl(attr, callback, level + 1, fullAttrString);
48645 }
48646 });
48647};
48648
48649/** Is object a value object (or a container object)?
48650 *
48651 * @param {object} obj
48652 * @return {boolean}
48653 * returns true for a valid value object and
48654 * false for tree nodes in the attribute hierarchy
48655 */
48656exports.isValObject = function(obj) {
48657 return obj && obj.valType !== undefined;
48658};
48659
48660/**
48661 * Find all data array attributes in a given trace object - including
48662 * `arrayOk` attributes.
48663 *
48664 * @param {object} trace
48665 * full trace object that contains a reference to `_module.attributes`
48666 *
48667 * @return {array} arrayAttributes
48668 * list of array attributes for the given trace
48669 */
48670exports.findArrayAttributes = function(trace) {
48671 var arrayAttributes = [];
48672 var stack = [];
48673 var isArrayStack = [];
48674 var baseContainer, baseAttrName;
48675
48676 function callback(attr, attrName, attrs, level) {
48677 stack = stack.slice(0, level).concat([attrName]);
48678 isArrayStack = isArrayStack.slice(0, level).concat([attr && attr._isLinkedToArray]);
48679
48680 var splittableAttr = (
48681 attr &&
48682 (attr.valType === 'data_array' || attr.arrayOk === true) &&
48683 !(stack[level - 1] === 'colorbar' && (attrName === 'ticktext' || attrName === 'tickvals'))
48684 );
48685
48686 // Manually exclude 'colorbar.tickvals' and 'colorbar.ticktext' for now
48687 // which are declared as `valType: 'data_array'` but scale independently of
48688 // the coordinate arrays.
48689 //
48690 // Down the road, we might want to add a schema field (e.g `uncorrelatedArray: true`)
48691 // to distinguish attributes of the likes.
48692
48693 if(!splittableAttr) return;
48694
48695 crawlIntoTrace(baseContainer, 0, '');
48696 }
48697
48698 function crawlIntoTrace(container, i, astrPartial) {
48699 var item = container[stack[i]];
48700 var newAstrPartial = astrPartial + stack[i];
48701 if(i === stack.length - 1) {
48702 if(isArrayOrTypedArray(item)) {
48703 arrayAttributes.push(baseAttrName + newAstrPartial);
48704 }
48705 } else {
48706 if(isArrayStack[i]) {
48707 if(Array.isArray(item)) {
48708 for(var j = 0; j < item.length; j++) {
48709 if(isPlainObject(item[j])) {
48710 crawlIntoTrace(item[j], i + 1, newAstrPartial + '[' + j + '].');
48711 }
48712 }
48713 }
48714 } else if(isPlainObject(item)) {
48715 crawlIntoTrace(item, i + 1, newAstrPartial + '.');
48716 }
48717 }
48718 }
48719
48720 baseContainer = trace;
48721 baseAttrName = '';
48722 exports.crawl(baseAttributes, callback);
48723 if(trace._module && trace._module.attributes) {
48724 exports.crawl(trace._module.attributes, callback);
48725 }
48726
48727 var transforms = trace.transforms;
48728 if(transforms) {
48729 for(var i = 0; i < transforms.length; i++) {
48730 var transform = transforms[i];
48731 var module = transform._module;
48732
48733 if(module) {
48734 baseAttrName = 'transforms[' + i + '].';
48735 baseContainer = transform;
48736
48737 exports.crawl(module.attributes, callback);
48738 }
48739 }
48740 }
48741
48742 return arrayAttributes;
48743};
48744
48745/*
48746 * Find the valObject for one attribute in an existing trace
48747 *
48748 * @param {object} trace
48749 * full trace object that contains a reference to `_module.attributes`
48750 * @param {object} parts
48751 * an array of parts, like ['transforms', 1, 'value']
48752 * typically from nestedProperty(...).parts
48753 *
48754 * @return {object|false}
48755 * the valObject for this attribute, or the last found parent
48756 * in some cases the innermost valObject will not exist, for example
48757 * `valType: 'any'` attributes where we might set a part of the attribute.
48758 * In that case, stop at the deepest valObject we *do* find.
48759 */
48760exports.getTraceValObject = function(trace, parts) {
48761 var head = parts[0];
48762 var i = 1; // index to start recursing from
48763 var moduleAttrs, valObject;
48764
48765 if(head === 'transforms') {
48766 if(parts.length === 1) {
48767 return baseAttributes.transforms;
48768 }
48769 var transforms = trace.transforms;
48770 if(!Array.isArray(transforms) || !transforms.length) return false;
48771 var tNum = parts[1];
48772 if(!isIndex(tNum) || tNum >= transforms.length) {
48773 return false;
48774 }
48775 moduleAttrs = (Registry.transformsRegistry[transforms[tNum].type] || {}).attributes;
48776 valObject = moduleAttrs && moduleAttrs[parts[2]];
48777 i = 3; // start recursing only inside the transform
48778 } else if(trace.type === 'area') {
48779 valObject = polarAreaAttrs[head];
48780 } else {
48781 // first look in the module for this trace
48782 // components have already merged their trace attributes in here
48783 var _module = trace._module;
48784 if(!_module) _module = (Registry.modules[trace.type || baseAttributes.type.dflt] || {})._module;
48785 if(!_module) return false;
48786
48787 moduleAttrs = _module.attributes;
48788 valObject = moduleAttrs && moduleAttrs[head];
48789
48790 // then look in the subplot attributes
48791 if(!valObject) {
48792 var subplotModule = _module.basePlotModule;
48793 if(subplotModule && subplotModule.attributes) {
48794 valObject = subplotModule.attributes[head];
48795 }
48796 }
48797
48798 // finally look in the global attributes
48799 if(!valObject) valObject = baseAttributes[head];
48800 }
48801
48802 return recurseIntoValObject(valObject, parts, i);
48803};
48804
48805/*
48806 * Find the valObject for one layout attribute
48807 *
48808 * @param {array} parts
48809 * an array of parts, like ['annotations', 1, 'x']
48810 * typically from nestedProperty(...).parts
48811 *
48812 * @return {object|false}
48813 * the valObject for this attribute, or the last found parent
48814 * in some cases the innermost valObject will not exist, for example
48815 * `valType: 'any'` attributes where we might set a part of the attribute.
48816 * In that case, stop at the deepest valObject we *do* find.
48817 */
48818exports.getLayoutValObject = function(fullLayout, parts) {
48819 var valObject = layoutHeadAttr(fullLayout, parts[0]);
48820
48821 return recurseIntoValObject(valObject, parts, 1);
48822};
48823
48824function layoutHeadAttr(fullLayout, head) {
48825 var i, key, _module, attributes;
48826
48827 // look for attributes of the subplot types used on the plot
48828 var basePlotModules = fullLayout._basePlotModules;
48829 if(basePlotModules) {
48830 var out;
48831 for(i = 0; i < basePlotModules.length; i++) {
48832 _module = basePlotModules[i];
48833 if(_module.attrRegex && _module.attrRegex.test(head)) {
48834 // if a module defines overrides, these take precedence
48835 // initially this is to allow gl2d different editTypes from svg cartesian
48836 if(_module.layoutAttrOverrides) return _module.layoutAttrOverrides;
48837
48838 // otherwise take the first attributes we find
48839 if(!out && _module.layoutAttributes) out = _module.layoutAttributes;
48840 }
48841
48842 // a module can also override the behavior of base (and component) module layout attrs
48843 // again see gl2d for initial use case
48844 var baseOverrides = _module.baseLayoutAttrOverrides;
48845 if(baseOverrides && head in baseOverrides) return baseOverrides[head];
48846 }
48847 if(out) return out;
48848 }
48849
48850 // look for layout attributes contributed by traces on the plot
48851 var modules = fullLayout._modules;
48852 if(modules) {
48853 for(i = 0; i < modules.length; i++) {
48854 attributes = modules[i].layoutAttributes;
48855 if(attributes && head in attributes) {
48856 return attributes[head];
48857 }
48858 }
48859 }
48860
48861 /*
48862 * Next look in components.
48863 * Components that define a schema have already merged this into
48864 * base and subplot attribute defs, so ignore these.
48865 * Others (older style) all put all their attributes
48866 * inside a container matching the module `name`
48867 * eg `attributes` (array) or `legend` (object)
48868 */
48869 for(key in Registry.componentsRegistry) {
48870 _module = Registry.componentsRegistry[key];
48871 if(_module.name === 'colorscale' && head.indexOf('coloraxis') === 0) {
48872 return _module.layoutAttributes[head];
48873 } else if(!_module.schema && (head === _module.name)) {
48874 return _module.layoutAttributes;
48875 }
48876 }
48877
48878 if(head in baseLayoutAttributes) return baseLayoutAttributes[head];
48879
48880 // Polar doesn't populate _modules or _basePlotModules
48881 // just fall back on these when the others fail
48882 if(head === 'radialaxis' || head === 'angularaxis') {
48883 return polarAxisAttrs[head];
48884 }
48885 return polarAxisAttrs.layout[head] || false;
48886}
48887
48888function recurseIntoValObject(valObject, parts, i) {
48889 if(!valObject) return false;
48890
48891 if(valObject._isLinkedToArray) {
48892 // skip array index, abort if we try to dive into an array without an index
48893 if(isIndex(parts[i])) i++;
48894 else if(i < parts.length) return false;
48895 }
48896
48897 // now recurse as far as we can. Occasionally we have an attribute
48898 // setting an internal part below what's in the schema; just return
48899 // the innermost schema item we find.
48900 for(; i < parts.length; i++) {
48901 var newValObject = valObject[parts[i]];
48902 if(isPlainObject(newValObject)) valObject = newValObject;
48903 else break;
48904
48905 if(i === parts.length - 1) break;
48906
48907 if(valObject._isLinkedToArray) {
48908 i++;
48909 if(!isIndex(parts[i])) return false;
48910 } else if(valObject.valType === 'info_array') {
48911 i++;
48912 var index = parts[i];
48913 if(!isIndex(index)) return false;
48914
48915 var items = valObject.items;
48916 if(Array.isArray(items)) {
48917 if(index >= items.length) return false;
48918 if(valObject.dimensions === 2) {
48919 i++;
48920 if(parts.length === i) return valObject;
48921 var index2 = parts[i];
48922 if(!isIndex(index2)) return false;
48923 valObject = items[index][index2];
48924 } else valObject = items[index];
48925 } else {
48926 valObject = items;
48927 }
48928 }
48929 }
48930
48931 return valObject;
48932}
48933
48934// note: this is different from Lib.isIndex, this one doesn't accept numeric
48935// strings, only actual numbers.
48936function isIndex(val) {
48937 return val === Math.round(val) && val >= 0;
48938}
48939
48940function getTraceAttributes(type) {
48941 var _module, basePlotModule;
48942
48943 if(type === 'area') {
48944 _module = { attributes: polarAreaAttrs };
48945 basePlotModule = {};
48946 } else {
48947 _module = Registry.modules[type]._module,
48948 basePlotModule = _module.basePlotModule;
48949 }
48950
48951 var attributes = {};
48952
48953 // make 'type' the first attribute in the object
48954 attributes.type = null;
48955
48956 var copyBaseAttributes = extendDeepAll({}, baseAttributes);
48957 var copyModuleAttributes = extendDeepAll({}, _module.attributes);
48958
48959 // prune global-level trace attributes that are already defined in a trace
48960 exports.crawl(copyModuleAttributes, function(attr, attrName, attrs, level, fullAttrString) {
48961 nestedProperty(copyBaseAttributes, fullAttrString).set(undefined);
48962 // Prune undefined attributes
48963 if(attr === undefined) nestedProperty(copyModuleAttributes, fullAttrString).set(undefined);
48964 });
48965
48966 // base attributes (same for all trace types)
48967 extendDeepAll(attributes, copyBaseAttributes);
48968
48969 // prune-out base attributes based on trace module categories
48970 if(Registry.traceIs(type, 'noOpacity')) {
48971 delete attributes.opacity;
48972 }
48973 if(!Registry.traceIs(type, 'showLegend')) {
48974 delete attributes.showlegend;
48975 delete attributes.legendgroup;
48976 }
48977 if(Registry.traceIs(type, 'noHover')) {
48978 delete attributes.hoverinfo;
48979 delete attributes.hoverlabel;
48980 }
48981 if(!_module.selectPoints) {
48982 delete attributes.selectedpoints;
48983 }
48984
48985 // module attributes
48986 extendDeepAll(attributes, copyModuleAttributes);
48987
48988 // subplot attributes
48989 if(basePlotModule.attributes) {
48990 extendDeepAll(attributes, basePlotModule.attributes);
48991 }
48992
48993 // 'type' gets overwritten by baseAttributes; reset it here
48994 attributes.type = type;
48995
48996 var out = {
48997 meta: _module.meta || {},
48998 categories: _module.categories || {},
48999 animatable: Boolean(_module.animatable),
49000 type: type,
49001 attributes: formatAttributes(attributes),
49002 };
49003
49004 // trace-specific layout attributes
49005 if(_module.layoutAttributes) {
49006 var layoutAttributes = {};
49007
49008 extendDeepAll(layoutAttributes, _module.layoutAttributes);
49009 out.layoutAttributes = formatAttributes(layoutAttributes);
49010 }
49011
49012 // drop anim:true in non-animatable modules
49013 if(!_module.animatable) {
49014 exports.crawl(out, function(attr) {
49015 if(exports.isValObject(attr) && 'anim' in attr) {
49016 delete attr.anim;
49017 }
49018 });
49019 }
49020
49021 return out;
49022}
49023
49024function getLayoutAttributes() {
49025 var layoutAttributes = {};
49026 var key, _module;
49027
49028 // global layout attributes
49029 extendDeepAll(layoutAttributes, baseLayoutAttributes);
49030
49031 // add base plot module layout attributes
49032 for(key in Registry.subplotsRegistry) {
49033 _module = Registry.subplotsRegistry[key];
49034
49035 if(!_module.layoutAttributes) continue;
49036
49037 if(Array.isArray(_module.attr)) {
49038 for(var i = 0; i < _module.attr.length; i++) {
49039 handleBasePlotModule(layoutAttributes, _module, _module.attr[i]);
49040 }
49041 } else {
49042 var astr = _module.attr === 'subplot' ? _module.name : _module.attr;
49043 handleBasePlotModule(layoutAttributes, _module, astr);
49044 }
49045 }
49046
49047 // polar layout attributes
49048 layoutAttributes = assignPolarLayoutAttrs(layoutAttributes);
49049
49050 // add registered components layout attributes
49051 for(key in Registry.componentsRegistry) {
49052 _module = Registry.componentsRegistry[key];
49053 var schema = _module.schema;
49054
49055 if(schema && (schema.subplots || schema.layout)) {
49056 /*
49057 * Components with defined schema have already been merged in at register time
49058 * but a few components define attributes that apply only to xaxis
49059 * not yaxis (rangeselector, rangeslider) - delete from y schema.
49060 * Note that the input attributes for xaxis/yaxis are the same object
49061 * so it's not possible to only add them to xaxis from the start.
49062 * If we ever have such asymmetry the other way, or anywhere else,
49063 * we will need to extend both this code and mergeComponentAttrsToSubplot
49064 * (which will not find yaxis only for example)
49065 */
49066 var subplots = schema.subplots;
49067 if(subplots && subplots.xaxis && !subplots.yaxis) {
49068 for(var xkey in subplots.xaxis) {
49069 delete layoutAttributes.yaxis[xkey];
49070 }
49071 }
49072 } else if(_module.name === 'colorscale') {
49073 extendDeepAll(layoutAttributes, _module.layoutAttributes);
49074 } else if(_module.layoutAttributes) {
49075 // older style without schema need to be explicitly merged in now
49076 insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name);
49077 }
49078 }
49079
49080 return {
49081 layoutAttributes: formatAttributes(layoutAttributes)
49082 };
49083}
49084
49085function getTransformAttributes(type) {
49086 var _module = Registry.transformsRegistry[type];
49087 var attributes = extendDeepAll({}, _module.attributes);
49088
49089 // add registered components transform attributes
49090 Object.keys(Registry.componentsRegistry).forEach(function(k) {
49091 var _module = Registry.componentsRegistry[k];
49092
49093 if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) {
49094 Object.keys(_module.schema.transforms[type]).forEach(function(v) {
49095 insertAttrs(attributes, _module.schema.transforms[type][v], v);
49096 });
49097 }
49098 });
49099
49100 return {
49101 attributes: formatAttributes(attributes)
49102 };
49103}
49104
49105function getFramesAttributes() {
49106 var attrs = {
49107 frames: extendDeepAll({}, frameAttributes)
49108 };
49109
49110 formatAttributes(attrs);
49111
49112 return attrs.frames;
49113}
49114
49115function formatAttributes(attrs) {
49116 mergeValTypeAndRole(attrs);
49117 formatArrayContainers(attrs);
49118 stringify(attrs);
49119
49120 return attrs;
49121}
49122
49123function mergeValTypeAndRole(attrs) {
49124 function makeSrcAttr(attrName) {
49125 return {
49126 valType: 'string',
49127
49128
49129 editType: 'none'
49130 };
49131 }
49132
49133 function callback(attr, attrName, attrs) {
49134 if(exports.isValObject(attr)) {
49135 if(attr.valType === 'data_array') {
49136 // all 'data_array' attrs have role 'data'
49137 attr.role = 'data';
49138 // all 'data_array' attrs have a corresponding 'src' attr
49139 attrs[attrName + 'src'] = makeSrcAttr(attrName);
49140 } else if(attr.arrayOk === true) {
49141 // all 'arrayOk' attrs have a corresponding 'src' attr
49142 attrs[attrName + 'src'] = makeSrcAttr(attrName);
49143 }
49144 } else if(isPlainObject(attr)) {
49145 // all attrs container objects get role 'object'
49146 attr.role = 'object';
49147 }
49148 }
49149
49150 exports.crawl(attrs, callback);
49151}
49152
49153function formatArrayContainers(attrs) {
49154 function callback(attr, attrName, attrs) {
49155 if(!attr) return;
49156
49157 var itemName = attr[IS_LINKED_TO_ARRAY];
49158
49159 if(!itemName) return;
49160
49161 delete attr[IS_LINKED_TO_ARRAY];
49162
49163 attrs[attrName] = { items: {} };
49164 attrs[attrName].items[itemName] = attr;
49165 attrs[attrName].role = 'object';
49166 }
49167
49168 exports.crawl(attrs, callback);
49169}
49170
49171// this can take around 10ms and should only be run from PlotSchema.get(),
49172// to ensure JSON.stringify(PlotSchema.get()) gives the intended result.
49173function stringify(attrs) {
49174 function walk(attr) {
49175 for(var k in attr) {
49176 if(isPlainObject(attr[k])) {
49177 walk(attr[k]);
49178 } else if(Array.isArray(attr[k])) {
49179 for(var i = 0; i < attr[k].length; i++) {
49180 walk(attr[k][i]);
49181 }
49182 } else {
49183 // as JSON.stringify(/test/) // => {}
49184 if(attr[k] instanceof RegExp) {
49185 attr[k] = attr[k].toString();
49186 }
49187 }
49188 }
49189 }
49190
49191 walk(attrs);
49192}
49193
49194function assignPolarLayoutAttrs(layoutAttributes) {
49195 extendFlat(layoutAttributes, {
49196 radialaxis: polarAxisAttrs.radialaxis,
49197 angularaxis: polarAxisAttrs.angularaxis
49198 });
49199
49200 extendFlat(layoutAttributes, polarAxisAttrs.layout);
49201
49202 return layoutAttributes;
49203}
49204
49205function handleBasePlotModule(layoutAttributes, _module, astr) {
49206 var np = nestedProperty(layoutAttributes, astr);
49207 var attrs = extendDeepAll({}, _module.layoutAttributes);
49208
49209 attrs[IS_SUBPLOT_OBJ] = true;
49210 np.set(attrs);
49211}
49212
49213function insertAttrs(baseAttrs, newAttrs, astr) {
49214 var np = nestedProperty(baseAttrs, astr);
49215
49216 np.set(extendDeepAll(np.get() || {}, newAttrs));
49217}
49218
49219},{"../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){
49220/**
49221* Copyright 2012-2020, Plotly, Inc.
49222* All rights reserved.
49223*
49224* This source code is licensed under the MIT license found in the
49225* LICENSE file in the root directory of this source tree.
49226*/
49227
49228
49229'use strict';
49230
49231var Lib = _dereq_('../lib');
49232var plotAttributes = _dereq_('../plots/attributes');
49233
49234var TEMPLATEITEMNAME = 'templateitemname';
49235
49236var templateAttrs = {
49237 name: {
49238 valType: 'string',
49239
49240 editType: 'none',
49241
49242 }
49243};
49244templateAttrs[TEMPLATEITEMNAME] = {
49245 valType: 'string',
49246
49247 editType: 'calc',
49248
49249};
49250
49251/**
49252 * templatedArray: decorate an attributes object with templating (and array)
49253 * properties.
49254 *
49255 * @param {string} name: the singular form of the array name. Sets
49256 * `_isLinkedToArray` to this, so the schema knows to treat this as an array.
49257 * @param {object} attrs: the item attributes. Since all callers are expected
49258 * to be constructing this object on the spot, we mutate it here for
49259 * performance, rather than extending a new object with it.
49260 *
49261 * @returns {object}: the decorated `attrs` object
49262 */
49263exports.templatedArray = function(name, attrs) {
49264 attrs._isLinkedToArray = name;
49265 attrs.name = templateAttrs.name;
49266 attrs[TEMPLATEITEMNAME] = templateAttrs[TEMPLATEITEMNAME];
49267 return attrs;
49268};
49269
49270/**
49271 * traceTemplater: logic for matching traces to trace templates
49272 *
49273 * @param {object} dataTemplate: collection of {traceType: [{template}, ...]}
49274 * ie each type the template applies to contains a list of template objects,
49275 * to be provided cyclically to data traces of that type.
49276 *
49277 * @returns {object}: {newTrace}, a function:
49278 * newTrace(traceIn): that takes the input traceIn, coerces its type, then
49279 * uses that type to find the next template to apply. returns the output
49280 * traceOut with template attached, ready to continue supplyDefaults.
49281 */
49282exports.traceTemplater = function(dataTemplate) {
49283 var traceCounts = {};
49284 var traceType, typeTemplates;
49285
49286 for(traceType in dataTemplate) {
49287 typeTemplates = dataTemplate[traceType];
49288 if(Array.isArray(typeTemplates) && typeTemplates.length) {
49289 traceCounts[traceType] = 0;
49290 }
49291 }
49292
49293 function newTrace(traceIn) {
49294 traceType = Lib.coerce(traceIn, {}, plotAttributes, 'type');
49295 var traceOut = {type: traceType, _template: null};
49296 if(traceType in traceCounts) {
49297 typeTemplates = dataTemplate[traceType];
49298 // cycle through traces in the template set for this type
49299 var typei = traceCounts[traceType] % typeTemplates.length;
49300 traceCounts[traceType]++;
49301 traceOut._template = typeTemplates[typei];
49302 } else {
49303 // TODO: anything we should do for types missing from the template?
49304 // try to apply some other type? Or just bail as we do here?
49305 // Actually I think yes, we should apply other types; would be nice
49306 // if all scatter* could inherit from each other, and if histogram
49307 // could inherit from bar, etc... but how to specify this? And do we
49308 // compose them, or if a type is present require it to be complete?
49309 // Actually this could apply to layout too - 3D annotations
49310 // inheriting from 2D, axes of different types inheriting from each
49311 // other...
49312 }
49313 return traceOut;
49314 }
49315
49316 return {
49317 newTrace: newTrace
49318 // TODO: function to figure out what's left & what didn't work
49319 };
49320};
49321
49322/**
49323 * newContainer: Create a new sub-container inside `container` and propagate any
49324 * applicable template to it. If there's no template, still propagates
49325 * `undefined` so relinkPrivate will not retain an old template!
49326 *
49327 * @param {object} container: the outer container, should already have _template
49328 * if there *is* a template for this plot
49329 * @param {string} name: the key of the new container to make
49330 * @param {string} baseName: if applicable, a base attribute to take the
49331 * template from, ie for xaxis3 the base would be xaxis
49332 *
49333 * @returns {object}: an object for inclusion _full*, empty except for the
49334 * appropriate template piece
49335 */
49336exports.newContainer = function(container, name, baseName) {
49337 var template = container._template;
49338 var part = template && (template[name] || (baseName && template[baseName]));
49339 if(!Lib.isPlainObject(part)) part = null;
49340
49341 var out = container[name] = {_template: part};
49342 return out;
49343};
49344
49345/**
49346 * arrayTemplater: special logic for templating both defaults and specific items
49347 * in a container array (annotations etc)
49348 *
49349 * @param {object} container: the outer container, should already have _template
49350 * if there *is* a template for this plot
49351 * @param {string} name: the name of the array to template (ie 'annotations')
49352 * will be used to find default ('annotationdefaults' object) and specific
49353 * ('annotations' array) template specs.
49354 * @param {string} inclusionAttr: the attribute determining this item's
49355 * inclusion in the output, usually 'visible' or 'enabled'
49356 *
49357 * @returns {object}: {newItem, defaultItems}, both functions:
49358 * newItem(itemIn): create an output item, bare except for the correct
49359 * template and name(s), as the base for supplyDefaults
49360 * defaultItems(): to be called after all newItem calls, return any
49361 * specific template items that have not already beeen included,
49362 * also as bare output items ready for supplyDefaults.
49363 */
49364exports.arrayTemplater = function(container, name, inclusionAttr) {
49365 var template = container._template;
49366 var defaultsTemplate = template && template[arrayDefaultKey(name)];
49367 var templateItems = template && template[name];
49368 if(!Array.isArray(templateItems) || !templateItems.length) {
49369 templateItems = [];
49370 }
49371
49372 var usedNames = {};
49373
49374 function newItem(itemIn) {
49375 // include name and templateitemname in the output object for ALL
49376 // container array items. Note: you could potentially use different
49377 // name and templateitemname, if you're using one template to make
49378 // another template. templateitemname would be the name in the original
49379 // template, and name is the new "subclassed" item name.
49380 var out = {name: itemIn.name, _input: itemIn};
49381 var templateItemName = out[TEMPLATEITEMNAME] = itemIn[TEMPLATEITEMNAME];
49382
49383 // no itemname: use the default template
49384 if(!validItemName(templateItemName)) {
49385 out._template = defaultsTemplate;
49386 return out;
49387 }
49388
49389 // look for an item matching this itemname
49390 // note these do not inherit from the default template, only the item.
49391 for(var i = 0; i < templateItems.length; i++) {
49392 var templateItem = templateItems[i];
49393 if(templateItem.name === templateItemName) {
49394 // Note: it's OK to use a template item more than once
49395 // but using it at least once will stop it from generating
49396 // a default item at the end.
49397 usedNames[templateItemName] = 1;
49398 out._template = templateItem;
49399 return out;
49400 }
49401 }
49402
49403 // Didn't find a matching template item, so since this item is intended
49404 // to only be modifications it's most likely broken. Hide it unless
49405 // it's explicitly marked visible - in which case it gets NO template,
49406 // not even the default.
49407 out[inclusionAttr] = itemIn[inclusionAttr] || false;
49408 // special falsy value we can look for in validateTemplate
49409 out._template = false;
49410 return out;
49411 }
49412
49413 function defaultItems() {
49414 var out = [];
49415 for(var i = 0; i < templateItems.length; i++) {
49416 var templateItem = templateItems[i];
49417 var name = templateItem.name;
49418 // only allow named items to be added as defaults,
49419 // and only allow each name once
49420 if(validItemName(name) && !usedNames[name]) {
49421 var outi = {
49422 _template: templateItem,
49423 name: name,
49424 _input: {_templateitemname: name}
49425 };
49426 outi[TEMPLATEITEMNAME] = templateItem[TEMPLATEITEMNAME];
49427 out.push(outi);
49428 usedNames[name] = 1;
49429 }
49430 }
49431 return out;
49432 }
49433
49434 return {
49435 newItem: newItem,
49436 defaultItems: defaultItems
49437 };
49438};
49439
49440function validItemName(name) {
49441 return name && typeof name === 'string';
49442}
49443
49444function arrayDefaultKey(name) {
49445 var lastChar = name.length - 1;
49446 if(name.charAt(lastChar) !== 's') {
49447 Lib.warn('bad argument to arrayDefaultKey: ' + name);
49448 }
49449 return name.substr(0, name.length - 1) + 'defaults';
49450}
49451exports.arrayDefaultKey = arrayDefaultKey;
49452
49453/**
49454 * arrayEditor: helper for editing array items that may have come from
49455 * template defaults (in which case they will not exist in the input yet)
49456 *
49457 * @param {object} parentIn: the input container (eg gd.layout)
49458 * @param {string} containerStr: the attribute string for the container inside
49459 * `parentIn`.
49460 * @param {object} itemOut: the _full* item (eg gd._fullLayout.annotations[0])
49461 * that we'll be editing. Assumed to have been created by `arrayTemplater`.
49462 *
49463 * @returns {object}: {modifyBase, modifyItem, getUpdateObj, applyUpdate}, all functions:
49464 * modifyBase(attr, value): Add an update that's *not* related to the item.
49465 * `attr` is the full attribute string.
49466 * modifyItem(attr, value): Add an update to the item. `attr` is just the
49467 * portion of the attribute string inside the item.
49468 * getUpdateObj(): Get the final constructed update object, to use in
49469 * `restyle` or `relayout`. Also resets the update object in case this
49470 * update was canceled.
49471 * applyUpdate(attr, value): optionally add an update `attr: value`,
49472 * then apply it to `parent` which should be the parent of `containerIn`,
49473 * ie the object to which `containerStr` is the attribute string.
49474 */
49475exports.arrayEditor = function(parentIn, containerStr, itemOut) {
49476 var lengthIn = (Lib.nestedProperty(parentIn, containerStr).get() || []).length;
49477 var index = itemOut._index;
49478 // Check that we are indeed off the end of this container.
49479 // Otherwise a devious user could put a key `_templateitemname` in their
49480 // own input and break lots of things.
49481 var templateItemName = (index >= lengthIn) && (itemOut._input || {})._templateitemname;
49482 if(templateItemName) index = lengthIn;
49483 var itemStr = containerStr + '[' + index + ']';
49484
49485 var update;
49486 function resetUpdate() {
49487 update = {};
49488 if(templateItemName) {
49489 update[itemStr] = {};
49490 update[itemStr][TEMPLATEITEMNAME] = templateItemName;
49491 }
49492 }
49493 resetUpdate();
49494
49495 function modifyBase(attr, value) {
49496 update[attr] = value;
49497 }
49498
49499 function modifyItem(attr, value) {
49500 if(templateItemName) {
49501 // we're making a new object: edit that object
49502 Lib.nestedProperty(update[itemStr], attr).set(value);
49503 } else {
49504 // we're editing an existing object: include *just* the edit
49505 update[itemStr + '.' + attr] = value;
49506 }
49507 }
49508
49509 function getUpdateObj() {
49510 var updateOut = update;
49511 resetUpdate();
49512 return updateOut;
49513 }
49514
49515 function applyUpdate(attr, value) {
49516 if(attr) modifyItem(attr, value);
49517 var updateToApply = getUpdateObj();
49518 for(var key in updateToApply) {
49519 Lib.nestedProperty(parentIn, key).set(updateToApply[key]);
49520 }
49521 }
49522
49523 return {
49524 modifyBase: modifyBase,
49525 modifyItem: modifyItem,
49526 getUpdateObj: getUpdateObj,
49527 applyUpdate: applyUpdate
49528 };
49529};
49530
49531},{"../lib":178,"../plots/attributes":219}],213:[function(_dereq_,module,exports){
49532/**
49533* Copyright 2012-2020, Plotly, Inc.
49534* All rights reserved.
49535*
49536* This source code is licensed under the MIT license found in the
49537* LICENSE file in the root directory of this source tree.
49538*/
49539
49540'use strict';
49541
49542var d3 = _dereq_('d3');
49543var Registry = _dereq_('../registry');
49544var Plots = _dereq_('../plots/plots');
49545
49546var Lib = _dereq_('../lib');
49547var clearGlCanvases = _dereq_('../lib/clear_gl_canvases');
49548
49549var Color = _dereq_('../components/color');
49550var Drawing = _dereq_('../components/drawing');
49551var Titles = _dereq_('../components/titles');
49552var ModeBar = _dereq_('../components/modebar');
49553
49554var Axes = _dereq_('../plots/cartesian/axes');
49555var alignmentConstants = _dereq_('../constants/alignment');
49556var axisConstraints = _dereq_('../plots/cartesian/constraints');
49557var enforceAxisConstraints = axisConstraints.enforce;
49558var cleanAxisConstraints = axisConstraints.clean;
49559var doAutoRange = _dereq_('../plots/cartesian/autorange').doAutoRange;
49560
49561var SVG_TEXT_ANCHOR_START = 'start';
49562var SVG_TEXT_ANCHOR_MIDDLE = 'middle';
49563var SVG_TEXT_ANCHOR_END = 'end';
49564
49565exports.layoutStyles = function(gd) {
49566 return Lib.syncOrAsync([Plots.doAutoMargin, lsInner], gd);
49567};
49568
49569function overlappingDomain(xDomain, yDomain, domains) {
49570 for(var i = 0; i < domains.length; i++) {
49571 var existingX = domains[i][0];
49572 var existingY = domains[i][1];
49573
49574 if(existingX[0] >= xDomain[1] || existingX[1] <= xDomain[0]) {
49575 continue;
49576 }
49577 if(existingY[0] < yDomain[1] && existingY[1] > yDomain[0]) {
49578 return true;
49579 }
49580 }
49581 return false;
49582}
49583
49584function lsInner(gd) {
49585 var fullLayout = gd._fullLayout;
49586 var gs = fullLayout._size;
49587 var pad = gs.p;
49588 var axList = Axes.list(gd, '', true);
49589 var i, subplot, plotinfo, ax, xa, ya;
49590
49591 fullLayout._paperdiv.style({
49592 width: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroWidth && !gd.layout.width) ? '100%' : fullLayout.width + 'px',
49593 height: (gd._context.responsive && fullLayout.autosize && !gd._context._hasZeroHeight && !gd.layout.height) ? '100%' : fullLayout.height + 'px'
49594 })
49595 .selectAll('.main-svg')
49596 .call(Drawing.setSize, fullLayout.width, fullLayout.height);
49597 gd._context.setBackground(gd, fullLayout.paper_bgcolor);
49598
49599 exports.drawMainTitle(gd);
49600 ModeBar.manage(gd);
49601
49602 // _has('cartesian') means SVG specifically, not GL2D - but GL2D
49603 // can still get here because it makes some of the SVG structure
49604 // for shared features like selections.
49605 if(!fullLayout._has('cartesian')) {
49606 return Plots.previousPromises(gd);
49607 }
49608
49609 function getLinePosition(ax, counterAx, side) {
49610 var lwHalf = ax._lw / 2;
49611
49612 if(ax._id.charAt(0) === 'x') {
49613 if(!counterAx) return gs.t + gs.h * (1 - (ax.position || 0)) + (lwHalf % 1);
49614 else if(side === 'top') return counterAx._offset - pad - lwHalf;
49615 return counterAx._offset + counterAx._length + pad + lwHalf;
49616 }
49617
49618 if(!counterAx) return gs.l + gs.w * (ax.position || 0) + (lwHalf % 1);
49619 else if(side === 'right') return counterAx._offset + counterAx._length + pad + lwHalf;
49620 return counterAx._offset - pad - lwHalf;
49621 }
49622
49623 // some preparation of axis position info
49624 for(i = 0; i < axList.length; i++) {
49625 ax = axList[i];
49626
49627 var counterAx = ax._anchorAxis;
49628
49629 // clear axis line positions, to be set in the subplot loop below
49630 ax._linepositions = {};
49631
49632 // stash crispRounded linewidth so we don't need to pass gd all over the place
49633 ax._lw = Drawing.crispRound(gd, ax.linewidth, 1);
49634
49635 // figure out the main axis line and main mirror line position.
49636 // it's easier to follow the logic if we handle these separately from
49637 // ax._linepositions, which are only used by mirror=allticks
49638 // for non-main-subplot ticks, and mirror=all(ticks)? for zero line
49639 // hiding logic
49640 ax._mainLinePosition = getLinePosition(ax, counterAx, ax.side);
49641 ax._mainMirrorPosition = (ax.mirror && counterAx) ?
49642 getLinePosition(ax, counterAx,
49643 alignmentConstants.OPPOSITE_SIDE[ax.side]) : null;
49644 }
49645
49646 // figure out which backgrounds we need to draw,
49647 // and in which layers to put them
49648 var lowerBackgroundIDs = [];
49649 var backgroundIds = [];
49650 var lowerDomains = [];
49651 // no need to draw background when paper and plot color are the same color,
49652 // activate mode just for large splom (which benefit the most from this
49653 // optimization), but this could apply to all cartesian subplots.
49654 var noNeedForBg = (
49655 Color.opacity(fullLayout.paper_bgcolor) === 1 &&
49656 Color.opacity(fullLayout.plot_bgcolor) === 1 &&
49657 fullLayout.paper_bgcolor === fullLayout.plot_bgcolor
49658 );
49659
49660 for(subplot in fullLayout._plots) {
49661 plotinfo = fullLayout._plots[subplot];
49662
49663 if(plotinfo.mainplot) {
49664 // mainplot is a reference to the main plot this one is overlaid on
49665 // so if it exists, this is an overlaid plot and we don't need to
49666 // give it its own background
49667 if(plotinfo.bg) {
49668 plotinfo.bg.remove();
49669 }
49670 plotinfo.bg = undefined;
49671 } else {
49672 var xDomain = plotinfo.xaxis.domain;
49673 var yDomain = plotinfo.yaxis.domain;
49674 var plotgroup = plotinfo.plotgroup;
49675
49676 if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
49677 var pgNode = plotgroup.node();
49678 var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
49679 pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
49680 backgroundIds.push(subplot);
49681 } else {
49682 plotgroup.select('rect.bg').remove();
49683 lowerDomains.push([xDomain, yDomain]);
49684 if(!noNeedForBg) {
49685 lowerBackgroundIDs.push(subplot);
49686 backgroundIds.push(subplot);
49687 }
49688 }
49689 }
49690 }
49691
49692 // now create all the lower-layer backgrounds at once now that
49693 // we have the list of subplots that need them
49694 var lowerBackgrounds = fullLayout._bgLayer.selectAll('.bg')
49695 .data(lowerBackgroundIDs);
49696
49697 lowerBackgrounds.enter().append('rect')
49698 .classed('bg', true);
49699
49700 lowerBackgrounds.exit().remove();
49701
49702 lowerBackgrounds.each(function(subplot) {
49703 fullLayout._plots[subplot].bg = d3.select(this);
49704 });
49705
49706 // style all backgrounds
49707 for(i = 0; i < backgroundIds.length; i++) {
49708 plotinfo = fullLayout._plots[backgroundIds[i]];
49709 xa = plotinfo.xaxis;
49710 ya = plotinfo.yaxis;
49711
49712 if(plotinfo.bg) {
49713 plotinfo.bg
49714 .call(Drawing.setRect,
49715 xa._offset - pad, ya._offset - pad,
49716 xa._length + 2 * pad, ya._length + 2 * pad)
49717 .call(Color.fill, fullLayout.plot_bgcolor)
49718 .style('stroke-width', 0);
49719 }
49720 }
49721
49722 if(!fullLayout._hasOnlyLargeSploms) {
49723 for(subplot in fullLayout._plots) {
49724 plotinfo = fullLayout._plots[subplot];
49725 xa = plotinfo.xaxis;
49726 ya = plotinfo.yaxis;
49727
49728 // Clip so that data only shows up on the plot area.
49729 var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
49730
49731 var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
49732 s.classed('plotclip', true)
49733 .append('rect');
49734 });
49735
49736 plotinfo.clipRect = plotClip.select('rect').attr({
49737 width: xa._length,
49738 height: ya._length
49739 });
49740
49741 Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);
49742
49743 var plotClipId;
49744 var layerClipId;
49745
49746 if(plotinfo._hasClipOnAxisFalse) {
49747 plotClipId = null;
49748 layerClipId = clipId;
49749 } else {
49750 plotClipId = clipId;
49751 layerClipId = null;
49752 }
49753
49754 Drawing.setClipUrl(plotinfo.plot, plotClipId, gd);
49755
49756 // stash layer clipId value (null or same as clipId)
49757 // to DRY up Drawing.setClipUrl calls on trace-module and trace layers
49758 // downstream
49759 plotinfo.layerClipId = layerClipId;
49760 }
49761 }
49762
49763 var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
49764 leftYLineWidth, rightYLineWidth;
49765 var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
49766 connectYBottom, connectYTop;
49767 var extraSubplot;
49768
49769 function xLinePath(y) {
49770 return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
49771 }
49772
49773 function xLinePathFree(y) {
49774 return 'M' + xa._offset + ',' + y + 'h' + xa._length;
49775 }
49776
49777 function yLinePath(x) {
49778 return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
49779 }
49780
49781 function yLinePathFree(x) {
49782 return 'M' + x + ',' + ya._offset + 'v' + ya._length;
49783 }
49784
49785 function mainPath(ax, pathFn, pathFnFree) {
49786 if(!ax.showline || subplot !== ax._mainSubplot) return '';
49787 if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
49788 var out = pathFn(ax._mainLinePosition);
49789 if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
49790 return out;
49791 }
49792
49793 for(subplot in fullLayout._plots) {
49794 plotinfo = fullLayout._plots[subplot];
49795 xa = plotinfo.xaxis;
49796 ya = plotinfo.yaxis;
49797
49798 /*
49799 * x lines get longer where they meet y lines, to make a crisp corner.
49800 * The x lines get the padding (margin.pad) plus the y line width to
49801 * fill up the corner nicely. Free x lines are excluded - they always
49802 * span exactly the data area of the plot
49803 *
49804 * | XXXXX
49805 * | XXXXX
49806 * |
49807 * +------
49808 * x1
49809 * -----
49810 * x2
49811 */
49812 var xPath = 'M0,0';
49813 if(shouldShowLinesOrTicks(xa, subplot)) {
49814 leftYLineWidth = findCounterAxisLineWidth(xa, 'left', ya, axList);
49815 xLinesXLeft = xa._offset - (leftYLineWidth ? (pad + leftYLineWidth) : 0);
49816 rightYLineWidth = findCounterAxisLineWidth(xa, 'right', ya, axList);
49817 xLinesXRight = xa._offset + xa._length + (rightYLineWidth ? (pad + rightYLineWidth) : 0);
49818 xLinesYBottom = getLinePosition(xa, ya, 'bottom');
49819 xLinesYTop = getLinePosition(xa, ya, 'top');
49820
49821 // save axis line positions for extra ticks to reference
49822 // each subplot that gets ticks from "allticks" gets an entry:
49823 // [left or bottom, right or top]
49824 extraSubplot = (!xa._anchorAxis || subplot !== xa._mainSubplot);
49825 if(extraSubplot && (xa.mirror === 'allticks' || xa.mirror === 'all')) {
49826 xa._linepositions[subplot] = [xLinesYBottom, xLinesYTop];
49827 }
49828
49829 xPath = mainPath(xa, xLinePath, xLinePathFree);
49830 if(extraSubplot && xa.showline && (xa.mirror === 'all' || xa.mirror === 'allticks')) {
49831 xPath += xLinePath(xLinesYBottom) + xLinePath(xLinesYTop);
49832 }
49833
49834 plotinfo.xlines
49835 .style('stroke-width', xa._lw + 'px')
49836 .call(Color.stroke, xa.showline ?
49837 xa.linecolor : 'rgba(0,0,0,0)');
49838 }
49839 plotinfo.xlines.attr('d', xPath);
49840
49841 /*
49842 * y lines that meet x axes get longer only by margin.pad, because
49843 * the x axes fill in the corner space. Free y axes, like free x axes,
49844 * always span exactly the data area of the plot
49845 *
49846 * | | XXXX
49847 * y2| y1| XXXX
49848 * | | XXXX
49849 * |
49850 * +-----
49851 */
49852 var yPath = 'M0,0';
49853 if(shouldShowLinesOrTicks(ya, subplot)) {
49854 connectYBottom = findCounterAxisLineWidth(ya, 'bottom', xa, axList);
49855 yLinesYBottom = ya._offset + ya._length + (connectYBottom ? pad : 0);
49856 connectYTop = findCounterAxisLineWidth(ya, 'top', xa, axList);
49857 yLinesYTop = ya._offset - (connectYTop ? pad : 0);
49858 yLinesXLeft = getLinePosition(ya, xa, 'left');
49859 yLinesXRight = getLinePosition(ya, xa, 'right');
49860
49861 extraSubplot = (!ya._anchorAxis || subplot !== ya._mainSubplot);
49862 if(extraSubplot && (ya.mirror === 'allticks' || ya.mirror === 'all')) {
49863 ya._linepositions[subplot] = [yLinesXLeft, yLinesXRight];
49864 }
49865
49866 yPath = mainPath(ya, yLinePath, yLinePathFree);
49867 if(extraSubplot && ya.showline && (ya.mirror === 'all' || ya.mirror === 'allticks')) {
49868 yPath += yLinePath(yLinesXLeft) + yLinePath(yLinesXRight);
49869 }
49870
49871 plotinfo.ylines
49872 .style('stroke-width', ya._lw + 'px')
49873 .call(Color.stroke, ya.showline ?
49874 ya.linecolor : 'rgba(0,0,0,0)');
49875 }
49876 plotinfo.ylines.attr('d', yPath);
49877 }
49878
49879 Axes.makeClipPaths(gd);
49880
49881 return Plots.previousPromises(gd);
49882}
49883
49884function shouldShowLinesOrTicks(ax, subplot) {
49885 return (ax.ticks || ax.showline) &&
49886 (subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks');
49887}
49888
49889/*
49890 * should we draw a line on counterAx at this side of ax?
49891 * It's assumed that counterAx is known to overlay the subplot we're working on
49892 * but it may not be its main axis.
49893 */
49894function shouldShowLineThisSide(ax, side, counterAx) {
49895 // does counterAx get a line at all?
49896 if(!counterAx.showline || !counterAx._lw) return false;
49897
49898 // are we drawing *all* lines for counterAx?
49899 if(counterAx.mirror === 'all' || counterAx.mirror === 'allticks') return true;
49900
49901 var anchorAx = counterAx._anchorAxis;
49902
49903 // is this a free axis? free axes can only have a subplot side-line with all(ticks)? mirroring
49904 if(!anchorAx) return false;
49905
49906 // in order to handle cases where the user forgot to anchor this axis correctly
49907 // (because its default anchor has the same domain on the relevant end)
49908 // check whether the relevant position is the same.
49909 var sideIndex = alignmentConstants.FROM_BL[side];
49910 if(counterAx.side === side) {
49911 return anchorAx.domain[sideIndex] === ax.domain[sideIndex];
49912 }
49913 return counterAx.mirror && anchorAx.domain[1 - sideIndex] === ax.domain[1 - sideIndex];
49914}
49915
49916/*
49917 * Is there another axis intersecting `side` end of `ax`?
49918 * First look at `counterAx` (the axis for this subplot),
49919 * then at all other potential counteraxes on or overlaying this subplot.
49920 * Take the line width from the first one that has a line.
49921 */
49922function findCounterAxisLineWidth(ax, side, counterAx, axList) {
49923 if(shouldShowLineThisSide(ax, side, counterAx)) {
49924 return counterAx._lw;
49925 }
49926 for(var i = 0; i < axList.length; i++) {
49927 var axi = axList[i];
49928 if(axi._mainAxis === counterAx._mainAxis && shouldShowLineThisSide(ax, side, axi)) {
49929 return axi._lw;
49930 }
49931 }
49932 return 0;
49933}
49934
49935exports.drawMainTitle = function(gd) {
49936 var fullLayout = gd._fullLayout;
49937
49938 var textAnchor = getMainTitleTextAnchor(fullLayout);
49939 var dy = getMainTitleDy(fullLayout);
49940
49941 Titles.draw(gd, 'gtitle', {
49942 propContainer: fullLayout,
49943 propName: 'title.text',
49944 placeholder: fullLayout._dfltTitle.plot,
49945 attributes: {
49946 x: getMainTitleX(fullLayout, textAnchor),
49947 y: getMainTitleY(fullLayout, dy),
49948 'text-anchor': textAnchor,
49949 dy: dy
49950 }
49951 });
49952};
49953
49954function getMainTitleX(fullLayout, textAnchor) {
49955 var title = fullLayout.title;
49956 var gs = fullLayout._size;
49957 var hPadShift = 0;
49958
49959 if(textAnchor === SVG_TEXT_ANCHOR_START) {
49960 hPadShift = title.pad.l;
49961 } else if(textAnchor === SVG_TEXT_ANCHOR_END) {
49962 hPadShift = -title.pad.r;
49963 }
49964
49965 switch(title.xref) {
49966 case 'paper':
49967 return gs.l + gs.w * title.x + hPadShift;
49968 case 'container':
49969 default:
49970 return fullLayout.width * title.x + hPadShift;
49971 }
49972}
49973
49974function getMainTitleY(fullLayout, dy) {
49975 var title = fullLayout.title;
49976 var gs = fullLayout._size;
49977 var vPadShift = 0;
49978
49979 if(dy === '0em' || !dy) {
49980 vPadShift = -title.pad.b;
49981 } else if(dy === alignmentConstants.CAP_SHIFT + 'em') {
49982 vPadShift = title.pad.t;
49983 }
49984
49985 if(title.y === 'auto') {
49986 return gs.t / 2;
49987 } else {
49988 switch(title.yref) {
49989 case 'paper':
49990 return gs.t + gs.h - gs.h * title.y + vPadShift;
49991 case 'container':
49992 default:
49993 return fullLayout.height - fullLayout.height * title.y + vPadShift;
49994 }
49995 }
49996}
49997
49998function getMainTitleTextAnchor(fullLayout) {
49999 var title = fullLayout.title;
50000
50001 var textAnchor = SVG_TEXT_ANCHOR_MIDDLE;
50002 if(Lib.isRightAnchor(title)) {
50003 textAnchor = SVG_TEXT_ANCHOR_END;
50004 } else if(Lib.isLeftAnchor(title)) {
50005 textAnchor = SVG_TEXT_ANCHOR_START;
50006 }
50007
50008 return textAnchor;
50009}
50010
50011function getMainTitleDy(fullLayout) {
50012 var title = fullLayout.title;
50013
50014 var dy = '0em';
50015 if(Lib.isTopAnchor(title)) {
50016 dy = alignmentConstants.CAP_SHIFT + 'em';
50017 } else if(Lib.isMiddleAnchor(title)) {
50018 dy = alignmentConstants.MID_SHIFT + 'em';
50019 }
50020
50021 return dy;
50022}
50023
50024exports.doTraceStyle = function(gd) {
50025 var calcdata = gd.calcdata;
50026 var editStyleCalls = [];
50027 var i;
50028
50029 for(i = 0; i < calcdata.length; i++) {
50030 var cd = calcdata[i];
50031 var cd0 = cd[0] || {};
50032 var trace = cd0.trace || {};
50033 var _module = trace._module || {};
50034
50035 // See if we need to do arraysToCalcdata
50036 // call it regardless of what change we made, in case
50037 // supplyDefaults brought in an array that was already
50038 // in gd.data but not in gd._fullData previously
50039 var arraysToCalcdata = _module.arraysToCalcdata;
50040 if(arraysToCalcdata) arraysToCalcdata(cd, trace);
50041
50042 var editStyle = _module.editStyle;
50043 if(editStyle) editStyleCalls.push({fn: editStyle, cd0: cd0});
50044 }
50045
50046 if(editStyleCalls.length) {
50047 for(i = 0; i < editStyleCalls.length; i++) {
50048 var edit = editStyleCalls[i];
50049 edit.fn(gd, edit.cd0);
50050 }
50051 clearGlCanvases(gd);
50052 exports.redrawReglTraces(gd);
50053 }
50054
50055 Plots.style(gd);
50056 Registry.getComponentMethod('legend', 'draw')(gd);
50057
50058 return Plots.previousPromises(gd);
50059};
50060
50061exports.doColorBars = function(gd) {
50062 Registry.getComponentMethod('colorbar', 'draw')(gd);
50063 return Plots.previousPromises(gd);
50064};
50065
50066// force plot() to redo the layout and replot with the modified layout
50067exports.layoutReplot = function(gd) {
50068 var layout = gd.layout;
50069 gd.layout = undefined;
50070 return Registry.call('plot', gd, '', layout);
50071};
50072
50073exports.doLegend = function(gd) {
50074 Registry.getComponentMethod('legend', 'draw')(gd);
50075 return Plots.previousPromises(gd);
50076};
50077
50078exports.doTicksRelayout = function(gd) {
50079 Axes.draw(gd, 'redraw');
50080
50081 if(gd._fullLayout._hasOnlyLargeSploms) {
50082 Registry.subplotsRegistry.splom.updateGrid(gd);
50083 clearGlCanvases(gd);
50084 exports.redrawReglTraces(gd);
50085 }
50086
50087 exports.drawMainTitle(gd);
50088 return Plots.previousPromises(gd);
50089};
50090
50091exports.doModeBar = function(gd) {
50092 var fullLayout = gd._fullLayout;
50093
50094 ModeBar.manage(gd);
50095
50096 for(var i = 0; i < fullLayout._basePlotModules.length; i++) {
50097 var updateFx = fullLayout._basePlotModules[i].updateFx;
50098 if(updateFx) updateFx(gd);
50099 }
50100
50101 return Plots.previousPromises(gd);
50102};
50103
50104exports.doCamera = function(gd) {
50105 var fullLayout = gd._fullLayout;
50106 var sceneIds = fullLayout._subplots.gl3d;
50107
50108 for(var i = 0; i < sceneIds.length; i++) {
50109 var sceneLayout = fullLayout[sceneIds[i]];
50110 var scene = sceneLayout._scene;
50111
50112 scene.setViewport(sceneLayout);
50113 }
50114};
50115
50116exports.drawData = function(gd) {
50117 var fullLayout = gd._fullLayout;
50118
50119 clearGlCanvases(gd);
50120
50121 // loop over the base plot modules present on graph
50122 var basePlotModules = fullLayout._basePlotModules;
50123 for(var i = 0; i < basePlotModules.length; i++) {
50124 basePlotModules[i].plot(gd);
50125 }
50126
50127 exports.redrawReglTraces(gd);
50128
50129 // styling separate from drawing
50130 Plots.style(gd);
50131
50132 // draw components that can be drawn on axes,
50133 // and that do not push the margins
50134 Registry.getComponentMethod('shapes', 'draw')(gd);
50135 Registry.getComponentMethod('annotations', 'draw')(gd);
50136 Registry.getComponentMethod('images', 'draw')(gd);
50137
50138 // Mark the first render as complete
50139 fullLayout._replotting = false;
50140
50141 return Plots.previousPromises(gd);
50142};
50143
50144// Draw (or redraw) all regl-based traces in one go,
50145// useful during drag and selection where buffers of targeted traces are updated,
50146// but all traces need to be redrawn following clearGlCanvases.
50147//
50148// Note that _module.plot for regl trace does NOT draw things
50149// on the canvas, they only update the buffers.
50150// Drawing is perform here.
50151//
50152// TODO try adding per-subplot option using gl.SCISSOR_TEST for
50153// non-overlaying, disjoint subplots.
50154//
50155// TODO try to include parcoords in here.
50156// https://github.com/plotly/plotly.js/issues/3069
50157exports.redrawReglTraces = function(gd) {
50158 var fullLayout = gd._fullLayout;
50159
50160 if(fullLayout._has('regl')) {
50161 var fullData = gd._fullData;
50162 var cartesianIds = [];
50163 var polarIds = [];
50164 var i, sp;
50165
50166 if(fullLayout._hasOnlyLargeSploms) {
50167 fullLayout._splomGrid.draw();
50168 }
50169
50170 // N.B.
50171 // - Loop over fullData (not _splomScenes) to preserve splom trace-to-trace ordering
50172 // - Fill list if subplot ids (instead of fullLayout._subplots) to handle cases where all traces
50173 // of a given module are `visible !== true`
50174 for(i = 0; i < fullData.length; i++) {
50175 var trace = fullData[i];
50176
50177 if(trace.visible === true && trace._length !== 0) {
50178 if(trace.type === 'splom') {
50179 fullLayout._splomScenes[trace.uid].draw();
50180 } else if(trace.type === 'scattergl') {
50181 Lib.pushUnique(cartesianIds, trace.xaxis + trace.yaxis);
50182 } else if(trace.type === 'scatterpolargl') {
50183 Lib.pushUnique(polarIds, trace.subplot);
50184 }
50185 }
50186 }
50187
50188 for(i = 0; i < cartesianIds.length; i++) {
50189 sp = fullLayout._plots[cartesianIds[i]];
50190 if(sp._scene) sp._scene.draw();
50191 }
50192
50193 for(i = 0; i < polarIds.length; i++) {
50194 sp = fullLayout[polarIds[i]]._subplot;
50195 if(sp._scene) sp._scene.draw();
50196 }
50197 }
50198};
50199
50200exports.doAutoRangeAndConstraints = function(gd) {
50201 var fullLayout = gd._fullLayout;
50202 var axList = Axes.list(gd, '', true);
50203 var matchGroups = fullLayout._axisMatchGroups || [];
50204 var axLookup = {};
50205 var ax;
50206 var axRng;
50207
50208 for(var i = 0; i < axList.length; i++) {
50209 ax = axList[i];
50210 cleanAxisConstraints(gd, ax);
50211 doAutoRange(gd, ax);
50212 axLookup[ax._id] = 1;
50213 }
50214
50215 enforceAxisConstraints(gd);
50216
50217 groupLoop:
50218 for(var j = 0; j < matchGroups.length; j++) {
50219 var group = matchGroups[j];
50220 var rng = null;
50221 var id;
50222
50223 for(id in group) {
50224 ax = Axes.getFromId(gd, id);
50225
50226 // skip over 'missing' axes which do not pass through doAutoRange
50227 if(!axLookup[ax._id]) continue;
50228 // if one axis has autorange false, we're done
50229 if(ax.autorange === false) continue groupLoop;
50230
50231 axRng = Lib.simpleMap(ax.range, ax.r2l);
50232 if(rng) {
50233 if(rng[0] < rng[1]) {
50234 rng[0] = Math.min(rng[0], axRng[0]);
50235 rng[1] = Math.max(rng[1], axRng[1]);
50236 } else {
50237 rng[0] = Math.max(rng[0], axRng[0]);
50238 rng[1] = Math.min(rng[1], axRng[1]);
50239 }
50240 } else {
50241 rng = axRng;
50242 }
50243 }
50244
50245 for(id in group) {
50246 ax = Axes.getFromId(gd, id);
50247 ax.range = Lib.simpleMap(rng, ax.l2r);
50248 ax._input.range = ax.range.slice();
50249 ax.setScale();
50250 }
50251 }
50252};
50253
50254// An initial paint must be completed before these components can be
50255// correctly sized and the whole plot re-margined. fullLayout._replotting must
50256// be set to false before these will work properly.
50257exports.finalDraw = function(gd) {
50258 // TODO: rangesliders really belong in marginPushers but they need to be
50259 // drawn after data - can we at least get the margin pushing part separated
50260 // out and done earlier?
50261 Registry.getComponentMethod('rangeslider', 'draw')(gd);
50262 // TODO: rangeselector only needs to be here (in addition to drawMarginPushers)
50263 // because the margins need to be fully determined before we can call
50264 // autorange and update axis ranges (which rangeselector needs to know which
50265 // button is active). Can we break out its automargin step from its draw step?
50266 Registry.getComponentMethod('rangeselector', 'draw')(gd);
50267};
50268
50269exports.drawMarginPushers = function(gd) {
50270 Registry.getComponentMethod('legend', 'draw')(gd);
50271 Registry.getComponentMethod('rangeselector', 'draw')(gd);
50272 Registry.getComponentMethod('sliders', 'draw')(gd);
50273 Registry.getComponentMethod('updatemenus', 'draw')(gd);
50274 Registry.getComponentMethod('colorbar', 'draw')(gd);
50275};
50276
50277},{"../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){
50278/**
50279* Copyright 2012-2020, Plotly, Inc.
50280* All rights reserved.
50281*
50282* This source code is licensed under the MIT license found in the
50283* LICENSE file in the root directory of this source tree.
50284*/
50285
50286
50287'use strict';
50288
50289var Lib = _dereq_('../lib');
50290var isPlainObject = Lib.isPlainObject;
50291var PlotSchema = _dereq_('./plot_schema');
50292var Plots = _dereq_('../plots/plots');
50293var plotAttributes = _dereq_('../plots/attributes');
50294var Template = _dereq_('./plot_template');
50295var dfltConfig = _dereq_('./plot_config').dfltConfig;
50296
50297/**
50298 * Plotly.makeTemplate: create a template off an existing figure to reuse
50299 * style attributes on other figures.
50300 *
50301 * Note: separated from the rest of templates because otherwise we get circular
50302 * references due to PlotSchema.
50303 *
50304 * @param {object|DOM element|string} figure: The figure to base the template on
50305 * should contain a trace array `figure.data`
50306 * and a layout object `figure.layout`
50307 * @returns {object} template: the extracted template - can then be used as
50308 * `layout.template` in another figure.
50309 */
50310exports.makeTemplate = function(figure) {
50311 figure = Lib.isPlainObject(figure) ? figure : Lib.getGraphDiv(figure);
50312 figure = Lib.extendDeep({_context: dfltConfig}, {data: figure.data, layout: figure.layout});
50313 Plots.supplyDefaults(figure);
50314 var data = figure.data || [];
50315 var layout = figure.layout || {};
50316 // copy over a few items to help follow the schema
50317 layout._basePlotModules = figure._fullLayout._basePlotModules;
50318 layout._modules = figure._fullLayout._modules;
50319
50320 var template = {
50321 data: {},
50322 layout: {}
50323 };
50324
50325 /*
50326 * Note: we do NOT validate template values, we just take what's in the
50327 * user inputs data and layout, not the validated values in fullData and
50328 * fullLayout. Even if we were to validate here, there's no guarantee that
50329 * these values would still be valid when applied to a new figure, which
50330 * may contain different trace modes, different axes, etc. So it's
50331 * important that when applying a template we still validate the template
50332 * values, rather than just using them as defaults.
50333 */
50334
50335 data.forEach(function(trace) {
50336 // TODO: What if no style info is extracted for this trace. We may
50337 // not want an empty object as the null value.
50338 // TODO: allow transforms to contribute to templates?
50339 // as it stands they are ignored, which may be for the best...
50340
50341 var traceTemplate = {};
50342 walkStyleKeys(trace, traceTemplate, getTraceInfo.bind(null, trace));
50343
50344 var traceType = Lib.coerce(trace, {}, plotAttributes, 'type');
50345 var typeTemplates = template.data[traceType];
50346 if(!typeTemplates) typeTemplates = template.data[traceType] = [];
50347 typeTemplates.push(traceTemplate);
50348 });
50349
50350 walkStyleKeys(layout, template.layout, getLayoutInfo.bind(null, layout));
50351
50352 /*
50353 * Compose the new template with an existing one to the same effect
50354 *
50355 * NOTE: there's a possibility of slightly different behavior: if the plot
50356 * has an invalid value and the old template has a valid value for the same
50357 * attribute, the plot will use the old template value but this routine
50358 * will pull the invalid value (resulting in the original default).
50359 * In the general case it's not possible to solve this with a single value,
50360 * since valid options can be context-dependent. It could be solved with
50361 * a *list* of values, but that would be huge complexity for little gain.
50362 */
50363 delete template.layout.template;
50364 var oldTemplate = layout.template;
50365 if(isPlainObject(oldTemplate)) {
50366 var oldLayoutTemplate = oldTemplate.layout;
50367
50368 var i, traceType, oldTypeTemplates, oldTypeLen, typeTemplates, typeLen;
50369
50370 if(isPlainObject(oldLayoutTemplate)) {
50371 mergeTemplates(oldLayoutTemplate, template.layout);
50372 }
50373 var oldDataTemplate = oldTemplate.data;
50374 if(isPlainObject(oldDataTemplate)) {
50375 for(traceType in template.data) {
50376 oldTypeTemplates = oldDataTemplate[traceType];
50377 if(Array.isArray(oldTypeTemplates)) {
50378 typeTemplates = template.data[traceType];
50379 typeLen = typeTemplates.length;
50380 oldTypeLen = oldTypeTemplates.length;
50381 for(i = 0; i < typeLen; i++) {
50382 mergeTemplates(oldTypeTemplates[i % oldTypeLen], typeTemplates[i]);
50383 }
50384 for(i = typeLen; i < oldTypeLen; i++) {
50385 typeTemplates.push(Lib.extendDeep({}, oldTypeTemplates[i]));
50386 }
50387 }
50388 }
50389 for(traceType in oldDataTemplate) {
50390 if(!(traceType in template.data)) {
50391 template.data[traceType] = Lib.extendDeep([], oldDataTemplate[traceType]);
50392 }
50393 }
50394 }
50395 }
50396
50397 return template;
50398};
50399
50400function mergeTemplates(oldTemplate, newTemplate) {
50401 // we don't care about speed here, just make sure we have a totally
50402 // distinct object from the previous template
50403 oldTemplate = Lib.extendDeep({}, oldTemplate);
50404
50405 // sort keys so we always get annotationdefaults before annotations etc
50406 // so arrayTemplater will work right
50407 var oldKeys = Object.keys(oldTemplate).sort();
50408 var i, j;
50409
50410 function mergeOne(oldVal, newVal, key) {
50411 if(isPlainObject(newVal) && isPlainObject(oldVal)) {
50412 mergeTemplates(oldVal, newVal);
50413 } else if(Array.isArray(newVal) && Array.isArray(oldVal)) {
50414 // Note: omitted `inclusionAttr` from arrayTemplater here,
50415 // it's irrelevant as we only want the resulting `_template`.
50416 var templater = Template.arrayTemplater({_template: oldTemplate}, key);
50417 for(j = 0; j < newVal.length; j++) {
50418 var item = newVal[j];
50419 var oldItem = templater.newItem(item)._template;
50420 if(oldItem) mergeTemplates(oldItem, item);
50421 }
50422 var defaultItems = templater.defaultItems();
50423 for(j = 0; j < defaultItems.length; j++) newVal.push(defaultItems[j]._template);
50424
50425 // templateitemname only applies to receiving plots
50426 for(j = 0; j < newVal.length; j++) delete newVal[j].templateitemname;
50427 }
50428 }
50429
50430 for(i = 0; i < oldKeys.length; i++) {
50431 var key = oldKeys[i];
50432 var oldVal = oldTemplate[key];
50433 if(key in newTemplate) {
50434 mergeOne(oldVal, newTemplate[key], key);
50435 } else newTemplate[key] = oldVal;
50436
50437 // if this is a base key from the old template (eg xaxis), look for
50438 // extended keys (eg xaxis2) in the new template to merge into
50439 if(getBaseKey(key) === key) {
50440 for(var key2 in newTemplate) {
50441 var baseKey2 = getBaseKey(key2);
50442 if(key2 !== baseKey2 && baseKey2 === key && !(key2 in oldTemplate)) {
50443 mergeOne(oldVal, newTemplate[key2], key);
50444 }
50445 }
50446 }
50447 }
50448}
50449
50450function getBaseKey(key) {
50451 return key.replace(/[0-9]+$/, '');
50452}
50453
50454function walkStyleKeys(parent, templateOut, getAttributeInfo, path, basePath) {
50455 var pathAttr = basePath && getAttributeInfo(basePath);
50456 for(var key in parent) {
50457 var child = parent[key];
50458 var nextPath = getNextPath(parent, key, path);
50459 var nextBasePath = getNextPath(parent, key, basePath);
50460 var attr = getAttributeInfo(nextBasePath);
50461 if(!attr) {
50462 var baseKey = getBaseKey(key);
50463 if(baseKey !== key) {
50464 nextBasePath = getNextPath(parent, baseKey, basePath);
50465 attr = getAttributeInfo(nextBasePath);
50466 }
50467 }
50468
50469 // we'll get an attr if path starts with a valid part, then has an
50470 // invalid ending. Make sure we got all the way to the end.
50471 if(pathAttr && (pathAttr === attr)) continue;
50472
50473 if(!attr || attr._noTemplating ||
50474 attr.valType === 'data_array' ||
50475 (attr.arrayOk && Array.isArray(child))
50476 ) {
50477 continue;
50478 }
50479
50480 if(!attr.valType && isPlainObject(child)) {
50481 walkStyleKeys(child, templateOut, getAttributeInfo, nextPath, nextBasePath);
50482 } else if(attr._isLinkedToArray && Array.isArray(child)) {
50483 var dfltDone = false;
50484 var namedIndex = 0;
50485 var usedNames = {};
50486 for(var i = 0; i < child.length; i++) {
50487 var item = child[i];
50488 if(isPlainObject(item)) {
50489 var name = item.name;
50490 if(name) {
50491 if(!usedNames[name]) {
50492 // named array items: allow all attributes except data arrays
50493 walkStyleKeys(item, templateOut, getAttributeInfo,
50494 getNextPath(child, namedIndex, nextPath),
50495 getNextPath(child, namedIndex, nextBasePath));
50496 namedIndex++;
50497 usedNames[name] = 1;
50498 }
50499 } else if(!dfltDone) {
50500 var dfltKey = Template.arrayDefaultKey(key);
50501 var dfltPath = getNextPath(parent, dfltKey, path);
50502
50503 // getAttributeInfo will fail if we try to use dfltKey directly.
50504 // Instead put this item into the next array element, then
50505 // pull it out and move it to dfltKey.
50506 var pathInArray = getNextPath(child, namedIndex, nextPath);
50507 walkStyleKeys(item, templateOut, getAttributeInfo, pathInArray,
50508 getNextPath(child, namedIndex, nextBasePath));
50509 var itemPropInArray = Lib.nestedProperty(templateOut, pathInArray);
50510 var dfltProp = Lib.nestedProperty(templateOut, dfltPath);
50511 dfltProp.set(itemPropInArray.get());
50512 itemPropInArray.set(null);
50513
50514 dfltDone = true;
50515 }
50516 }
50517 }
50518 } else {
50519 var templateProp = Lib.nestedProperty(templateOut, nextPath);
50520 templateProp.set(child);
50521 }
50522 }
50523}
50524
50525function getLayoutInfo(layout, path) {
50526 return PlotSchema.getLayoutValObject(
50527 layout, Lib.nestedProperty({}, path).parts
50528 );
50529}
50530
50531function getTraceInfo(trace, path) {
50532 return PlotSchema.getTraceValObject(
50533 trace, Lib.nestedProperty({}, path).parts
50534 );
50535}
50536
50537function getNextPath(parent, key, path) {
50538 var nextPath;
50539 if(!path) nextPath = key;
50540 else if(Array.isArray(parent)) nextPath = path + '[' + key + ']';
50541 else nextPath = path + '.' + key;
50542
50543 return nextPath;
50544}
50545
50546/**
50547 * validateTemplate: Test for consistency between the given figure and
50548 * a template, either already included in the figure or given separately.
50549 * Note that not every issue we identify here is necessarily a problem,
50550 * it depends on what you're using the template for.
50551 *
50552 * @param {object|DOM element} figure: the plot, with {data, layout} members,
50553 * to test the template against
50554 * @param {Optional(object)} template: the template, with its own {data, layout},
50555 * to test. If omitted, we will look for a template already attached as the
50556 * plot's `layout.template` attribute.
50557 *
50558 * @returns {array} array of error objects each containing:
50559 * - {string} code
50560 * error code ('missing', 'unused', 'reused', 'noLayout', 'noData')
50561 * - {string} msg
50562 * a full readable description of the issue.
50563 */
50564exports.validateTemplate = function(figureIn, template) {
50565 var figure = Lib.extendDeep({}, {
50566 _context: dfltConfig,
50567 data: figureIn.data,
50568 layout: figureIn.layout
50569 });
50570 var layout = figure.layout || {};
50571 if(!isPlainObject(template)) template = layout.template || {};
50572 var layoutTemplate = template.layout;
50573 var dataTemplate = template.data;
50574 var errorList = [];
50575
50576 figure.layout = layout;
50577 figure.layout.template = template;
50578 Plots.supplyDefaults(figure);
50579
50580 var fullLayout = figure._fullLayout;
50581 var fullData = figure._fullData;
50582
50583 var layoutPaths = {};
50584 function crawlLayoutForContainers(obj, paths) {
50585 for(var key in obj) {
50586 if(key.charAt(0) !== '_' && isPlainObject(obj[key])) {
50587 var baseKey = getBaseKey(key);
50588 var nextPaths = [];
50589 var i;
50590 for(i = 0; i < paths.length; i++) {
50591 nextPaths.push(getNextPath(obj, key, paths[i]));
50592 if(baseKey !== key) nextPaths.push(getNextPath(obj, baseKey, paths[i]));
50593 }
50594 for(i = 0; i < nextPaths.length; i++) {
50595 layoutPaths[nextPaths[i]] = 1;
50596 }
50597 crawlLayoutForContainers(obj[key], nextPaths);
50598 }
50599 }
50600 }
50601
50602 function crawlLayoutTemplateForContainers(obj, path) {
50603 for(var key in obj) {
50604 if(key.indexOf('defaults') === -1 && isPlainObject(obj[key])) {
50605 var nextPath = getNextPath(obj, key, path);
50606 if(layoutPaths[nextPath]) {
50607 crawlLayoutTemplateForContainers(obj[key], nextPath);
50608 } else {
50609 errorList.push({code: 'unused', path: nextPath});
50610 }
50611 }
50612 }
50613 }
50614
50615 if(!isPlainObject(layoutTemplate)) {
50616 errorList.push({code: 'layout'});
50617 } else {
50618 crawlLayoutForContainers(fullLayout, ['layout']);
50619 crawlLayoutTemplateForContainers(layoutTemplate, 'layout');
50620 }
50621
50622 if(!isPlainObject(dataTemplate)) {
50623 errorList.push({code: 'data'});
50624 } else {
50625 var typeCount = {};
50626 var traceType;
50627 for(var i = 0; i < fullData.length; i++) {
50628 var fullTrace = fullData[i];
50629 traceType = fullTrace.type;
50630 typeCount[traceType] = (typeCount[traceType] || 0) + 1;
50631 if(!fullTrace._fullInput._template) {
50632 // this takes care of the case of traceType in the data but not
50633 // the template
50634 errorList.push({
50635 code: 'missing',
50636 index: fullTrace._fullInput.index,
50637 traceType: traceType
50638 });
50639 }
50640 }
50641 for(traceType in dataTemplate) {
50642 var templateCount = dataTemplate[traceType].length;
50643 var dataCount = typeCount[traceType] || 0;
50644 if(templateCount > dataCount) {
50645 errorList.push({
50646 code: 'unused',
50647 traceType: traceType,
50648 templateCount: templateCount,
50649 dataCount: dataCount
50650 });
50651 } else if(dataCount > templateCount) {
50652 errorList.push({
50653 code: 'reused',
50654 traceType: traceType,
50655 templateCount: templateCount,
50656 dataCount: dataCount
50657 });
50658 }
50659 }
50660 }
50661
50662 // _template: false is when someone tried to modify an array item
50663 // but there was no template with matching name
50664 function crawlForMissingTemplates(obj, path) {
50665 for(var key in obj) {
50666 if(key.charAt(0) === '_') continue;
50667 var val = obj[key];
50668 var nextPath = getNextPath(obj, key, path);
50669 if(isPlainObject(val)) {
50670 if(Array.isArray(obj) && val._template === false && val.templateitemname) {
50671 errorList.push({
50672 code: 'missing',
50673 path: nextPath,
50674 templateitemname: val.templateitemname
50675 });
50676 }
50677 crawlForMissingTemplates(val, nextPath);
50678 } else if(Array.isArray(val) && hasPlainObject(val)) {
50679 crawlForMissingTemplates(val, nextPath);
50680 }
50681 }
50682 }
50683 crawlForMissingTemplates({data: fullData, layout: fullLayout}, '');
50684
50685 if(errorList.length) return errorList.map(format);
50686};
50687
50688function hasPlainObject(arr) {
50689 for(var i = 0; i < arr.length; i++) {
50690 if(isPlainObject(arr[i])) return true;
50691 }
50692}
50693
50694function format(opts) {
50695 var msg;
50696 switch(opts.code) {
50697 case 'data':
50698 msg = 'The template has no key data.';
50699 break;
50700 case 'layout':
50701 msg = 'The template has no key layout.';
50702 break;
50703 case 'missing':
50704 if(opts.path) {
50705 msg = 'There are no templates for item ' + opts.path +
50706 ' with name ' + opts.templateitemname;
50707 } else {
50708 msg = 'There are no templates for trace ' + opts.index +
50709 ', of type ' + opts.traceType + '.';
50710 }
50711 break;
50712 case 'unused':
50713 if(opts.path) {
50714 msg = 'The template item at ' + opts.path +
50715 ' was not used in constructing the plot.';
50716 } else if(opts.dataCount) {
50717 msg = 'Some of the templates of type ' + opts.traceType +
50718 ' were not used. The template has ' + opts.templateCount +
50719 ' traces, the data only has ' + opts.dataCount +
50720 ' of this type.';
50721 } else {
50722 msg = 'The template has ' + opts.templateCount +
50723 ' traces of type ' + opts.traceType +
50724 ' but there are none in the data.';
50725 }
50726 break;
50727 case 'reused':
50728 msg = 'Some of the templates of type ' + opts.traceType +
50729 ' were used more than once. The template has ' +
50730 opts.templateCount + ' traces, the data has ' +
50731 opts.dataCount + ' of this type.';
50732 break;
50733 }
50734 opts.msg = msg;
50735
50736 return opts;
50737}
50738
50739},{"../lib":178,"../plots/attributes":219,"../plots/plots":256,"./plot_config":210,"./plot_schema":211,"./plot_template":212}],215:[function(_dereq_,module,exports){
50740/**
50741* Copyright 2012-2020, Plotly, Inc.
50742* All rights reserved.
50743*
50744* This source code is licensed under the MIT license found in the
50745* LICENSE file in the root directory of this source tree.
50746*/
50747
50748'use strict';
50749
50750var isNumeric = _dereq_('fast-isnumeric');
50751
50752var plotApi = _dereq_('./plot_api');
50753var plots = _dereq_('../plots/plots');
50754var Lib = _dereq_('../lib');
50755
50756var helpers = _dereq_('../snapshot/helpers');
50757var toSVG = _dereq_('../snapshot/tosvg');
50758var svgToImg = _dereq_('../snapshot/svgtoimg');
50759var version = _dereq_('../version').version;
50760
50761var attrs = {
50762 format: {
50763 valType: 'enumerated',
50764 values: ['png', 'jpeg', 'webp', 'svg', 'full-json'],
50765 dflt: 'png',
50766
50767 },
50768 width: {
50769 valType: 'number',
50770 min: 1,
50771
50772 },
50773 height: {
50774 valType: 'number',
50775 min: 1,
50776
50777 },
50778 scale: {
50779 valType: 'number',
50780 min: 0,
50781 dflt: 1,
50782
50783 },
50784 setBackground: {
50785 valType: 'any',
50786 dflt: false,
50787
50788 },
50789 imageDataOnly: {
50790 valType: 'boolean',
50791 dflt: false,
50792
50793 }
50794};
50795
50796/** Plotly.toImage
50797 *
50798 * @param {object | string | HTML div} gd
50799 * can either be a data/layout/config object
50800 * or an existing graph <div>
50801 * or an id to an existing graph <div>
50802 * @param {object} opts (see above)
50803 * @return {promise}
50804 */
50805function toImage(gd, opts) {
50806 opts = opts || {};
50807
50808 var data;
50809 var layout;
50810 var config;
50811 var fullLayout;
50812
50813 if(Lib.isPlainObject(gd)) {
50814 data = gd.data || [];
50815 layout = gd.layout || {};
50816 config = gd.config || {};
50817 fullLayout = {};
50818 } else {
50819 gd = Lib.getGraphDiv(gd);
50820 data = Lib.extendDeep([], gd.data);
50821 layout = Lib.extendDeep({}, gd.layout);
50822 config = gd._context;
50823 fullLayout = gd._fullLayout || {};
50824 }
50825
50826 function isImpliedOrValid(attr) {
50827 return !(attr in opts) || Lib.validate(opts[attr], attrs[attr]);
50828 }
50829
50830 if((!isImpliedOrValid('width') && opts.width !== null) ||
50831 (!isImpliedOrValid('height') && opts.height !== null)) {
50832 throw new Error('Height and width should be pixel values.');
50833 }
50834
50835 if(!isImpliedOrValid('format')) {
50836 throw new Error('Image format is not jpeg, png, svg or webp.');
50837 }
50838
50839 var fullOpts = {};
50840
50841 function coerce(attr, dflt) {
50842 return Lib.coerce(opts, fullOpts, attrs, attr, dflt);
50843 }
50844
50845 var format = coerce('format');
50846 var width = coerce('width');
50847 var height = coerce('height');
50848 var scale = coerce('scale');
50849 var setBackground = coerce('setBackground');
50850 var imageDataOnly = coerce('imageDataOnly');
50851
50852 // put the cloned div somewhere off screen before attaching to DOM
50853 var clonedGd = document.createElement('div');
50854 clonedGd.style.position = 'absolute';
50855 clonedGd.style.left = '-5000px';
50856 document.body.appendChild(clonedGd);
50857
50858 // extend layout with image options
50859 var layoutImage = Lib.extendFlat({}, layout);
50860 if(width) {
50861 layoutImage.width = width;
50862 } else if(opts.width === null && isNumeric(fullLayout.width)) {
50863 layoutImage.width = fullLayout.width;
50864 }
50865 if(height) {
50866 layoutImage.height = height;
50867 } else if(opts.height === null && isNumeric(fullLayout.height)) {
50868 layoutImage.height = fullLayout.height;
50869 }
50870
50871 // extend config for static plot
50872 var configImage = Lib.extendFlat({}, config, {
50873 _exportedPlot: true,
50874 staticPlot: true,
50875 setBackground: setBackground
50876 });
50877
50878 var redrawFunc = helpers.getRedrawFunc(clonedGd);
50879
50880 function wait() {
50881 return new Promise(function(resolve) {
50882 setTimeout(resolve, helpers.getDelay(clonedGd._fullLayout));
50883 });
50884 }
50885
50886 function convert() {
50887 return new Promise(function(resolve, reject) {
50888 var svg = toSVG(clonedGd, format, scale);
50889 var width = clonedGd._fullLayout.width;
50890 var height = clonedGd._fullLayout.height;
50891
50892 function cleanup() {
50893 plotApi.purge(clonedGd);
50894 document.body.removeChild(clonedGd);
50895 }
50896
50897 if(format === 'full-json') {
50898 var json = plots.graphJson(clonedGd, false, 'keepdata', 'object', true, true);
50899 json.version = version;
50900 json = JSON.stringify(json);
50901 cleanup();
50902 if(imageDataOnly) {
50903 return resolve(json);
50904 } else {
50905 return resolve(helpers.encodeJSON(json));
50906 }
50907 }
50908
50909 cleanup();
50910
50911 if(format === 'svg') {
50912 if(imageDataOnly) {
50913 return resolve(svg);
50914 } else {
50915 return resolve(helpers.encodeSVG(svg));
50916 }
50917 }
50918
50919 var canvas = document.createElement('canvas');
50920 canvas.id = Lib.randstr();
50921
50922 svgToImg({
50923 format: format,
50924 width: width,
50925 height: height,
50926 scale: scale,
50927 canvas: canvas,
50928 svg: svg,
50929 // ask svgToImg to return a Promise
50930 // rather than EventEmitter
50931 // leave EventEmitter for backward
50932 // compatibility
50933 promise: true
50934 })
50935 .then(resolve)
50936 .catch(reject);
50937 });
50938 }
50939
50940 function urlToImageData(url) {
50941 if(imageDataOnly) {
50942 return url.replace(helpers.IMAGE_URL_PREFIX, '');
50943 } else {
50944 return url;
50945 }
50946 }
50947
50948 return new Promise(function(resolve, reject) {
50949 plotApi.plot(clonedGd, data, layoutImage, configImage)
50950 .then(redrawFunc)
50951 .then(wait)
50952 .then(convert)
50953 .then(function(url) { resolve(urlToImageData(url)); })
50954 .catch(function(err) { reject(err); });
50955 });
50956}
50957
50958module.exports = toImage;
50959
50960},{"../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){
50961/**
50962* Copyright 2012-2020, Plotly, Inc.
50963* All rights reserved.
50964*
50965* This source code is licensed under the MIT license found in the
50966* LICENSE file in the root directory of this source tree.
50967*/
50968
50969'use strict';
50970
50971var Lib = _dereq_('../lib');
50972var Plots = _dereq_('../plots/plots');
50973var PlotSchema = _dereq_('./plot_schema');
50974var dfltConfig = _dereq_('./plot_config').dfltConfig;
50975
50976var isPlainObject = Lib.isPlainObject;
50977var isArray = Array.isArray;
50978var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
50979
50980/**
50981 * Validate a data array and layout object.
50982 *
50983 * @param {array} data
50984 * @param {object} layout
50985 *
50986 * @return {array} array of error objects each containing:
50987 * - {string} code
50988 * error code ('object', 'array', 'schema', 'unused', 'invisible' or 'value')
50989 * - {string} container
50990 * container where the error occurs ('data' or 'layout')
50991 * - {number} trace
50992 * trace index of the 'data' container where the error occurs
50993 * - {array} path
50994 * nested path to the key that causes the error
50995 * - {string} astr
50996 * attribute string variant of 'path' compatible with Plotly.restyle and
50997 * Plotly.relayout.
50998 * - {string} msg
50999 * error message (shown in console in logger config argument is enable)
51000 */
51001module.exports = function validate(data, layout) {
51002 var schema = PlotSchema.get();
51003 var errorList = [];
51004 var gd = {_context: Lib.extendFlat({}, dfltConfig)};
51005
51006 var dataIn, layoutIn;
51007
51008 if(isArray(data)) {
51009 gd.data = Lib.extendDeep([], data);
51010 dataIn = data;
51011 } else {
51012 gd.data = [];
51013 dataIn = [];
51014 errorList.push(format('array', 'data'));
51015 }
51016
51017 if(isPlainObject(layout)) {
51018 gd.layout = Lib.extendDeep({}, layout);
51019 layoutIn = layout;
51020 } else {
51021 gd.layout = {};
51022 layoutIn = {};
51023 if(arguments.length > 1) {
51024 errorList.push(format('object', 'layout'));
51025 }
51026 }
51027
51028 // N.B. dataIn and layoutIn are in general not the same as
51029 // gd.data and gd.layout after supplyDefaults as some attributes
51030 // in gd.data and gd.layout (still) get mutated during this step.
51031
51032 Plots.supplyDefaults(gd);
51033
51034 var dataOut = gd._fullData;
51035 var len = dataIn.length;
51036
51037 for(var i = 0; i < len; i++) {
51038 var traceIn = dataIn[i];
51039 var base = ['data', i];
51040
51041 if(!isPlainObject(traceIn)) {
51042 errorList.push(format('object', base));
51043 continue;
51044 }
51045
51046 var traceOut = dataOut[i];
51047 var traceType = traceOut.type;
51048 var traceSchema = schema.traces[traceType].attributes;
51049
51050 // PlotSchema does something fancy with trace 'type', reset it here
51051 // to make the trace schema compatible with Lib.validate.
51052 traceSchema.type = {
51053 valType: 'enumerated',
51054 values: [traceType]
51055 };
51056
51057 if(traceOut.visible === false && traceIn.visible !== false) {
51058 errorList.push(format('invisible', base));
51059 }
51060
51061 crawl(traceIn, traceOut, traceSchema, errorList, base);
51062
51063 var transformsIn = traceIn.transforms;
51064 var transformsOut = traceOut.transforms;
51065
51066 if(transformsIn) {
51067 if(!isArray(transformsIn)) {
51068 errorList.push(format('array', base, ['transforms']));
51069 }
51070
51071 base.push('transforms');
51072
51073 for(var j = 0; j < transformsIn.length; j++) {
51074 var path = ['transforms', j];
51075 var transformType = transformsIn[j].type;
51076
51077 if(!isPlainObject(transformsIn[j])) {
51078 errorList.push(format('object', base, path));
51079 continue;
51080 }
51081
51082 var transformSchema = schema.transforms[transformType] ?
51083 schema.transforms[transformType].attributes :
51084 {};
51085
51086 // add 'type' to transform schema to validate the transform type
51087 transformSchema.type = {
51088 valType: 'enumerated',
51089 values: Object.keys(schema.transforms)
51090 };
51091
51092 crawl(transformsIn[j], transformsOut[j], transformSchema, errorList, base, path);
51093 }
51094 }
51095 }
51096
51097 var layoutOut = gd._fullLayout;
51098 var layoutSchema = fillLayoutSchema(schema, dataOut);
51099
51100 crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout');
51101
51102 // return undefined if no validation errors were found
51103 return (errorList.length === 0) ? void(0) : errorList;
51104};
51105
51106function crawl(objIn, objOut, schema, list, base, path) {
51107 path = path || [];
51108
51109 var keys = Object.keys(objIn);
51110
51111 for(var i = 0; i < keys.length; i++) {
51112 var k = keys[i];
51113
51114 // transforms are handled separately
51115 if(k === 'transforms') continue;
51116
51117 var p = path.slice();
51118 p.push(k);
51119
51120 var valIn = objIn[k];
51121 var valOut = objOut[k];
51122
51123 var nestedSchema = getNestedSchema(schema, k);
51124 var nestedValType = (nestedSchema || {}).valType;
51125 var isInfoArray = nestedValType === 'info_array';
51126 var isColorscale = nestedValType === 'colorscale';
51127 var items = (nestedSchema || {}).items;
51128
51129 if(!isInSchema(schema, k)) {
51130 list.push(format('schema', base, p));
51131 } else if(isPlainObject(valIn) && isPlainObject(valOut) && nestedValType !== 'any') {
51132 crawl(valIn, valOut, nestedSchema, list, base, p);
51133 } else if(isInfoArray && isArray(valIn)) {
51134 if(valIn.length > valOut.length) {
51135 list.push(format('unused', base, p.concat(valOut.length)));
51136 }
51137 var len = valOut.length;
51138 var arrayItems = Array.isArray(items);
51139 if(arrayItems) len = Math.min(len, items.length);
51140 var m, n, item, valInPart, valOutPart;
51141 if(nestedSchema.dimensions === 2) {
51142 for(n = 0; n < len; n++) {
51143 if(isArray(valIn[n])) {
51144 if(valIn[n].length > valOut[n].length) {
51145 list.push(format('unused', base, p.concat(n, valOut[n].length)));
51146 }
51147 var len2 = valOut[n].length;
51148 for(m = 0; m < (arrayItems ? Math.min(len2, items[n].length) : len2); m++) {
51149 item = arrayItems ? items[n][m] : items;
51150 valInPart = valIn[n][m];
51151 valOutPart = valOut[n][m];
51152 if(!Lib.validate(valInPart, item)) {
51153 list.push(format('value', base, p.concat(n, m), valInPart));
51154 } else if(valOutPart !== valInPart && valOutPart !== +valInPart) {
51155 list.push(format('dynamic', base, p.concat(n, m), valInPart, valOutPart));
51156 }
51157 }
51158 } else {
51159 list.push(format('array', base, p.concat(n), valIn[n]));
51160 }
51161 }
51162 } else {
51163 for(n = 0; n < len; n++) {
51164 item = arrayItems ? items[n] : items;
51165 valInPart = valIn[n];
51166 valOutPart = valOut[n];
51167 if(!Lib.validate(valInPart, item)) {
51168 list.push(format('value', base, p.concat(n), valInPart));
51169 } else if(valOutPart !== valInPart && valOutPart !== +valInPart) {
51170 list.push(format('dynamic', base, p.concat(n), valInPart, valOutPart));
51171 }
51172 }
51173 }
51174 } else if(nestedSchema.items && !isInfoArray && isArray(valIn)) {
51175 var _nestedSchema = items[Object.keys(items)[0]];
51176 var indexList = [];
51177
51178 var j, _p;
51179
51180 // loop over valOut items while keeping track of their
51181 // corresponding input container index (given by _index)
51182 for(j = 0; j < valOut.length; j++) {
51183 var _index = valOut[j]._index || j;
51184
51185 _p = p.slice();
51186 _p.push(_index);
51187
51188 if(isPlainObject(valIn[_index]) && isPlainObject(valOut[j])) {
51189 indexList.push(_index);
51190 var valInj = valIn[_index];
51191 var valOutj = valOut[j];
51192 if(isPlainObject(valInj) && valInj.visible !== false && valOutj.visible === false) {
51193 list.push(format('invisible', base, _p));
51194 } else crawl(valInj, valOutj, _nestedSchema, list, base, _p);
51195 }
51196 }
51197
51198 // loop over valIn to determine where it went wrong for some items
51199 for(j = 0; j < valIn.length; j++) {
51200 _p = p.slice();
51201 _p.push(j);
51202
51203 if(!isPlainObject(valIn[j])) {
51204 list.push(format('object', base, _p, valIn[j]));
51205 } else if(indexList.indexOf(j) === -1) {
51206 list.push(format('unused', base, _p));
51207 }
51208 }
51209 } else if(!isPlainObject(valIn) && isPlainObject(valOut)) {
51210 list.push(format('object', base, p, valIn));
51211 } else if(!isArrayOrTypedArray(valIn) && isArrayOrTypedArray(valOut) && !isInfoArray && !isColorscale) {
51212 list.push(format('array', base, p, valIn));
51213 } else if(!(k in objOut)) {
51214 list.push(format('unused', base, p, valIn));
51215 } else if(!Lib.validate(valIn, nestedSchema)) {
51216 list.push(format('value', base, p, valIn));
51217 } else if(nestedSchema.valType === 'enumerated' &&
51218 ((nestedSchema.coerceNumber && valIn !== +valOut) || valIn !== valOut)
51219 ) {
51220 list.push(format('dynamic', base, p, valIn, valOut));
51221 }
51222 }
51223
51224 return list;
51225}
51226
51227// the 'full' layout schema depends on the traces types presents
51228function fillLayoutSchema(schema, dataOut) {
51229 var layoutSchema = schema.layout.layoutAttributes;
51230
51231 for(var i = 0; i < dataOut.length; i++) {
51232 var traceOut = dataOut[i];
51233 var traceSchema = schema.traces[traceOut.type];
51234 var traceLayoutAttr = traceSchema.layoutAttributes;
51235
51236 if(traceLayoutAttr) {
51237 if(traceOut.subplot) {
51238 Lib.extendFlat(layoutSchema[traceSchema.attributes.subplot.dflt], traceLayoutAttr);
51239 } else {
51240 Lib.extendFlat(layoutSchema, traceLayoutAttr);
51241 }
51242 }
51243 }
51244
51245 return layoutSchema;
51246}
51247
51248// validation error codes
51249var code2msgFunc = {
51250 object: function(base, astr) {
51251 var prefix;
51252
51253 if(base === 'layout' && astr === '') prefix = 'The layout argument';
51254 else if(base[0] === 'data' && astr === '') {
51255 prefix = 'Trace ' + base[1] + ' in the data argument';
51256 } else prefix = inBase(base) + 'key ' + astr;
51257
51258 return prefix + ' must be linked to an object container';
51259 },
51260 array: function(base, astr) {
51261 var prefix;
51262
51263 if(base === 'data') prefix = 'The data argument';
51264 else prefix = inBase(base) + 'key ' + astr;
51265
51266 return prefix + ' must be linked to an array container';
51267 },
51268 schema: function(base, astr) {
51269 return inBase(base) + 'key ' + astr + ' is not part of the schema';
51270 },
51271 unused: function(base, astr, valIn) {
51272 var target = isPlainObject(valIn) ? 'container' : 'key';
51273
51274 return inBase(base) + target + ' ' + astr + ' did not get coerced';
51275 },
51276 dynamic: function(base, astr, valIn, valOut) {
51277 return [
51278 inBase(base) + 'key',
51279 astr,
51280 '(set to \'' + valIn + '\')',
51281 'got reset to',
51282 '\'' + valOut + '\'',
51283 'during defaults.'
51284 ].join(' ');
51285 },
51286 invisible: function(base, astr) {
51287 return (
51288 astr ? (inBase(base) + 'item ' + astr) : ('Trace ' + base[1])
51289 ) + ' got defaulted to be not visible';
51290 },
51291 value: function(base, astr, valIn) {
51292 return [
51293 inBase(base) + 'key ' + astr,
51294 'is set to an invalid value (' + valIn + ')'
51295 ].join(' ');
51296 }
51297};
51298
51299function inBase(base) {
51300 if(isArray(base)) return 'In data trace ' + base[1] + ', ';
51301
51302 return 'In ' + base + ', ';
51303}
51304
51305function format(code, base, path, valIn, valOut) {
51306 path = path || '';
51307
51308 var container, trace;
51309
51310 // container is either 'data' or 'layout
51311 // trace is the trace index if 'data', null otherwise
51312
51313 if(isArray(base)) {
51314 container = base[0];
51315 trace = base[1];
51316 } else {
51317 container = base;
51318 trace = null;
51319 }
51320
51321 var astr = convertPathToAttributeString(path);
51322 var msg = code2msgFunc[code](base, astr, valIn, valOut);
51323
51324 // log to console if logger config option is enabled
51325 Lib.log(msg);
51326
51327 return {
51328 code: code,
51329 container: container,
51330 trace: trace,
51331 path: path,
51332 astr: astr,
51333 msg: msg
51334 };
51335}
51336
51337function isInSchema(schema, key) {
51338 var parts = splitKey(key);
51339 var keyMinusId = parts.keyMinusId;
51340 var id = parts.id;
51341
51342 if((keyMinusId in schema) && schema[keyMinusId]._isSubplotObj && id) {
51343 return true;
51344 }
51345
51346 return (key in schema);
51347}
51348
51349function getNestedSchema(schema, key) {
51350 if(key in schema) return schema[key];
51351
51352 var parts = splitKey(key);
51353
51354 return schema[parts.keyMinusId];
51355}
51356
51357var idRegex = Lib.counterRegex('([a-z]+)');
51358
51359function splitKey(key) {
51360 var idMatch = key.match(idRegex);
51361
51362 return {
51363 keyMinusId: idMatch && idMatch[1],
51364 id: idMatch && idMatch[2]
51365 };
51366}
51367
51368function convertPathToAttributeString(path) {
51369 if(!isArray(path)) return String(path);
51370
51371 var astr = '';
51372
51373 for(var i = 0; i < path.length; i++) {
51374 var p = path[i];
51375
51376 if(typeof p === 'number') {
51377 astr = astr.substr(0, astr.length - 1) + '[' + p + ']';
51378 } else {
51379 astr += p;
51380 }
51381
51382 if(i < path.length - 1) astr += '.';
51383 }
51384
51385 return astr;
51386}
51387
51388},{"../lib":178,"../plots/plots":256,"./plot_config":210,"./plot_schema":211}],217:[function(_dereq_,module,exports){
51389/**
51390* Copyright 2012-2020, Plotly, Inc.
51391* All rights reserved.
51392*
51393* This source code is licensed under the MIT license found in the
51394* LICENSE file in the root directory of this source tree.
51395*/
51396
51397'use strict';
51398
51399module.exports = {
51400 mode: {
51401 valType: 'enumerated',
51402 dflt: 'afterall',
51403
51404 values: ['immediate', 'next', 'afterall'],
51405
51406 },
51407 direction: {
51408 valType: 'enumerated',
51409
51410 values: ['forward', 'reverse'],
51411 dflt: 'forward',
51412
51413 },
51414 fromcurrent: {
51415 valType: 'boolean',
51416 dflt: false,
51417
51418
51419 },
51420 frame: {
51421 duration: {
51422 valType: 'number',
51423
51424 min: 0,
51425 dflt: 500,
51426
51427 },
51428 redraw: {
51429 valType: 'boolean',
51430
51431 dflt: true,
51432
51433 },
51434 },
51435 transition: {
51436 duration: {
51437 valType: 'number',
51438
51439 min: 0,
51440 dflt: 500,
51441 editType: 'none',
51442
51443 },
51444 easing: {
51445 valType: 'enumerated',
51446 dflt: 'cubic-in-out',
51447 values: [
51448 'linear',
51449 'quad',
51450 'cubic',
51451 'sin',
51452 'exp',
51453 'circle',
51454 'elastic',
51455 'back',
51456 'bounce',
51457 'linear-in',
51458 'quad-in',
51459 'cubic-in',
51460 'sin-in',
51461 'exp-in',
51462 'circle-in',
51463 'elastic-in',
51464 'back-in',
51465 'bounce-in',
51466 'linear-out',
51467 'quad-out',
51468 'cubic-out',
51469 'sin-out',
51470 'exp-out',
51471 'circle-out',
51472 'elastic-out',
51473 'back-out',
51474 'bounce-out',
51475 'linear-in-out',
51476 'quad-in-out',
51477 'cubic-in-out',
51478 'sin-in-out',
51479 'exp-in-out',
51480 'circle-in-out',
51481 'elastic-in-out',
51482 'back-in-out',
51483 'bounce-in-out'
51484 ],
51485
51486 editType: 'none',
51487
51488 },
51489 ordering: {
51490 valType: 'enumerated',
51491 values: ['layout first', 'traces first'],
51492 dflt: 'layout first',
51493
51494 editType: 'none',
51495
51496 }
51497 }
51498};
51499
51500},{}],218:[function(_dereq_,module,exports){
51501/**
51502* Copyright 2012-2020, Plotly, Inc.
51503* All rights reserved.
51504*
51505* This source code is licensed under the MIT license found in the
51506* LICENSE file in the root directory of this source tree.
51507*/
51508
51509'use strict';
51510
51511var Lib = _dereq_('../lib');
51512var Template = _dereq_('../plot_api/plot_template');
51513
51514/** Convenience wrapper for making array container logic DRY and consistent
51515 *
51516 * @param {object} parentObjIn
51517 * user input object where the container in question is linked
51518 * (i.e. either a user trace object or the user layout object)
51519 *
51520 * @param {object} parentObjOut
51521 * full object where the coerced container will be linked
51522 * (i.e. either a full trace object or the full layout object)
51523 *
51524 * @param {object} opts
51525 * options object:
51526 * - name {string}
51527 * name of the key linking the container in question
51528 * - inclusionAttr {string}
51529 * name of the item attribute for inclusion/exclusion. Default is 'visible'.
51530 * Since inclusion is true, use eg 'enabled' instead of 'disabled'.
51531 * - handleItemDefaults {function}
51532 * defaults method to be called on each item in the array container in question
51533 *
51534 * Its arguments are:
51535 * - itemIn {object} item in user layout
51536 * - itemOut {object} item in full layout
51537 * - parentObj {object} (as in closure)
51538 * - opts {object} (as in closure)
51539 * N.B.
51540 *
51541 * - opts is passed to handleItemDefaults so it can also store
51542 * links to supplementary data (e.g. fullData for layout components)
51543 *
51544 */
51545module.exports = function handleArrayContainerDefaults(parentObjIn, parentObjOut, opts) {
51546 var name = opts.name;
51547 var inclusionAttr = opts.inclusionAttr || 'visible';
51548
51549 var previousContOut = parentObjOut[name];
51550
51551 var contIn = Lib.isArrayOrTypedArray(parentObjIn[name]) ? parentObjIn[name] : [];
51552 var contOut = parentObjOut[name] = [];
51553 var templater = Template.arrayTemplater(parentObjOut, name, inclusionAttr);
51554 var i, itemOut;
51555
51556 for(i = 0; i < contIn.length; i++) {
51557 var itemIn = contIn[i];
51558
51559 if(!Lib.isPlainObject(itemIn)) {
51560 itemOut = templater.newItem({});
51561 itemOut[inclusionAttr] = false;
51562 } else {
51563 itemOut = templater.newItem(itemIn);
51564 }
51565
51566 itemOut._index = i;
51567
51568 if(itemOut[inclusionAttr] !== false) {
51569 opts.handleItemDefaults(itemIn, itemOut, parentObjOut, opts);
51570 }
51571
51572 contOut.push(itemOut);
51573 }
51574
51575 var defaultItems = templater.defaultItems();
51576 for(i = 0; i < defaultItems.length; i++) {
51577 itemOut = defaultItems[i];
51578 itemOut._index = contOut.length;
51579 opts.handleItemDefaults({}, itemOut, parentObjOut, opts, {});
51580 contOut.push(itemOut);
51581 }
51582
51583 // in case this array gets its defaults rebuilt independent of the whole layout,
51584 // relink the private keys just for this array.
51585 if(Lib.isArrayOrTypedArray(previousContOut)) {
51586 var len = Math.min(previousContOut.length, contOut.length);
51587 for(i = 0; i < len; i++) {
51588 Lib.relinkPrivateKeys(contOut[i], previousContOut[i]);
51589 }
51590 }
51591
51592 return contOut;
51593};
51594
51595},{"../lib":178,"../plot_api/plot_template":212}],219:[function(_dereq_,module,exports){
51596/**
51597* Copyright 2012-2020, Plotly, Inc.
51598* All rights reserved.
51599*
51600* This source code is licensed under the MIT license found in the
51601* LICENSE file in the root directory of this source tree.
51602*/
51603
51604'use strict';
51605
51606var fxAttrs = _dereq_('../components/fx/attributes');
51607
51608module.exports = {
51609 type: {
51610 valType: 'enumerated',
51611
51612 values: [], // listed dynamically
51613 dflt: 'scatter',
51614 editType: 'calc+clearAxisTypes',
51615 _noTemplating: true // we handle this at a higher level
51616 },
51617 visible: {
51618 valType: 'enumerated',
51619 values: [true, false, 'legendonly'],
51620
51621 dflt: true,
51622 editType: 'calc',
51623
51624 },
51625 showlegend: {
51626 valType: 'boolean',
51627
51628 dflt: true,
51629 editType: 'style',
51630
51631 },
51632 legendgroup: {
51633 valType: 'string',
51634
51635 dflt: '',
51636 editType: 'style',
51637
51638 },
51639 opacity: {
51640 valType: 'number',
51641
51642 min: 0,
51643 max: 1,
51644 dflt: 1,
51645 editType: 'style',
51646
51647 },
51648 name: {
51649 valType: 'string',
51650
51651 editType: 'style',
51652
51653 },
51654 uid: {
51655 valType: 'string',
51656
51657 editType: 'plot',
51658 anim: true,
51659
51660 },
51661 ids: {
51662 valType: 'data_array',
51663 editType: 'calc',
51664 anim: true,
51665
51666 },
51667 customdata: {
51668 valType: 'data_array',
51669 editType: 'calc',
51670
51671 },
51672 meta: {
51673 valType: 'any',
51674 arrayOk: true,
51675
51676 editType: 'plot',
51677
51678 },
51679
51680 // N.B. these cannot be 'data_array' as they do not have the same length as
51681 // other data arrays and arrayOk attributes in general
51682 //
51683 // Maybe add another valType:
51684 // https://github.com/plotly/plotly.js/issues/1894
51685 selectedpoints: {
51686 valType: 'any',
51687
51688 editType: 'calc',
51689
51690 },
51691
51692 hoverinfo: {
51693 valType: 'flaglist',
51694
51695 flags: ['x', 'y', 'z', 'text', 'name'],
51696 extras: ['all', 'none', 'skip'],
51697 arrayOk: true,
51698 dflt: 'all',
51699 editType: 'none',
51700
51701 },
51702 hoverlabel: fxAttrs.hoverlabel,
51703 stream: {
51704 token: {
51705 valType: 'string',
51706 noBlank: true,
51707 strict: true,
51708
51709 editType: 'calc',
51710
51711 },
51712 maxpoints: {
51713 valType: 'number',
51714 min: 0,
51715 max: 10000,
51716 dflt: 500,
51717
51718 editType: 'calc',
51719
51720 },
51721 editType: 'calc'
51722 },
51723 transforms: {
51724 _isLinkedToArray: 'transform',
51725 editType: 'calc',
51726
51727 },
51728 uirevision: {
51729 valType: 'any',
51730
51731 editType: 'none',
51732
51733 }
51734};
51735
51736},{"../components/fx/attributes":83}],220:[function(_dereq_,module,exports){
51737/**
51738* Copyright 2012-2020, Plotly, Inc.
51739* All rights reserved.
51740*
51741* This source code is licensed under the MIT license found in the
51742* LICENSE file in the root directory of this source tree.
51743*/
51744
51745'use strict';
51746
51747
51748module.exports = {
51749 xaxis: {
51750 valType: 'subplotid',
51751
51752 dflt: 'x',
51753 editType: 'calc+clearAxisTypes',
51754
51755 },
51756 yaxis: {
51757 valType: 'subplotid',
51758
51759 dflt: 'y',
51760 editType: 'calc+clearAxisTypes',
51761
51762 }
51763};
51764
51765},{}],221:[function(_dereq_,module,exports){
51766/**
51767* Copyright 2012-2020, Plotly, Inc.
51768* All rights reserved.
51769*
51770* This source code is licensed under the MIT license found in the
51771* LICENSE file in the root directory of this source tree.
51772*/
51773
51774'use strict';
51775
51776var isNumeric = _dereq_('fast-isnumeric');
51777
51778var Lib = _dereq_('../../lib');
51779var FP_SAFE = _dereq_('../../constants/numerical').FP_SAFE;
51780var Registry = _dereq_('../../registry');
51781
51782module.exports = {
51783 getAutoRange: getAutoRange,
51784 makePadFn: makePadFn,
51785 doAutoRange: doAutoRange,
51786 findExtremes: findExtremes,
51787 concatExtremes: concatExtremes
51788};
51789
51790/**
51791 * getAutoRange
51792 *
51793 * Collects all _extremes values corresponding to a given axis
51794 * and computes its auto range.
51795 *
51796 * Note that getAutoRange uses return values from findExtremes.
51797 *
51798 * @param {object} gd:
51799 * graph div object with filled-in fullData and fullLayout, in particular
51800 * with filled-in '_extremes' containers:
51801 * {
51802 * val: calcdata value,
51803 * pad: extra pixels beyond this value,
51804 * extrapad: bool, does this point want 5% extra padding
51805 * }
51806 * @param {object} ax:
51807 * full axis object, in particular with filled-in '_traceIndices'
51808 * and '_annIndices' / '_shapeIndices' if applicable
51809 * @return {array}
51810 * an array of [min, max]. These are calcdata for log and category axes
51811 * and data for linear and date axes.
51812 *
51813 * TODO: we want to change log to data as well, but it's hard to do this
51814 * maintaining backward compatibility. category will always have to use calcdata
51815 * though, because otherwise values between categories (or outside all categories)
51816 * would be impossible.
51817 */
51818function getAutoRange(gd, ax) {
51819 var i, j;
51820 var newRange = [];
51821
51822 var getPad = makePadFn(ax);
51823 var extremes = concatExtremes(gd, ax);
51824 var minArray = extremes.min;
51825 var maxArray = extremes.max;
51826
51827 if(minArray.length === 0 || maxArray.length === 0) {
51828 return Lib.simpleMap(ax.range, ax.r2l);
51829 }
51830
51831 var minmin = minArray[0].val;
51832 var maxmax = maxArray[0].val;
51833
51834 for(i = 1; i < minArray.length; i++) {
51835 if(minmin !== maxmax) break;
51836 minmin = Math.min(minmin, minArray[i].val);
51837 }
51838 for(i = 1; i < maxArray.length; i++) {
51839 if(minmin !== maxmax) break;
51840 maxmax = Math.max(maxmax, maxArray[i].val);
51841 }
51842
51843 var axReverse = false;
51844
51845 if(ax.range) {
51846 var rng = Lib.simpleMap(ax.range, ax.r2l);
51847 axReverse = rng[1] < rng[0];
51848 }
51849 // one-time setting to easily reverse the axis
51850 // when plotting from code
51851 if(ax.autorange === 'reversed') {
51852 axReverse = true;
51853 ax.autorange = true;
51854 }
51855
51856 var rangeMode = ax.rangemode;
51857 var toZero = rangeMode === 'tozero';
51858 var nonNegative = rangeMode === 'nonnegative';
51859 var axLen = ax._length;
51860 // don't allow padding to reduce the data to < 10% of the length
51861 var minSpan = axLen / 10;
51862
51863 // find axis rangebreaks in [v0,v1] and compute its length in value space
51864 var calcBreaksLength = function(v0, v1) {
51865 var lBreaks = 0;
51866 if(ax.rangebreaks) {
51867 var rangebreaksOut = ax.locateBreaks(v0, v1);
51868 for(var i = 0; i < rangebreaksOut.length; i++) {
51869 var brk = rangebreaksOut[i];
51870 lBreaks += brk.max - brk.min;
51871 }
51872 }
51873 return lBreaks;
51874 };
51875
51876 var mbest = 0;
51877 var minpt, maxpt, minbest, maxbest, dp, dv;
51878
51879 for(i = 0; i < minArray.length; i++) {
51880 minpt = minArray[i];
51881 for(j = 0; j < maxArray.length; j++) {
51882 maxpt = maxArray[j];
51883 dv = maxpt.val - minpt.val - calcBreaksLength(minpt.val, maxpt.val);
51884 if(dv > 0) {
51885 dp = axLen - getPad(minpt) - getPad(maxpt);
51886 if(dp > minSpan) {
51887 if(dv / dp > mbest) {
51888 minbest = minpt;
51889 maxbest = maxpt;
51890 mbest = dv / dp;
51891 }
51892 } else if(dv / axLen > mbest) {
51893 // in case of padding longer than the axis
51894 // at least include the unpadded data values.
51895 minbest = {val: minpt.val, pad: 0};
51896 maxbest = {val: maxpt.val, pad: 0};
51897 mbest = dv / axLen;
51898 }
51899 }
51900 }
51901 }
51902
51903 function getMaxPad(prev, pt) {
51904 return Math.max(prev, getPad(pt));
51905 }
51906
51907 if(minmin === maxmax) {
51908 var lower = minmin - 1;
51909 var upper = minmin + 1;
51910 if(toZero) {
51911 if(minmin === 0) {
51912 // The only value we have on this axis is 0, and we want to
51913 // autorange so zero is one end.
51914 // In principle this could be [0, 1] or [-1, 0] but usually
51915 // 'tozero' pins 0 to the low end, so follow that.
51916 newRange = [0, 1];
51917 } else {
51918 var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0);
51919 // we're pushing a single value away from the edge due to its
51920 // padding, with the other end clamped at zero
51921 // 0.5 means don't push it farther than the center.
51922 var rangeEnd = minmin / (1 - Math.min(0.5, maxPad / axLen));
51923 newRange = minmin > 0 ? [0, rangeEnd] : [rangeEnd, 0];
51924 }
51925 } else if(nonNegative) {
51926 newRange = [Math.max(0, lower), Math.max(1, upper)];
51927 } else {
51928 newRange = [lower, upper];
51929 }
51930 } else {
51931 if(toZero) {
51932 if(minbest.val >= 0) {
51933 minbest = {val: 0, pad: 0};
51934 }
51935 if(maxbest.val <= 0) {
51936 maxbest = {val: 0, pad: 0};
51937 }
51938 } else if(nonNegative) {
51939 if(minbest.val - mbest * getPad(minbest) < 0) {
51940 minbest = {val: 0, pad: 0};
51941 }
51942 if(maxbest.val <= 0) {
51943 maxbest = {val: 1, pad: 0};
51944 }
51945 }
51946
51947 // in case it changed again...
51948 mbest = (maxbest.val - minbest.val - calcBreaksLength(minpt.val, maxpt.val)) /
51949 (axLen - getPad(minbest) - getPad(maxbest));
51950
51951 newRange = [
51952 minbest.val - mbest * getPad(minbest),
51953 maxbest.val + mbest * getPad(maxbest)
51954 ];
51955 }
51956
51957 // maintain reversal
51958 if(axReverse) newRange.reverse();
51959
51960 return Lib.simpleMap(newRange, ax.l2r || Number);
51961}
51962
51963/*
51964 * calculate the pixel padding for ax._min and ax._max entries with
51965 * optional extrapad as 5% of the total axis length
51966 */
51967function makePadFn(ax) {
51968 // 5% padding for points that specify extrapad: true
51969 var extrappad = ax._length / 20;
51970
51971 // domain-constrained axes: base extrappad on the unconstrained
51972 // domain so it's consistent as the domain changes
51973 if((ax.constrain === 'domain') && ax._inputDomain) {
51974 extrappad *= (ax._inputDomain[1] - ax._inputDomain[0]) /
51975 (ax.domain[1] - ax.domain[0]);
51976 }
51977
51978 return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); };
51979}
51980
51981function concatExtremes(gd, ax) {
51982 var axId = ax._id;
51983 var fullData = gd._fullData;
51984 var fullLayout = gd._fullLayout;
51985 var minArray = [];
51986 var maxArray = [];
51987 var i, j, d;
51988
51989 function _concat(cont, indices) {
51990 for(i = 0; i < indices.length; i++) {
51991 var item = cont[indices[i]];
51992 var extremes = (item._extremes || {})[axId];
51993 if(item.visible === true && extremes) {
51994 for(j = 0; j < extremes.min.length; j++) {
51995 d = extremes.min[j];
51996 collapseMinArray(minArray, d.val, d.pad, {extrapad: d.extrapad});
51997 }
51998 for(j = 0; j < extremes.max.length; j++) {
51999 d = extremes.max[j];
52000 collapseMaxArray(maxArray, d.val, d.pad, {extrapad: d.extrapad});
52001 }
52002 }
52003 }
52004 }
52005
52006 _concat(fullData, ax._traceIndices);
52007 _concat(fullLayout.annotations || [], ax._annIndices || []);
52008 _concat(fullLayout.shapes || [], ax._shapeIndices || []);
52009
52010 return {min: minArray, max: maxArray};
52011}
52012
52013function doAutoRange(gd, ax) {
52014 ax.setScale();
52015
52016 if(ax.autorange) {
52017 ax.range = getAutoRange(gd, ax);
52018
52019 ax._r = ax.range.slice();
52020 ax._rl = Lib.simpleMap(ax._r, ax.r2l);
52021
52022 // doAutoRange will get called on fullLayout,
52023 // but we want to report its results back to layout
52024
52025 var axIn = ax._input;
52026
52027 // before we edit _input, store preGUI values
52028 var edits = {};
52029 edits[ax._attr + '.range'] = ax.range;
52030 edits[ax._attr + '.autorange'] = ax.autorange;
52031 Registry.call('_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, edits);
52032
52033 axIn.range = ax.range.slice();
52034 axIn.autorange = ax.autorange;
52035 }
52036
52037 var anchorAx = ax._anchorAxis;
52038
52039 if(anchorAx && anchorAx.rangeslider) {
52040 var axeRangeOpts = anchorAx.rangeslider[ax._name];
52041 if(axeRangeOpts) {
52042 if(axeRangeOpts.rangemode === 'auto') {
52043 axeRangeOpts.range = getAutoRange(gd, ax);
52044 }
52045 }
52046 anchorAx._input.rangeslider[ax._name] = Lib.extendFlat({}, axeRangeOpts);
52047 }
52048}
52049
52050/**
52051 * findExtremes
52052 *
52053 * Find min/max extremes of an array of coordinates on a given axis.
52054 *
52055 * Note that findExtremes is called during `calc`, when we don't yet know the axis
52056 * length; all the inputs should be based solely on the trace data, nothing
52057 * about the axis layout.
52058 *
52059 * Note that `ppad` and `vpad` as well as their asymmetric variants refer to
52060 * the before and after padding of the passed `data` array, not to the whole axis.
52061 *
52062 * @param {object} ax: full axis object
52063 * relies on
52064 * - ax.type
52065 * - ax._m (just its sign)
52066 * - ax.d2l
52067 * @param {array} data:
52068 * array of numbers (i.e. already run though ax.d2c)
52069 * @param {object} opts:
52070 * available keys are:
52071 * vpad: (number or number array) pad values (data value +-vpad)
52072 * ppad: (number or number array) pad pixels (pixel location +-ppad)
52073 * ppadplus, ppadminus, vpadplus, vpadminus:
52074 * separate padding for each side, overrides symmetric
52075 * padded: (boolean) add 5% padding to both ends
52076 * (unless one end is overridden by tozero)
52077 * tozero: (boolean) make sure to include zero if axis is linear,
52078 * and make it a tight bound if possible
52079 * vpadLinearized: (boolean) whether or not vpad (or vpadplus/vpadminus)
52080 * is linearized (for log scale axes)
52081 *
52082 * @return {object}
52083 * - min {array of objects}
52084 * - max {array of objects}
52085 * each object item has fields:
52086 * - val {number}
52087 * - pad {number}
52088 * - extrappad {number}
52089 * - opts {object}: a ref to the passed "options" object
52090 */
52091function findExtremes(ax, data, opts) {
52092 if(!opts) opts = {};
52093 if(!ax._m) ax.setScale();
52094
52095 var minArray = [];
52096 var maxArray = [];
52097
52098 var len = data.length;
52099 var extrapad = opts.padded || false;
52100 var tozero = opts.tozero && (ax.type === 'linear' || ax.type === '-');
52101 var isLog = ax.type === 'log';
52102 var hasArrayOption = false;
52103 var vpadLinearized = opts.vpadLinearized || false;
52104 var i, v, di, dmin, dmax, ppadiplus, ppadiminus, vmin, vmax;
52105
52106 function makePadAccessor(item) {
52107 if(Array.isArray(item)) {
52108 hasArrayOption = true;
52109 return function(i) { return Math.max(Number(item[i]||0), 0); };
52110 } else {
52111 var v = Math.max(Number(item||0), 0);
52112 return function() { return v; };
52113 }
52114 }
52115
52116 var ppadplus = makePadAccessor((ax._m > 0 ?
52117 opts.ppadplus : opts.ppadminus) || opts.ppad || 0);
52118 var ppadminus = makePadAccessor((ax._m > 0 ?
52119 opts.ppadminus : opts.ppadplus) || opts.ppad || 0);
52120 var vpadplus = makePadAccessor(opts.vpadplus || opts.vpad);
52121 var vpadminus = makePadAccessor(opts.vpadminus || opts.vpad);
52122
52123 if(!hasArrayOption) {
52124 // with no arrays other than `data` we don't need to consider
52125 // every point, only the extreme data points
52126 vmin = Infinity;
52127 vmax = -Infinity;
52128
52129 if(isLog) {
52130 for(i = 0; i < len; i++) {
52131 v = data[i];
52132 // data is not linearized yet so we still have to filter out negative logs
52133 if(v < vmin && v > 0) vmin = v;
52134 if(v > vmax && v < FP_SAFE) vmax = v;
52135 }
52136 } else {
52137 for(i = 0; i < len; i++) {
52138 v = data[i];
52139 if(v < vmin && v > -FP_SAFE) vmin = v;
52140 if(v > vmax && v < FP_SAFE) vmax = v;
52141 }
52142 }
52143
52144 data = [vmin, vmax];
52145 len = 2;
52146 }
52147
52148 var collapseOpts = {tozero: tozero, extrapad: extrapad};
52149
52150 function addItem(i) {
52151 di = data[i];
52152 if(!isNumeric(di)) return;
52153 ppadiplus = ppadplus(i);
52154 ppadiminus = ppadminus(i);
52155
52156 if(vpadLinearized) {
52157 dmin = ax.c2l(di) - vpadminus(i);
52158 dmax = ax.c2l(di) + vpadplus(i);
52159 } else {
52160 vmin = di - vpadminus(i);
52161 vmax = di + vpadplus(i);
52162 // special case for log axes: if vpad makes this object span
52163 // more than an order of mag, clip it to one order. This is so
52164 // we don't have non-positive errors or absurdly large lower
52165 // range due to rounding errors
52166 if(isLog && vmin < vmax / 10) vmin = vmax / 10;
52167
52168 dmin = ax.c2l(vmin);
52169 dmax = ax.c2l(vmax);
52170 }
52171
52172 if(tozero) {
52173 dmin = Math.min(0, dmin);
52174 dmax = Math.max(0, dmax);
52175 }
52176 if(goodNumber(dmin)) {
52177 collapseMinArray(minArray, dmin, ppadiminus, collapseOpts);
52178 }
52179 if(goodNumber(dmax)) {
52180 collapseMaxArray(maxArray, dmax, ppadiplus, collapseOpts);
52181 }
52182 }
52183
52184 // For efficiency covering monotonic or near-monotonic data,
52185 // check a few points at both ends first and then sweep
52186 // through the middle
52187 var iMax = Math.min(6, len);
52188 for(i = 0; i < iMax; i++) addItem(i);
52189 for(i = len - 1; i >= iMax; i--) addItem(i);
52190
52191 return {
52192 min: minArray,
52193 max: maxArray,
52194 opts: opts
52195 };
52196}
52197
52198function collapseMinArray(array, newVal, newPad, opts) {
52199 collapseArray(array, newVal, newPad, opts, lessOrEqual);
52200}
52201
52202function collapseMaxArray(array, newVal, newPad, opts) {
52203 collapseArray(array, newVal, newPad, opts, greaterOrEqual);
52204}
52205
52206/**
52207 * collapseArray
52208 *
52209 * Takes items from 'array' and compares them to 'newVal', 'newPad'.
52210 *
52211 * @param {array} array:
52212 * current set of min or max extremes
52213 * @param {number} newVal:
52214 * new value to compare against
52215 * @param {number} newPad:
52216 * pad value associated with 'newVal'
52217 * @param {object} opts:
52218 * - tozero {boolean}
52219 * - extrapad {number}
52220 * @param {function} atLeastAsExtreme:
52221 * comparison function, use
52222 * - lessOrEqual for min 'array' and
52223 * - greaterOrEqual for max 'array'
52224 *
52225 * In practice, 'array' is either
52226 * - 'extremes[ax._id].min' or
52227 * - 'extremes[ax._id].max
52228 * found in traces and layout items that affect autorange.
52229 *
52230 * Since we don't yet know the relationship between pixels and values
52231 * (that's what we're trying to figure out!) AND we don't yet know how
52232 * many pixels `extrapad` represents (it's going to be 5% of the length,
52233 * but we don't want to have to redo calc just because length changed)
52234 * two point must satisfy three criteria simultaneously for one to supersede the other:
52235 * - at least as extreme a `val`
52236 * - at least as big a `pad`
52237 * - an unpadded point cannot supersede a padded point, but any other combination can
52238 *
52239 * Then:
52240 * - If the item supersedes the new point, set includeThis false
52241 * - If the new pt supersedes the item, delete it from 'array'
52242 */
52243function collapseArray(array, newVal, newPad, opts, atLeastAsExtreme) {
52244 var tozero = opts.tozero;
52245 var extrapad = opts.extrapad;
52246 var includeThis = true;
52247
52248 for(var j = 0; j < array.length && includeThis; j++) {
52249 var v = array[j];
52250 if(atLeastAsExtreme(v.val, newVal) && v.pad >= newPad && (v.extrapad || !extrapad)) {
52251 includeThis = false;
52252 break;
52253 } else if(atLeastAsExtreme(newVal, v.val) && v.pad <= newPad && (extrapad || !v.extrapad)) {
52254 array.splice(j, 1);
52255 j--;
52256 }
52257 }
52258 if(includeThis) {
52259 var clipAtZero = (tozero && newVal === 0);
52260 array.push({
52261 val: newVal,
52262 pad: clipAtZero ? 0 : newPad,
52263 extrapad: clipAtZero ? false : extrapad
52264 });
52265 }
52266}
52267
52268// In order to stop overflow errors, don't consider points
52269// too close to the limits of js floating point
52270function goodNumber(v) {
52271 return isNumeric(v) && Math.abs(v) < FP_SAFE;
52272}
52273
52274function lessOrEqual(v0, v1) { return v0 <= v1; }
52275function greaterOrEqual(v0, v1) { return v0 >= v1; }
52276
52277},{"../../constants/numerical":158,"../../lib":178,"../../registry":269,"fast-isnumeric":18}],222:[function(_dereq_,module,exports){
52278/**
52279* Copyright 2012-2020, Plotly, Inc.
52280* All rights reserved.
52281*
52282* This source code is licensed under the MIT license found in the
52283* LICENSE file in the root directory of this source tree.
52284*/
52285
52286'use strict';
52287
52288var d3 = _dereq_('d3');
52289var isNumeric = _dereq_('fast-isnumeric');
52290var Plots = _dereq_('../../plots/plots');
52291
52292var Registry = _dereq_('../../registry');
52293var Lib = _dereq_('../../lib');
52294var svgTextUtils = _dereq_('../../lib/svg_text_utils');
52295var Titles = _dereq_('../../components/titles');
52296var Color = _dereq_('../../components/color');
52297var Drawing = _dereq_('../../components/drawing');
52298
52299var axAttrs = _dereq_('./layout_attributes');
52300var cleanTicks = _dereq_('./clean_ticks');
52301
52302var constants = _dereq_('../../constants/numerical');
52303var ONEAVGYEAR = constants.ONEAVGYEAR;
52304var ONEAVGMONTH = constants.ONEAVGMONTH;
52305var ONEDAY = constants.ONEDAY;
52306var ONEHOUR = constants.ONEHOUR;
52307var ONEMIN = constants.ONEMIN;
52308var ONESEC = constants.ONESEC;
52309var MINUS_SIGN = constants.MINUS_SIGN;
52310var BADNUM = constants.BADNUM;
52311
52312var alignmentConstants = _dereq_('../../constants/alignment');
52313var MID_SHIFT = alignmentConstants.MID_SHIFT;
52314var CAP_SHIFT = alignmentConstants.CAP_SHIFT;
52315var LINE_SPACING = alignmentConstants.LINE_SPACING;
52316var OPPOSITE_SIDE = alignmentConstants.OPPOSITE_SIDE;
52317
52318var axes = module.exports = {};
52319
52320axes.setConvert = _dereq_('./set_convert');
52321var autoType = _dereq_('./axis_autotype');
52322
52323var axisIds = _dereq_('./axis_ids');
52324axes.id2name = axisIds.id2name;
52325axes.name2id = axisIds.name2id;
52326axes.cleanId = axisIds.cleanId;
52327axes.list = axisIds.list;
52328axes.listIds = axisIds.listIds;
52329axes.getFromId = axisIds.getFromId;
52330axes.getFromTrace = axisIds.getFromTrace;
52331
52332var autorange = _dereq_('./autorange');
52333axes.getAutoRange = autorange.getAutoRange;
52334axes.findExtremes = autorange.findExtremes;
52335
52336var epsilon = 0.0001;
52337function expandRange(range) {
52338 var delta = (range[1] - range[0]) * epsilon;
52339 return [
52340 range[0] - delta,
52341 range[1] + delta
52342 ];
52343}
52344
52345/*
52346 * find the list of possible axes to reference with an xref or yref attribute
52347 * and coerce it to that list
52348 *
52349 * attr: the attribute we're generating a reference for. Should end in 'x' or 'y'
52350 * but can be prefixed, like 'ax' for annotation's arrow x
52351 * dflt: the default to coerce to, or blank to use the first axis (falling back on
52352 * extraOption if there is no axis)
52353 * extraOption: aside from existing axes with this letter, what non-axis value is allowed?
52354 * Only required if it's different from `dflt`
52355 */
52356axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
52357 var axLetter = attr.charAt(attr.length - 1);
52358 var axlist = gd._fullLayout._subplots[axLetter + 'axis'];
52359 var refAttr = attr + 'ref';
52360 var attrDef = {};
52361
52362 if(!dflt) dflt = axlist[0] || extraOption;
52363 if(!extraOption) extraOption = dflt;
52364
52365 // data-ref annotations are not supported in gl2d yet
52366
52367 attrDef[refAttr] = {
52368 valType: 'enumerated',
52369 values: axlist.concat(extraOption ? [extraOption] : []),
52370 dflt: dflt
52371 };
52372
52373 // xref, yref
52374 return Lib.coerce(containerIn, containerOut, attrDef, refAttr);
52375};
52376
52377/*
52378 * coerce position attributes (range-type) that can be either on axes or absolute
52379 * (paper or pixel) referenced. The biggest complication here is that we don't know
52380 * before looking at the axis whether the value must be a number or not (it may be
52381 * a date string), so we can't use the regular valType='number' machinery
52382 *
52383 * axRef (string): the axis this position is referenced to, or:
52384 * paper: fraction of the plot area
52385 * pixel: pixels relative to some starting position
52386 * attr (string): the attribute in containerOut we are coercing
52387 * dflt (number): the default position, as a fraction or pixels. If the attribute
52388 * is to be axis-referenced, this will be converted to an axis data value
52389 *
52390 * Also cleans the values, since the attribute definition itself has to say
52391 * valType: 'any' to handle date axes. This allows us to accept:
52392 * - for category axes: category names, and convert them here into serial numbers.
52393 * Note that this will NOT work for axis range endpoints, because we don't know
52394 * the category list yet (it's set by ax.makeCalcdata during calc)
52395 * but it works for component (note, shape, images) positions.
52396 * - for date axes: JS Dates or milliseconds, and convert to date strings
52397 * - for other types: coerce them to numbers
52398 */
52399axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) {
52400 var cleanPos, pos;
52401
52402 if(axRef === 'paper' || axRef === 'pixel') {
52403 cleanPos = Lib.ensureNumber;
52404 pos = coerce(attr, dflt);
52405 } else {
52406 var ax = axes.getFromId(gd, axRef);
52407 dflt = ax.fraction2r(dflt);
52408 pos = coerce(attr, dflt);
52409 cleanPos = ax.cleanPos;
52410 }
52411
52412 containerOut[attr] = cleanPos(pos);
52413};
52414
52415axes.cleanPosition = function(pos, gd, axRef) {
52416 var cleanPos = (axRef === 'paper' || axRef === 'pixel') ?
52417 Lib.ensureNumber :
52418 axes.getFromId(gd, axRef).cleanPos;
52419
52420 return cleanPos(pos);
52421};
52422
52423axes.redrawComponents = function(gd, axIds) {
52424 axIds = axIds ? axIds : axes.listIds(gd);
52425
52426 var fullLayout = gd._fullLayout;
52427
52428 function _redrawOneComp(moduleName, methodName, stashName, shortCircuit) {
52429 var method = Registry.getComponentMethod(moduleName, methodName);
52430 var stash = {};
52431
52432 for(var i = 0; i < axIds.length; i++) {
52433 var ax = fullLayout[axes.id2name(axIds[i])];
52434 var indices = ax[stashName];
52435
52436 for(var j = 0; j < indices.length; j++) {
52437 var ind = indices[j];
52438
52439 if(!stash[ind]) {
52440 method(gd, ind);
52441 stash[ind] = 1;
52442 // once is enough for images (which doesn't use the `i` arg anyway)
52443 if(shortCircuit) return;
52444 }
52445 }
52446 }
52447 }
52448
52449 // annotations and shapes 'draw' method is slow,
52450 // use the finer-grained 'drawOne' method instead
52451 _redrawOneComp('annotations', 'drawOne', '_annIndices');
52452 _redrawOneComp('shapes', 'drawOne', '_shapeIndices');
52453 _redrawOneComp('images', 'draw', '_imgIndices', true);
52454};
52455
52456var getDataConversions = axes.getDataConversions = function(gd, trace, target, targetArray) {
52457 var ax;
52458
52459 // If target points to an axis, use the type we already have for that
52460 // axis to find the data type. Otherwise use the values to autotype.
52461 var d2cTarget = (target === 'x' || target === 'y' || target === 'z') ?
52462 target :
52463 targetArray;
52464
52465 // In the case of an array target, make a mock data array
52466 // and call supplyDefaults to the data type and
52467 // setup the data-to-calc method.
52468 if(Array.isArray(d2cTarget)) {
52469 ax = {
52470 type: autoType(targetArray),
52471 _categories: []
52472 };
52473 axes.setConvert(ax);
52474
52475 // build up ax._categories (usually done during ax.makeCalcdata()
52476 if(ax.type === 'category') {
52477 for(var i = 0; i < targetArray.length; i++) {
52478 ax.d2c(targetArray[i]);
52479 }
52480 }
52481 // TODO what to do for transforms?
52482 } else {
52483 ax = axes.getFromTrace(gd, trace, d2cTarget);
52484 }
52485
52486 // if 'target' has corresponding axis
52487 // -> use setConvert method
52488 if(ax) return {d2c: ax.d2c, c2d: ax.c2d};
52489
52490 // special case for 'ids'
52491 // -> cast to String
52492 if(d2cTarget === 'ids') return {d2c: toString, c2d: toString};
52493
52494 // otherwise (e.g. numeric-array of 'marker.color' or 'marker.size')
52495 // -> cast to Number
52496
52497 return {d2c: toNum, c2d: toNum};
52498};
52499
52500function toNum(v) { return +v; }
52501function toString(v) { return String(v); }
52502
52503axes.getDataToCoordFunc = function(gd, trace, target, targetArray) {
52504 return getDataConversions(gd, trace, target, targetArray).d2c;
52505};
52506
52507// get counteraxis letter for this axis (name or id)
52508// this can also be used as the id for default counter axis
52509axes.counterLetter = function(id) {
52510 var axLetter = id.charAt(0);
52511 if(axLetter === 'x') return 'y';
52512 if(axLetter === 'y') return 'x';
52513};
52514
52515// incorporate a new minimum difference and first tick into
52516// forced
52517// note that _forceTick0 is linearized, so needs to be turned into
52518// a range value for setting tick0
52519axes.minDtick = function(ax, newDiff, newFirst, allow) {
52520 // doesn't make sense to do forced min dTick on log or category axes,
52521 // and the plot itself may decide to cancel (ie non-grouped bars)
52522 if(['log', 'category', 'multicategory'].indexOf(ax.type) !== -1 || !allow) {
52523 ax._minDtick = 0;
52524 } else if(ax._minDtick === undefined) {
52525 // undefined means there's nothing there yet
52526
52527 ax._minDtick = newDiff;
52528 ax._forceTick0 = newFirst;
52529 } else if(ax._minDtick) {
52530 if((ax._minDtick / newDiff + 1e-6) % 1 < 2e-6 &&
52531 // existing minDtick is an integer multiple of newDiff
52532 // (within rounding err)
52533 // and forceTick0 can be shifted to newFirst
52534
52535 (((newFirst - ax._forceTick0) / newDiff % 1) +
52536 1.000001) % 1 < 2e-6) {
52537 ax._minDtick = newDiff;
52538 ax._forceTick0 = newFirst;
52539 } else if((newDiff / ax._minDtick + 1e-6) % 1 > 2e-6 ||
52540 // if the converse is true (newDiff is a multiple of minDtick and
52541 // newFirst can be shifted to forceTick0) then do nothing - same
52542 // forcing stands. Otherwise, cancel forced minimum
52543
52544 (((newFirst - ax._forceTick0) / ax._minDtick % 1) +
52545 1.000001) % 1 > 2e-6) {
52546 ax._minDtick = 0;
52547 }
52548 }
52549};
52550
52551// save a copy of the initial axis ranges in fullLayout
52552// use them in mode bar and dblclick events
52553axes.saveRangeInitial = function(gd, overwrite) {
52554 var axList = axes.list(gd, '', true);
52555 var hasOneAxisChanged = false;
52556
52557 for(var i = 0; i < axList.length; i++) {
52558 var ax = axList[i];
52559 var isNew = (ax._rangeInitial === undefined);
52560 var hasChanged = isNew || !(
52561 ax.range[0] === ax._rangeInitial[0] &&
52562 ax.range[1] === ax._rangeInitial[1]
52563 );
52564
52565 if((isNew && ax.autorange === false) || (overwrite && hasChanged)) {
52566 ax._rangeInitial = ax.range.slice();
52567 hasOneAxisChanged = true;
52568 }
52569 }
52570
52571 return hasOneAxisChanged;
52572};
52573
52574// save a copy of the initial spike visibility
52575axes.saveShowSpikeInitial = function(gd, overwrite) {
52576 var axList = axes.list(gd, '', true);
52577 var hasOneAxisChanged = false;
52578 var allSpikesEnabled = 'on';
52579
52580 for(var i = 0; i < axList.length; i++) {
52581 var ax = axList[i];
52582 var isNew = (ax._showSpikeInitial === undefined);
52583 var hasChanged = isNew || !(ax.showspikes === ax._showspikes);
52584
52585 if(isNew || (overwrite && hasChanged)) {
52586 ax._showSpikeInitial = ax.showspikes;
52587 hasOneAxisChanged = true;
52588 }
52589
52590 if(allSpikesEnabled === 'on' && !ax.showspikes) {
52591 allSpikesEnabled = 'off';
52592 }
52593 }
52594 gd._fullLayout._cartesianSpikesEnabled = allSpikesEnabled;
52595 return hasOneAxisChanged;
52596};
52597
52598axes.autoBin = function(data, ax, nbins, is2d, calendar, size) {
52599 var dataMin = Lib.aggNums(Math.min, null, data);
52600 var dataMax = Lib.aggNums(Math.max, null, data);
52601
52602 if(ax.type === 'category' || ax.type === 'multicategory') {
52603 return {
52604 start: dataMin - 0.5,
52605 end: dataMax + 0.5,
52606 size: Math.max(1, Math.round(size) || 1),
52607 _dataSpan: dataMax - dataMin,
52608 };
52609 }
52610
52611 if(!calendar) calendar = ax.calendar;
52612
52613 // piggyback off tick code to make "nice" bin sizes and edges
52614 var dummyAx;
52615 if(ax.type === 'log') {
52616 dummyAx = {
52617 type: 'linear',
52618 range: [dataMin, dataMax]
52619 };
52620 } else {
52621 dummyAx = {
52622 type: ax.type,
52623 range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
52624 calendar: calendar
52625 };
52626 }
52627 axes.setConvert(dummyAx);
52628
52629 size = size && cleanTicks.dtick(size, dummyAx.type);
52630
52631 if(size) {
52632 dummyAx.dtick = size;
52633 dummyAx.tick0 = cleanTicks.tick0(undefined, dummyAx.type, calendar);
52634 } else {
52635 var size0;
52636 if(nbins) size0 = ((dataMax - dataMin) / nbins);
52637 else {
52638 // totally auto: scale off std deviation so the highest bin is
52639 // somewhat taller than the total number of bins, but don't let
52640 // the size get smaller than the 'nice' rounded down minimum
52641 // difference between values
52642 var distinctData = Lib.distinctVals(data);
52643 var msexp = Math.pow(10, Math.floor(
52644 Math.log(distinctData.minDiff) / Math.LN10));
52645 var minSize = msexp * Lib.roundUp(
52646 distinctData.minDiff / msexp, [0.9, 1.9, 4.9, 9.9], true);
52647 size0 = Math.max(minSize, 2 * Lib.stdev(data) /
52648 Math.pow(data.length, is2d ? 0.25 : 0.4));
52649
52650 // fallback if ax.d2c output BADNUMs
52651 // e.g. when user try to plot categorical bins
52652 // on a layout.xaxis.type: 'linear'
52653 if(!isNumeric(size0)) size0 = 1;
52654 }
52655
52656 axes.autoTicks(dummyAx, size0);
52657 }
52658
52659 var finalSize = dummyAx.dtick;
52660 var binStart = axes.tickIncrement(
52661 axes.tickFirst(dummyAx), finalSize, 'reverse', calendar);
52662 var binEnd, bincount;
52663
52664 // check for too many data points right at the edges of bins
52665 // (>50% within 1% of bin edges) or all data points integral
52666 // and offset the bins accordingly
52667 if(typeof finalSize === 'number') {
52668 binStart = autoShiftNumericBins(binStart, data, dummyAx, dataMin, dataMax);
52669
52670 bincount = 1 + Math.floor((dataMax - binStart) / finalSize);
52671 binEnd = binStart + bincount * finalSize;
52672 } else {
52673 // month ticks - should be the only nonlinear kind we have at this point.
52674 // dtick (as supplied by axes.autoTick) only has nonlinear values on
52675 // date and log axes, but even if you display a histogram on a log axis
52676 // we bin it on a linear axis (which one could argue against, but that's
52677 // a separate issue)
52678 if(dummyAx.dtick.charAt(0) === 'M') {
52679 binStart = autoShiftMonthBins(binStart, data, finalSize, dataMin, calendar);
52680 }
52681
52682 // calculate the endpoint for nonlinear ticks - you have to
52683 // just increment until you're done
52684 binEnd = binStart;
52685 bincount = 0;
52686 while(binEnd <= dataMax) {
52687 binEnd = axes.tickIncrement(binEnd, finalSize, false, calendar);
52688 bincount++;
52689 }
52690 }
52691
52692 return {
52693 start: ax.c2r(binStart, 0, calendar),
52694 end: ax.c2r(binEnd, 0, calendar),
52695 size: finalSize,
52696 _dataSpan: dataMax - dataMin
52697 };
52698};
52699
52700
52701function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) {
52702 var edgecount = 0;
52703 var midcount = 0;
52704 var intcount = 0;
52705 var blankCount = 0;
52706
52707 function nearEdge(v) {
52708 // is a value within 1% of a bin edge?
52709 return (1 + (v - binStart) * 100 / ax.dtick) % 100 < 2;
52710 }
52711
52712 for(var i = 0; i < data.length; i++) {
52713 if(data[i] % 1 === 0) intcount++;
52714 else if(!isNumeric(data[i])) blankCount++;
52715
52716 if(nearEdge(data[i])) edgecount++;
52717 if(nearEdge(data[i] + ax.dtick / 2)) midcount++;
52718 }
52719 var dataCount = data.length - blankCount;
52720
52721 if(intcount === dataCount && ax.type !== 'date') {
52722 if(ax.dtick < 1) {
52723 // all integers: if bin size is <1, it's because
52724 // that was specifically requested (large nbins)
52725 // so respect that... but center the bins containing
52726 // integers on those integers
52727
52728 binStart = dataMin - 0.5 * ax.dtick;
52729 } else {
52730 // otherwise start half an integer down regardless of
52731 // the bin size, just enough to clear up endpoint
52732 // ambiguity about which integers are in which bins.
52733
52734 binStart -= 0.5;
52735 if(binStart + ax.dtick < dataMin) binStart += ax.dtick;
52736 }
52737 } else if(midcount < dataCount * 0.1) {
52738 if(edgecount > dataCount * 0.3 ||
52739 nearEdge(dataMin) || nearEdge(dataMax)) {
52740 // lots of points at the edge, not many in the middle
52741 // shift half a bin
52742 var binshift = ax.dtick / 2;
52743 binStart += (binStart + binshift < dataMin) ? binshift : -binshift;
52744 }
52745 }
52746 return binStart;
52747}
52748
52749
52750function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
52751 var stats = Lib.findExactDates(data, calendar);
52752 // number of data points that needs to be an exact value
52753 // to shift that increment to (near) the bin center
52754 var threshold = 0.8;
52755
52756 if(stats.exactDays > threshold) {
52757 var numMonths = Number(dtick.substr(1));
52758
52759 if((stats.exactYears > threshold) && (numMonths % 12 === 0)) {
52760 // The exact middle of a non-leap-year is 1.5 days into July
52761 // so if we start the bins here, all but leap years will
52762 // get hover-labeled as exact years.
52763 binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5;
52764 } else if(stats.exactMonths > threshold) {
52765 // Months are not as clean, but if we shift half the *longest*
52766 // month (31/2 days) then 31-day months will get labeled exactly
52767 // and shorter months will get labeled with the correct month
52768 // but shifted 12-36 hours into it.
52769 binStart = axes.tickIncrement(binStart, 'M1', 'reverse') + ONEDAY * 15.5;
52770 } else {
52771 // Shifting half a day is exact, but since these are month bins it
52772 // will always give a somewhat odd-looking label, until we do something
52773 // smarter like showing the bin boundaries (or the bounds of the actual
52774 // data in each bin)
52775 binStart -= ONEDAY / 2;
52776 }
52777 var nextBinStart = axes.tickIncrement(binStart, dtick);
52778
52779 if(nextBinStart <= dataMin) return nextBinStart;
52780 }
52781 return binStart;
52782}
52783
52784// ----------------------------------------------------
52785// Ticks and grids
52786// ----------------------------------------------------
52787
52788// ensure we have tick0, dtick, and tick rounding calculated
52789axes.prepTicks = function(ax, opts) {
52790 var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
52791
52792 // calculate max number of (auto) ticks to display based on plot size
52793 if(ax.tickmode === 'auto' || !ax.dtick) {
52794 var nt = ax.nticks;
52795 var minPx;
52796
52797 if(!nt) {
52798 if(ax.type === 'category' || ax.type === 'multicategory') {
52799 minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
52800 nt = ax._length / minPx;
52801 } else {
52802 minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
52803 nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
52804 }
52805
52806 // radial axes span half their domain,
52807 // multiply nticks value by two to get correct number of auto ticks.
52808 if(ax._name === 'radialaxis') nt *= 2;
52809 }
52810
52811 // add a couple of extra digits for filling in ticks when we
52812 // have explicit tickvals without tick text
52813 if(ax.tickmode === 'array') nt *= 100;
52814
52815
52816 ax._roughDTick = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / nt;
52817 axes.autoTicks(ax, ax._roughDTick);
52818
52819 // check for a forced minimum dtick
52820 if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
52821 ax.dtick = ax._minDtick;
52822 ax.tick0 = ax.l2r(ax._forceTick0);
52823 }
52824 }
52825
52826 // check for missing tick0
52827 if(!ax.tick0) {
52828 ax.tick0 = (ax.type === 'date') ? '2000-01-01' : 0;
52829 }
52830
52831 // ensure we don't try to make ticks below our minimum precision
52832 // see https://github.com/plotly/plotly.js/issues/2892
52833 if(ax.type === 'date' && ax.dtick < 0.1) ax.dtick = 0.1;
52834
52835 // now figure out rounding of tick values
52836 autoTickRound(ax);
52837};
52838
52839// calculate the ticks: text, values, positioning
52840// if ticks are set to automatic, determine the right values (tick0,dtick)
52841// in any case, set tickround to # of digits to round tick labels to,
52842// or codes to this effect for log and date scales
52843axes.calcTicks = function calcTicks(ax, opts) {
52844 axes.prepTicks(ax, opts);
52845 var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
52846
52847 // now that we've figured out the auto values for formatting
52848 // in case we're missing some ticktext, we can break out for array ticks
52849 if(ax.tickmode === 'array') return arrayTicks(ax);
52850
52851 // find the first tick
52852 ax._tmin = axes.tickFirst(ax, opts);
52853
52854 // add a tiny bit so we get ticks which may have rounded out
52855 var exRng = expandRange(rng);
52856 var startTick = exRng[0];
52857 var endTick = exRng[1];
52858 // check for reversed axis
52859 var axrev = (rng[1] < rng[0]);
52860
52861 // No visible ticks? Quit.
52862 // I've only seen this on category axes with all categories off the edge.
52863 if((ax._tmin < startTick) !== axrev) return [];
52864
52865 // return the full set of tick vals
52866 if(ax.type === 'category' || ax.type === 'multicategory') {
52867 endTick = (axrev) ? Math.max(-0.5, endTick) :
52868 Math.min(ax._categories.length - 0.5, endTick);
52869 }
52870
52871 var isDLog = (ax.type === 'log') && !(isNumeric(ax.dtick) || ax.dtick.charAt(0) === 'L');
52872
52873 var tickVals;
52874 function generateTicks() {
52875 var xPrevious = null;
52876 var maxTicks = Math.max(1000, ax._length || 0);
52877 tickVals = [];
52878 for(var x = ax._tmin;
52879 (axrev) ? (x >= endTick) : (x <= endTick);
52880 x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) {
52881 // prevent infinite loops - no more than one tick per pixel,
52882 // and make sure each value is different from the previous
52883 if(tickVals.length > maxTicks || x === xPrevious) break;
52884 xPrevious = x;
52885
52886 var minor = false;
52887 if(isDLog && (x !== (x | 0))) {
52888 minor = true;
52889 }
52890
52891 tickVals.push({
52892 minor: minor,
52893 value: x
52894 });
52895 }
52896 }
52897
52898 generateTicks();
52899
52900 if(ax.rangebreaks) {
52901 // replace ticks inside breaks that would get a tick
52902 // and reduce ticks
52903 var len = tickVals.length;
52904 if(len) {
52905 var tf = 0;
52906 if(ax.tickmode === 'auto') {
52907 tf =
52908 (ax._id.charAt(0) === 'y' ? 2 : 6) *
52909 (ax.tickfont ? ax.tickfont.size : 12);
52910 }
52911
52912 var newTickVals = [];
52913 var prevPos;
52914
52915 var dir = axrev ? 1 : -1;
52916 var first = axrev ? 0 : len - 1;
52917 var last = axrev ? len - 1 : 0;
52918 for(var q = first; dir * q <= dir * last; q += dir) {
52919 var tickVal = tickVals[q];
52920 if(ax.maskBreaks(tickVal.value) === BADNUM) {
52921 tickVal.value = moveOutsideBreak(tickVal.value, ax);
52922
52923 if(ax._rl && (
52924 ax._rl[0] === tickVal.value ||
52925 ax._rl[1] === tickVal.value
52926 )) continue;
52927 }
52928
52929 var pos = ax.c2p(tickVal.value);
52930
52931 if(pos === prevPos) {
52932 if(newTickVals[newTickVals.length - 1].value < tickVal.value) {
52933 newTickVals[newTickVals.length - 1] = tickVal;
52934 }
52935 } else if(prevPos === undefined || Math.abs(pos - prevPos) > tf) {
52936 prevPos = pos;
52937 newTickVals.push(tickVal);
52938 }
52939 }
52940 tickVals = newTickVals.reverse();
52941 }
52942 }
52943
52944 // If same angle over a full circle, the last tick vals is a duplicate.
52945 // TODO must do something similar for angular date axes.
52946 if(isAngular(ax) && Math.abs(rng[1] - rng[0]) === 360) {
52947 tickVals.pop();
52948 }
52949
52950 // save the last tick as well as first, so we can
52951 // show the exponent only on the last one
52952 ax._tmax = (tickVals[tickVals.length - 1] || {}).value;
52953
52954 // for showing the rest of a date when the main tick label is only the
52955 // latter part: ax._prevDateHead holds what we showed most recently.
52956 // Start with it cleared and mark that we're in calcTicks (ie calculating a
52957 // whole string of these so we should care what the previous date head was!)
52958 ax._prevDateHead = '';
52959 ax._inCalcTicks = true;
52960
52961 var ticksOut = new Array(tickVals.length);
52962 for(var i = 0; i < tickVals.length; i++) {
52963 var _minor = tickVals[i].minor;
52964 var _value = tickVals[i].value;
52965
52966 ticksOut[i] = axes.tickText(
52967 ax,
52968 _value,
52969 false, // hover
52970 _minor // noSuffixPrefix
52971 );
52972 }
52973
52974 ax._inCalcTicks = false;
52975
52976 return ticksOut;
52977};
52978
52979function arrayTicks(ax) {
52980 var vals = ax.tickvals;
52981 var text = ax.ticktext;
52982 var ticksOut = new Array(vals.length);
52983 var rng = Lib.simpleMap(ax.range, ax.r2l);
52984 var exRng = expandRange(rng);
52985 var tickMin = Math.min(exRng[0], exRng[1]);
52986 var tickMax = Math.max(exRng[0], exRng[1]);
52987 var j = 0;
52988
52989 // without a text array, just format the given values as any other ticks
52990 // except with more precision to the numbers
52991 if(!Array.isArray(text)) text = [];
52992
52993 // make sure showing ticks doesn't accidentally add new categories
52994 // TODO multicategory, if we allow ticktext / tickvals
52995 var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
52996
52997 // array ticks on log axes always show the full number
52998 // (if no explicit ticktext overrides it)
52999 if(ax.type === 'log' && String(ax.dtick).charAt(0) !== 'L') {
53000 ax.dtick = 'L' + Math.pow(10, Math.floor(Math.min(ax.range[0], ax.range[1])) - 1);
53001 }
53002
53003 for(var i = 0; i < vals.length; i++) {
53004 var vali = tickVal2l(vals[i]);
53005 if(vali > tickMin && vali < tickMax) {
53006 if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali);
53007 else ticksOut[j] = tickTextObj(ax, vali, String(text[i]));
53008 j++;
53009 }
53010 }
53011
53012 if(j < vals.length) ticksOut.splice(j, vals.length - j);
53013
53014 if(ax.rangebreaks) {
53015 // remove ticks falling inside rangebreaks
53016 ticksOut = ticksOut.filter(function(d) {
53017 return ax.maskBreaks(d.x) !== BADNUM;
53018 });
53019 }
53020
53021 return ticksOut;
53022}
53023
53024var roundBase10 = [2, 5, 10];
53025var roundBase24 = [1, 2, 3, 6, 12];
53026var roundBase60 = [1, 2, 5, 10, 15, 30];
53027// 2&3 day ticks are weird, but need something btwn 1&7
53028var roundDays = [1, 2, 3, 7, 14];
53029// approx. tick positions for log axes, showing all (1) and just 1, 2, 5 (2)
53030// these don't have to be exact, just close enough to round to the right value
53031var roundLog1 = [-0.046, 0, 0.301, 0.477, 0.602, 0.699, 0.778, 0.845, 0.903, 0.954, 1];
53032var roundLog2 = [-0.301, 0, 0.301, 0.699, 1];
53033// N.B. `thetaunit; 'radians' angular axes must be converted to degrees
53034var roundAngles = [15, 30, 45, 90, 180];
53035
53036function roundDTick(roughDTick, base, roundingSet) {
53037 return base * Lib.roundUp(roughDTick / base, roundingSet);
53038}
53039
53040// autoTicks: calculate best guess at pleasant ticks for this axis
53041// inputs:
53042// ax - an axis object
53043// roughDTick - rough tick spacing (to be turned into a nice round number)
53044// outputs (into ax):
53045// tick0: starting point for ticks (not necessarily on the graph)
53046// usually 0 for numeric (=10^0=1 for log) or jan 1, 2000 for dates
53047// dtick: the actual, nice round tick spacing, usually a little larger than roughDTick
53048// if the ticks are spaced linearly (linear scale, categories,
53049// log with only full powers, date ticks < month),
53050// this will just be a number
53051// months: M#
53052// years: M# where # is 12*number of years
53053// log with linear ticks: L# where # is the linear tick spacing
53054// log showing powers plus some intermediates:
53055// D1 shows all digits, D2 shows 2 and 5
53056axes.autoTicks = function(ax, roughDTick) {
53057 var base;
53058
53059 function getBase(v) {
53060 return Math.pow(v, Math.floor(Math.log(roughDTick) / Math.LN10));
53061 }
53062
53063 if(ax.type === 'date') {
53064 ax.tick0 = Lib.dateTick0(ax.calendar);
53065 // the criteria below are all based on the rough spacing we calculate
53066 // being > half of the final unit - so precalculate twice the rough val
53067 var roughX2 = 2 * roughDTick;
53068
53069 if(roughX2 > ONEAVGYEAR) {
53070 roughDTick /= ONEAVGYEAR;
53071 base = getBase(10);
53072 ax.dtick = 'M' + (12 * roundDTick(roughDTick, base, roundBase10));
53073 } else if(roughX2 > ONEAVGMONTH) {
53074 roughDTick /= ONEAVGMONTH;
53075 ax.dtick = 'M' + roundDTick(roughDTick, 1, roundBase24);
53076 } else if(roughX2 > ONEDAY) {
53077 ax.dtick = roundDTick(roughDTick, ONEDAY, ax._hasDayOfWeekBreaks ? [1, 2, 7, 14] : roundDays);
53078 // get week ticks on sunday
53079 // this will also move the base tick off 2000-01-01 if dtick is
53080 // 2 or 3 days... but that's a weird enough case that we'll ignore it.
53081 ax.tick0 = Lib.dateTick0(ax.calendar, true);
53082 } else if(roughX2 > ONEHOUR) {
53083 ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24);
53084 } else if(roughX2 > ONEMIN) {
53085 ax.dtick = roundDTick(roughDTick, ONEMIN, roundBase60);
53086 } else if(roughX2 > ONESEC) {
53087 ax.dtick = roundDTick(roughDTick, ONESEC, roundBase60);
53088 } else {
53089 // milliseconds
53090 base = getBase(10);
53091 ax.dtick = roundDTick(roughDTick, base, roundBase10);
53092 }
53093 } else if(ax.type === 'log') {
53094 ax.tick0 = 0;
53095 var rng = Lib.simpleMap(ax.range, ax.r2l);
53096
53097 if(roughDTick > 0.7) {
53098 // only show powers of 10
53099 ax.dtick = Math.ceil(roughDTick);
53100 } else if(Math.abs(rng[1] - rng[0]) < 1) {
53101 // span is less than one power of 10
53102 var nt = 1.5 * Math.abs((rng[1] - rng[0]) / roughDTick);
53103
53104 // ticks on a linear scale, labeled fully
53105 roughDTick = Math.abs(Math.pow(10, rng[1]) -
53106 Math.pow(10, rng[0])) / nt;
53107 base = getBase(10);
53108 ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
53109 } else {
53110 // include intermediates between powers of 10,
53111 // labeled with small digits
53112 // ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits)
53113 ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1';
53114 }
53115 } else if(ax.type === 'category' || ax.type === 'multicategory') {
53116 ax.tick0 = 0;
53117 ax.dtick = Math.ceil(Math.max(roughDTick, 1));
53118 } else if(isAngular(ax)) {
53119 ax.tick0 = 0;
53120 base = 1;
53121 ax.dtick = roundDTick(roughDTick, base, roundAngles);
53122 } else {
53123 // auto ticks always start at 0
53124 ax.tick0 = 0;
53125 base = getBase(10);
53126 ax.dtick = roundDTick(roughDTick, base, roundBase10);
53127 }
53128
53129 // prevent infinite loops
53130 if(ax.dtick === 0) ax.dtick = 1;
53131
53132 // TODO: this is from log axis histograms with autorange off
53133 if(!isNumeric(ax.dtick) && typeof ax.dtick !== 'string') {
53134 var olddtick = ax.dtick;
53135 ax.dtick = 1;
53136 throw 'ax.dtick error: ' + String(olddtick);
53137 }
53138};
53139
53140// after dtick is already known, find tickround = precision
53141// to display in tick labels
53142// for numeric ticks, integer # digits after . to round to
53143// for date ticks, the last date part to show (y,m,d,H,M,S)
53144// or an integer # digits past seconds
53145function autoTickRound(ax) {
53146 var dtick = ax.dtick;
53147
53148 ax._tickexponent = 0;
53149 if(!isNumeric(dtick) && typeof dtick !== 'string') {
53150 dtick = 1;
53151 }
53152
53153 if(ax.type === 'category' || ax.type === 'multicategory') {
53154 ax._tickround = null;
53155 }
53156 if(ax.type === 'date') {
53157 // If tick0 is unusual, give tickround a bit more information
53158 // not necessarily *all* the information in tick0 though, if it's really odd
53159 // minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
53160 // take off a leading minus (year < 0) and i (intercalary month) so length is consistent
53161 var tick0ms = ax.r2l(ax.tick0);
53162 var tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, '');
53163 var tick0len = tick0str.length;
53164
53165 if(String(dtick).charAt(0) === 'M') {
53166 // any tick0 more specific than a year: alway show the full date
53167 if(tick0len > 10 || tick0str.substr(5) !== '01-01') ax._tickround = 'd';
53168 // show the month unless ticks are full multiples of a year
53169 else ax._tickround = (+(dtick.substr(1)) % 12 === 0) ? 'y' : 'm';
53170 } else if((dtick >= ONEDAY && tick0len <= 10) || (dtick >= ONEDAY * 15)) ax._tickround = 'd';
53171 else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M';
53172 else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S';
53173 else {
53174 // tickround is a number of digits of fractional seconds
53175 // of any two adjacent ticks, at least one will have the maximum fractional digits
53176 // of all possible ticks - so take the max. length of tick0 and the next one
53177 var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length;
53178 ax._tickround = Math.max(tick0len, tick1len) - 20;
53179
53180 // We shouldn't get here... but in case there's a situation I'm
53181 // not thinking of where tick0str and tick1str are identical or
53182 // something, fall back on maximum precision
53183 if(ax._tickround < 0) ax._tickround = 4;
53184 }
53185 } else if(isNumeric(dtick) || dtick.charAt(0) === 'L') {
53186 // linear or log (except D1, D2)
53187 var rng = ax.range.map(ax.r2d || Number);
53188 if(!isNumeric(dtick)) dtick = Number(dtick.substr(1));
53189 // 2 digits past largest digit of dtick
53190 ax._tickround = 2 - Math.floor(Math.log(dtick) / Math.LN10 + 0.01);
53191
53192 var maxend = Math.max(Math.abs(rng[0]), Math.abs(rng[1]));
53193 var rangeexp = Math.floor(Math.log(maxend) / Math.LN10 + 0.01);
53194 if(Math.abs(rangeexp) > 3) {
53195 if(isSIFormat(ax.exponentformat) && !beyondSI(rangeexp)) {
53196 ax._tickexponent = 3 * Math.round((rangeexp - 1) / 3);
53197 } else ax._tickexponent = rangeexp;
53198 }
53199 } else {
53200 // D1 or D2 (log)
53201 ax._tickround = null;
53202 }
53203}
53204
53205// months and years don't have constant millisecond values
53206// (but a year is always 12 months so we only need months)
53207// log-scale ticks are also not consistently spaced, except
53208// for pure powers of 10
53209// numeric ticks always have constant differences, other datetime ticks
53210// can all be calculated as constant number of milliseconds
53211axes.tickIncrement = function(x, dtick, axrev, calendar) {
53212 var axSign = axrev ? -1 : 1;
53213
53214 // includes linear, all dates smaller than month, and pure 10^n in log
53215 if(isNumeric(dtick)) return x + axSign * dtick;
53216
53217 // everything else is a string, one character plus a number
53218 var tType = dtick.charAt(0);
53219 var dtSigned = axSign * Number(dtick.substr(1));
53220
53221 // Dates: months (or years - see Lib.incrementMonth)
53222 if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
53223
53224 // Log scales: Linear, Digits
53225 if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
53226
53227 // log10 of 2,5,10, or all digits (logs just have to be
53228 // close enough to round)
53229 if(tType === 'D') {
53230 var tickset = (dtick === 'D2') ? roundLog2 : roundLog1;
53231 var x2 = x + axSign * 0.01;
53232 var frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev);
53233
53234 return Math.floor(x2) +
53235 Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
53236 }
53237
53238 throw 'unrecognized dtick ' + String(dtick);
53239};
53240
53241// calculate the first tick on an axis
53242axes.tickFirst = function(ax, opts) {
53243 var r2l = ax.r2l || Number;
53244 var rng = Lib.simpleMap(ax.range, r2l, undefined, undefined, opts);
53245 var axrev = rng[1] < rng[0];
53246 var sRound = axrev ? Math.floor : Math.ceil;
53247 // add a tiny extra bit to make sure we get ticks
53248 // that may have been rounded out
53249 var r0 = expandRange(rng)[0];
53250 var dtick = ax.dtick;
53251 var tick0 = r2l(ax.tick0);
53252
53253 if(isNumeric(dtick)) {
53254 var tmin = sRound((r0 - tick0) / dtick) * dtick + tick0;
53255
53256 // make sure no ticks outside the category list
53257 if(ax.type === 'category' || ax.type === 'multicategory') {
53258 tmin = Lib.constrain(tmin, 0, ax._categories.length - 1);
53259 }
53260 return tmin;
53261 }
53262
53263 var tType = dtick.charAt(0);
53264 var dtNum = Number(dtick.substr(1));
53265
53266 // Dates: months (or years)
53267 if(tType === 'M') {
53268 var cnt = 0;
53269 var t0 = tick0;
53270 var t1, mult, newDTick;
53271
53272 // This algorithm should work for *any* nonlinear (but close to linear!)
53273 // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3.
53274 while(cnt < 10) {
53275 t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar);
53276 if((t1 - r0) * (t0 - r0) <= 0) {
53277 // t1 and t0 are on opposite sides of r0! we've succeeded!
53278 if(axrev) return Math.min(t0, t1);
53279 return Math.max(t0, t1);
53280 }
53281 mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0);
53282 newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum);
53283 t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar);
53284 cnt++;
53285 }
53286 Lib.error('tickFirst did not converge', ax);
53287 return t0;
53288 } else if(tType === 'L') {
53289 // Log scales: Linear, Digits
53290
53291 return Math.log(sRound(
53292 (Math.pow(10, r0) - tick0) / dtNum) * dtNum + tick0) / Math.LN10;
53293 } else if(tType === 'D') {
53294 var tickset = (dtick === 'D2') ? roundLog2 : roundLog1;
53295 var frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev);
53296
53297 return Math.floor(r0) +
53298 Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
53299 } else throw 'unrecognized dtick ' + String(dtick);
53300};
53301
53302// draw the text for one tick.
53303// px,py are the location on gd.paper
53304// prefix is there so the x axis ticks can be dropped a line
53305// ax is the axis layout, x is the tick value
53306// hover is a (truthy) flag for whether to show numbers with a bit
53307// more precision for hovertext
53308axes.tickText = function(ax, x, hover, noSuffixPrefix) {
53309 var out = tickTextObj(ax, x);
53310 var arrayMode = ax.tickmode === 'array';
53311 var extraPrecision = hover || arrayMode;
53312 var axType = ax.type;
53313 // TODO multicategory, if we allow ticktext / tickvals
53314 var tickVal2l = axType === 'category' ? ax.d2l_noadd : ax.d2l;
53315 var i;
53316
53317 if(arrayMode && Array.isArray(ax.ticktext)) {
53318 var rng = Lib.simpleMap(ax.range, ax.r2l);
53319 var minDiff = (Math.abs(rng[1] - rng[0]) - (ax._lBreaks || 0)) / 10000;
53320
53321 for(i = 0; i < ax.ticktext.length; i++) {
53322 if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break;
53323 }
53324 if(i < ax.ticktext.length) {
53325 out.text = String(ax.ticktext[i]);
53326 return out;
53327 }
53328 }
53329
53330 function isHidden(showAttr) {
53331 if(showAttr === undefined) return true;
53332 if(hover) return showAttr === 'none';
53333
53334 var firstOrLast = {
53335 first: ax._tmin,
53336 last: ax._tmax
53337 }[showAttr];
53338
53339 return showAttr !== 'all' && x !== firstOrLast;
53340 }
53341
53342 var hideexp = hover ?
53343 'never' :
53344 ax.exponentformat !== 'none' && isHidden(ax.showexponent) ? 'hide' : '';
53345
53346 if(axType === 'date') formatDate(ax, out, hover, extraPrecision);
53347 else if(axType === 'log') formatLog(ax, out, hover, extraPrecision, hideexp);
53348 else if(axType === 'category') formatCategory(ax, out);
53349 else if(axType === 'multicategory') formatMultiCategory(ax, out, hover);
53350 else if(isAngular(ax)) formatAngle(ax, out, hover, extraPrecision, hideexp);
53351 else formatLinear(ax, out, hover, extraPrecision, hideexp);
53352
53353 // add prefix and suffix
53354 if(!noSuffixPrefix) {
53355 if(ax.tickprefix && !isHidden(ax.showtickprefix)) out.text = ax.tickprefix + out.text;
53356 if(ax.ticksuffix && !isHidden(ax.showticksuffix)) out.text += ax.ticksuffix;
53357 }
53358
53359 // Setup ticks and grid lines boundaries
53360 // at 1/2 a 'category' to the left/bottom
53361 if(ax.tickson === 'boundaries' || ax.showdividers) {
53362 var inbounds = function(v) {
53363 var p = ax.l2p(v);
53364 return p >= 0 && p <= ax._length ? v : null;
53365 };
53366
53367 out.xbnd = [
53368 inbounds(out.x - 0.5),
53369 inbounds(out.x + ax.dtick - 0.5)
53370 ];
53371 }
53372
53373 return out;
53374};
53375
53376/**
53377 * create text for a hover label on this axis, with special handling of
53378 * log axes (where negative values can't be displayed but can appear in hover text)
53379 *
53380 * @param {object} ax: the axis to format text for
53381 * @param {number} val: calcdata value to format
53382 * @param {Optional(number)} val2: a second value to display
53383 *
53384 * @returns {string} `val` formatted as a string appropriate to this axis, or
53385 * `val` and `val2` as a range (ie '<val> - <val2>') if `val2` is provided and
53386 * it's different from `val`.
53387 */
53388axes.hoverLabelText = function(ax, val, val2) {
53389 if(val2 !== BADNUM && val2 !== val) {
53390 return axes.hoverLabelText(ax, val) + ' - ' + axes.hoverLabelText(ax, val2);
53391 }
53392
53393 var logOffScale = (ax.type === 'log' && val <= 0);
53394 var tx = axes.tickText(ax, ax.c2l(logOffScale ? -val : val), 'hover').text;
53395
53396 if(logOffScale) {
53397 return val === 0 ? '0' : MINUS_SIGN + tx;
53398 }
53399
53400 // TODO: should we do something special if the axis calendar and
53401 // the data calendar are different? Somehow display both dates with
53402 // their system names? Right now it will just display in the axis calendar
53403 // but users could add the other one as text.
53404 return tx;
53405};
53406
53407function tickTextObj(ax, x, text) {
53408 var tf = ax.tickfont || {};
53409
53410 return {
53411 x: x,
53412 dx: 0,
53413 dy: 0,
53414 text: text || '',
53415 fontSize: tf.size,
53416 font: tf.family,
53417 fontColor: tf.color
53418 };
53419}
53420
53421function formatDate(ax, out, hover, extraPrecision) {
53422 var tr = ax._tickround;
53423 var fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax);
53424
53425 if(extraPrecision) {
53426 // second or sub-second precision: extra always shows max digits.
53427 // for other fields, extra precision just adds one field.
53428 if(isNumeric(tr)) tr = 4;
53429 else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
53430 }
53431
53432 var dateStr = Lib.formatDate(out.x, fmt, tr, ax._dateFormat, ax.calendar, ax._extraFormat);
53433 var headStr;
53434
53435 var splitIndex = dateStr.indexOf('\n');
53436 if(splitIndex !== -1) {
53437 headStr = dateStr.substr(splitIndex + 1);
53438 dateStr = dateStr.substr(0, splitIndex);
53439 }
53440
53441 if(extraPrecision) {
53442 // if extraPrecision led to trailing zeros, strip them off
53443 // actually, this can lead to removing even more zeros than
53444 // in the original rounding, but that's fine because in these
53445 // contexts uniformity is not so important (if there's even
53446 // anything to be uniform with!)
53447
53448 // can we remove the whole time part?
53449 if(dateStr === '00:00:00' || dateStr === '00:00') {
53450 dateStr = headStr;
53451 headStr = '';
53452 } else if(dateStr.length === 8) {
53453 // strip off seconds if they're zero (zero fractional seconds
53454 // are already omitted)
53455 // but we never remove minutes and leave just hours
53456 dateStr = dateStr.replace(/:00$/, '');
53457 }
53458 }
53459
53460 if(headStr) {
53461 if(hover) {
53462 // hover puts it all on one line, so headPart works best up front
53463 // except for year headPart: turn this into "Jan 1, 2000" etc.
53464 if(tr === 'd') dateStr += ', ' + headStr;
53465 else dateStr = headStr + (dateStr ? ', ' + dateStr : '');
53466 } else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) {
53467 dateStr += '<br>' + headStr;
53468 ax._prevDateHead = headStr;
53469 }
53470 }
53471
53472 out.text = dateStr;
53473}
53474
53475function formatLog(ax, out, hover, extraPrecision, hideexp) {
53476 var dtick = ax.dtick;
53477 var x = out.x;
53478 var tickformat = ax.tickformat;
53479 var dtChar0 = typeof dtick === 'string' && dtick.charAt(0);
53480
53481 if(hideexp === 'never') {
53482 // If this is a hover label, then we must *never* hide the exponent
53483 // for the sake of display, which could give the wrong value by
53484 // potentially many orders of magnitude. If hideexp was 'never', then
53485 // it's now succeeded by preventing the other condition from automating
53486 // this choice. Thus we can unset it so that the axis formatting takes
53487 // precedence.
53488 hideexp = '';
53489 }
53490
53491 if(extraPrecision && (dtChar0 !== 'L')) {
53492 dtick = 'L3';
53493 dtChar0 = 'L';
53494 }
53495
53496 if(tickformat || (dtChar0 === 'L')) {
53497 out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision);
53498 } else if(isNumeric(dtick) || ((dtChar0 === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) {
53499 var p = Math.round(x);
53500 var absP = Math.abs(p);
53501 var exponentFormat = ax.exponentformat;
53502 if(exponentFormat === 'power' || (isSIFormat(exponentFormat) && beyondSI(p))) {
53503 if(p === 0) out.text = 1;
53504 else if(p === 1) out.text = '10';
53505 else out.text = '10<sup>' + (p > 1 ? '' : MINUS_SIGN) + absP + '</sup>';
53506
53507 out.fontSize *= 1.25;
53508 } else if((exponentFormat === 'e' || exponentFormat === 'E') && absP > 2) {
53509 out.text = '1' + exponentFormat + (p > 0 ? '+' : MINUS_SIGN) + absP;
53510 } else {
53511 out.text = numFormat(Math.pow(10, x), ax, '', 'fakehover');
53512 if(dtick === 'D1' && ax._id.charAt(0) === 'y') {
53513 out.dy -= out.fontSize / 6;
53514 }
53515 }
53516 } else if(dtChar0 === 'D') {
53517 out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1))));
53518 out.fontSize *= 0.75;
53519 } else throw 'unrecognized dtick ' + String(dtick);
53520
53521 // if 9's are printed on log scale, move the 10's away a bit
53522 if(ax.dtick === 'D1') {
53523 var firstChar = String(out.text).charAt(0);
53524 if(firstChar === '0' || firstChar === '1') {
53525 if(ax._id.charAt(0) === 'y') {
53526 out.dx -= out.fontSize / 4;
53527 } else {
53528 out.dy += out.fontSize / 2;
53529 out.dx += (ax.range[1] > ax.range[0] ? 1 : -1) *
53530 out.fontSize * (x < 0 ? 0.5 : 0.25);
53531 }
53532 }
53533 }
53534}
53535
53536function formatCategory(ax, out) {
53537 var tt = ax._categories[Math.round(out.x)];
53538 if(tt === undefined) tt = '';
53539 out.text = String(tt);
53540}
53541
53542function formatMultiCategory(ax, out, hover) {
53543 var v = Math.round(out.x);
53544 var cats = ax._categories[v] || [];
53545 var tt = cats[1] === undefined ? '' : String(cats[1]);
53546 var tt2 = cats[0] === undefined ? '' : String(cats[0]);
53547
53548 if(hover) {
53549 // TODO is this what we want?
53550 out.text = tt2 + ' - ' + tt;
53551 } else {
53552 // setup for secondary labels
53553 out.text = tt;
53554 out.text2 = tt2;
53555 }
53556}
53557
53558function formatLinear(ax, out, hover, extraPrecision, hideexp) {
53559 if(hideexp === 'never') {
53560 // If this is a hover label, then we must *never* hide the exponent
53561 // for the sake of display, which could give the wrong value by
53562 // potentially many orders of magnitude. If hideexp was 'never', then
53563 // it's now succeeded by preventing the other condition from automating
53564 // this choice. Thus we can unset it so that the axis formatting takes
53565 // precedence.
53566 hideexp = '';
53567 } else if(ax.showexponent === 'all' && Math.abs(out.x / ax.dtick) < 1e-6) {
53568 // don't add an exponent to zero if we're showing all exponents
53569 // so the only reason you'd show an exponent on zero is if it's the
53570 // ONLY tick to get an exponent (first or last)
53571 hideexp = 'hide';
53572 }
53573 out.text = numFormat(out.x, ax, hideexp, extraPrecision);
53574}
53575
53576function formatAngle(ax, out, hover, extraPrecision, hideexp) {
53577 if(ax.thetaunit === 'radians' && !hover) {
53578 var num = out.x / 180;
53579
53580 if(num === 0) {
53581 out.text = '0';
53582 } else {
53583 var frac = num2frac(num);
53584
53585 if(frac[1] >= 100) {
53586 out.text = numFormat(Lib.deg2rad(out.x), ax, hideexp, extraPrecision);
53587 } else {
53588 var isNeg = out.x < 0;
53589
53590 if(frac[1] === 1) {
53591 if(frac[0] === 1) out.text = 'π';
53592 else out.text = frac[0] + 'π';
53593 } else {
53594 out.text = [
53595 '<sup>', frac[0], '</sup>',
53596 '⁄',
53597 '<sub>', frac[1], '</sub>',
53598 'π'
53599 ].join('');
53600 }
53601
53602 if(isNeg) out.text = MINUS_SIGN + out.text;
53603 }
53604 }
53605 } else {
53606 out.text = numFormat(out.x, ax, hideexp, extraPrecision);
53607 }
53608}
53609
53610// inspired by
53611// https://github.com/yisibl/num2fraction/blob/master/index.js
53612function num2frac(num) {
53613 function almostEq(a, b) {
53614 return Math.abs(a - b) <= 1e-6;
53615 }
53616
53617 function findGCD(a, b) {
53618 return almostEq(b, 0) ? a : findGCD(b, a % b);
53619 }
53620
53621 function findPrecision(n) {
53622 var e = 1;
53623 while(!almostEq(Math.round(n * e) / e, n)) {
53624 e *= 10;
53625 }
53626 return e;
53627 }
53628
53629 var precision = findPrecision(num);
53630 var number = num * precision;
53631 var gcd = Math.abs(findGCD(number, precision));
53632
53633 return [
53634 // numerator
53635 Math.round(number / gcd),
53636 // denominator
53637 Math.round(precision / gcd)
53638 ];
53639}
53640
53641// format a number (tick value) according to the axis settings
53642// new, more reliable procedure than d3.round or similar:
53643// add half the rounding increment, then stringify and truncate
53644// also automatically switch to sci. notation
53645var SIPREFIXES = ['f', 'p', 'n', 'μ', 'm', '', 'k', 'M', 'G', 'T'];
53646
53647function isSIFormat(exponentFormat) {
53648 return exponentFormat === 'SI' || exponentFormat === 'B';
53649}
53650
53651// are we beyond the range of common SI prefixes?
53652// 10^-16 -> 1x10^-16
53653// 10^-15 -> 1f
53654// ...
53655// 10^14 -> 100T
53656// 10^15 -> 1x10^15
53657// 10^16 -> 1x10^16
53658function beyondSI(exponent) {
53659 return exponent > 14 || exponent < -15;
53660}
53661
53662function numFormat(v, ax, fmtoverride, hover) {
53663 var isNeg = v < 0;
53664 // max number of digits past decimal point to show
53665 var tickRound = ax._tickround;
53666 var exponentFormat = fmtoverride || ax.exponentformat || 'B';
53667 var exponent = ax._tickexponent;
53668 var tickformat = axes.getTickFormat(ax);
53669 var separatethousands = ax.separatethousands;
53670
53671 // special case for hover: set exponent just for this value, and
53672 // add a couple more digits of precision over tick labels
53673 if(hover) {
53674 // make a dummy axis obj to get the auto rounding and exponent
53675 var ah = {
53676 exponentformat: exponentFormat,
53677 dtick: ax.showexponent === 'none' ? ax.dtick :
53678 (isNumeric(v) ? Math.abs(v) || 1 : 1),
53679 // if not showing any exponents, don't change the exponent
53680 // from what we calculate
53681 range: ax.showexponent === 'none' ? ax.range.map(ax.r2d) : [0, v || 1]
53682 };
53683 autoTickRound(ah);
53684 tickRound = (Number(ah._tickround) || 0) + 4;
53685 exponent = ah._tickexponent;
53686 if(ax.hoverformat) tickformat = ax.hoverformat;
53687 }
53688
53689 if(tickformat) return ax._numFormat(tickformat)(v).replace(/-/g, MINUS_SIGN);
53690
53691 // 'epsilon' - rounding increment
53692 var e = Math.pow(10, -tickRound) / 2;
53693
53694 // exponentFormat codes:
53695 // 'e' (1.2e+6, default)
53696 // 'E' (1.2E+6)
53697 // 'SI' (1.2M)
53698 // 'B' (same as SI except 10^9=B not G)
53699 // 'none' (1200000)
53700 // 'power' (1.2x10^6)
53701 // 'hide' (1.2, use 3rd argument=='hide' to eg
53702 // only show exponent on last tick)
53703 if(exponentFormat === 'none') exponent = 0;
53704
53705 // take the sign out, put it back manually at the end
53706 // - makes cases easier
53707 v = Math.abs(v);
53708 if(v < e) {
53709 // 0 is just 0, but may get exponent if it's the last tick
53710 v = '0';
53711 isNeg = false;
53712 } else {
53713 v += e;
53714 // take out a common exponent, if any
53715 if(exponent) {
53716 v *= Math.pow(10, -exponent);
53717 tickRound += exponent;
53718 }
53719 // round the mantissa
53720 if(tickRound === 0) v = String(Math.floor(v));
53721 else if(tickRound < 0) {
53722 v = String(Math.round(v));
53723 v = v.substr(0, v.length + tickRound);
53724 for(var i = tickRound; i < 0; i++) v += '0';
53725 } else {
53726 v = String(v);
53727 var dp = v.indexOf('.') + 1;
53728 if(dp) v = v.substr(0, dp + tickRound).replace(/\.?0+$/, '');
53729 }
53730 // insert appropriate decimal point and thousands separator
53731 v = Lib.numSeparate(v, ax._separators, separatethousands);
53732 }
53733
53734 // add exponent
53735 if(exponent && exponentFormat !== 'hide') {
53736 if(isSIFormat(exponentFormat) && beyondSI(exponent)) exponentFormat = 'power';
53737
53738 var signedExponent;
53739 if(exponent < 0) signedExponent = MINUS_SIGN + -exponent;
53740 else if(exponentFormat !== 'power') signedExponent = '+' + exponent;
53741 else signedExponent = String(exponent);
53742
53743 if(exponentFormat === 'e' || exponentFormat === 'E') {
53744 v += exponentFormat + signedExponent;
53745 } else if(exponentFormat === 'power') {
53746 v += '×10<sup>' + signedExponent + '</sup>';
53747 } else if(exponentFormat === 'B' && exponent === 9) {
53748 v += 'B';
53749 } else if(isSIFormat(exponentFormat)) {
53750 v += SIPREFIXES[exponent / 3 + 5];
53751 }
53752 }
53753
53754 // put sign back in and return
53755 // replace standard minus character (which is technically a hyphen)
53756 // with a true minus sign
53757 if(isNeg) return MINUS_SIGN + v;
53758 return v;
53759}
53760
53761axes.getTickFormat = function(ax) {
53762 var i;
53763
53764 function convertToMs(dtick) {
53765 return typeof dtick !== 'string' ? dtick : Number(dtick.replace('M', '')) * ONEAVGMONTH;
53766 }
53767
53768 function compareLogTicks(left, right) {
53769 var priority = ['L', 'D'];
53770 if(typeof left === typeof right) {
53771 if(typeof left === 'number') {
53772 return left - right;
53773 } else {
53774 var leftPriority = priority.indexOf(left.charAt(0));
53775 var rightPriority = priority.indexOf(right.charAt(0));
53776 if(leftPriority === rightPriority) {
53777 return Number(left.replace(/(L|D)/g, '')) - Number(right.replace(/(L|D)/g, ''));
53778 } else {
53779 return leftPriority - rightPriority;
53780 }
53781 }
53782 } else {
53783 return typeof left === 'number' ? 1 : -1;
53784 }
53785 }
53786
53787 function isProperStop(dtick, range, convert) {
53788 var convertFn = convert || function(x) { return x;};
53789 var leftDtick = range[0];
53790 var rightDtick = range[1];
53791 return ((!leftDtick && typeof leftDtick !== 'number') || convertFn(leftDtick) <= convertFn(dtick)) &&
53792 ((!rightDtick && typeof rightDtick !== 'number') || convertFn(rightDtick) >= convertFn(dtick));
53793 }
53794
53795 function isProperLogStop(dtick, range) {
53796 var isLeftDtickNull = range[0] === null;
53797 var isRightDtickNull = range[1] === null;
53798 var isDtickInRangeLeft = compareLogTicks(dtick, range[0]) >= 0;
53799 var isDtickInRangeRight = compareLogTicks(dtick, range[1]) <= 0;
53800 return (isLeftDtickNull || isDtickInRangeLeft) && (isRightDtickNull || isDtickInRangeRight);
53801 }
53802
53803 var tickstop, stopi;
53804 if(ax.tickformatstops && ax.tickformatstops.length > 0) {
53805 switch(ax.type) {
53806 case 'date':
53807 case 'linear': {
53808 for(i = 0; i < ax.tickformatstops.length; i++) {
53809 stopi = ax.tickformatstops[i];
53810 if(stopi.enabled && isProperStop(ax.dtick, stopi.dtickrange, convertToMs)) {
53811 tickstop = stopi;
53812 break;
53813 }
53814 }
53815 break;
53816 }
53817 case 'log': {
53818 for(i = 0; i < ax.tickformatstops.length; i++) {
53819 stopi = ax.tickformatstops[i];
53820 if(stopi.enabled && isProperLogStop(ax.dtick, stopi.dtickrange)) {
53821 tickstop = stopi;
53822 break;
53823 }
53824 }
53825 break;
53826 }
53827 default:
53828 }
53829 }
53830 return tickstop ? tickstop.value : ax.tickformat;
53831};
53832
53833// getSubplots - extract all subplot IDs we need
53834// as an array of items like 'xy', 'x2y', 'x2y2'...
53835// sorted by x (x,x2,x3...) then y
53836// optionally restrict to only subplots containing axis object ax
53837//
53838// NOTE: this is currently only used OUTSIDE plotly.js (toolpanel, webapp)
53839// ideally we get rid of it there (or just copy this there) and remove it here
53840axes.getSubplots = function(gd, ax) {
53841 var subplotObj = gd._fullLayout._subplots;
53842 var allSubplots = subplotObj.cartesian.concat(subplotObj.gl2d || []);
53843
53844 var out = ax ? axes.findSubplotsWithAxis(allSubplots, ax) : allSubplots;
53845
53846 out.sort(function(a, b) {
53847 var aParts = a.substr(1).split('y');
53848 var bParts = b.substr(1).split('y');
53849
53850 if(aParts[0] === bParts[0]) return +aParts[1] - +bParts[1];
53851 return +aParts[0] - +bParts[0];
53852 });
53853
53854 return out;
53855};
53856
53857// find all subplots with axis 'ax'
53858// NOTE: this is only used in axes.getSubplots (only used outside plotly.js) and
53859// gl2d/convert (where it restricts axis subplots to only those with gl2d)
53860axes.findSubplotsWithAxis = function(subplots, ax) {
53861 var axMatch = new RegExp(
53862 (ax._id.charAt(0) === 'x') ? ('^' + ax._id + 'y') : (ax._id + '$')
53863 );
53864 var subplotsWithAx = [];
53865
53866 for(var i = 0; i < subplots.length; i++) {
53867 var sp = subplots[i];
53868 if(axMatch.test(sp)) subplotsWithAx.push(sp);
53869 }
53870
53871 return subplotsWithAx;
53872};
53873
53874// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings
53875axes.makeClipPaths = function(gd) {
53876 var fullLayout = gd._fullLayout;
53877
53878 // for more info: https://github.com/plotly/plotly.js/issues/2595
53879 if(fullLayout._hasOnlyLargeSploms) return;
53880
53881 var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''};
53882 var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''};
53883 var xaList = axes.list(gd, 'x', true);
53884 var yaList = axes.list(gd, 'y', true);
53885 var clipList = [];
53886 var i, j;
53887
53888 for(i = 0; i < xaList.length; i++) {
53889 clipList.push({x: xaList[i], y: fullHeight});
53890 for(j = 0; j < yaList.length; j++) {
53891 if(i === 0) clipList.push({x: fullWidth, y: yaList[j]});
53892 clipList.push({x: xaList[i], y: yaList[j]});
53893 }
53894 }
53895
53896 // selectors don't work right with camelCase tags,
53897 // have to use class instead
53898 // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I
53899 var axClips = fullLayout._clips.selectAll('.axesclip')
53900 .data(clipList, function(d) { return d.x._id + d.y._id; });
53901
53902 axClips.enter().append('clipPath')
53903 .classed('axesclip', true)
53904 .attr('id', function(d) { return 'clip' + fullLayout._uid + d.x._id + d.y._id; })
53905 .append('rect');
53906
53907 axClips.exit().remove();
53908
53909 axClips.each(function(d) {
53910 d3.select(this).select('rect').attr({
53911 x: d.x._offset || 0,
53912 y: d.y._offset || 0,
53913 width: d.x._length || 1,
53914 height: d.y._length || 1
53915 });
53916 });
53917};
53918
53919/**
53920 * Main multi-axis drawing routine!
53921 *
53922 * @param {DOM element} gd : graph div
53923 * @param {string or array of strings} arg : polymorphic argument
53924 * @param {object} opts:
53925 * - @param {boolean} skipTitle : optional flag to skip axis title draw/update
53926 *
53927 * Signature 1: Axes.draw(gd, 'redraw')
53928 * use this to clear and redraw all axes on graph
53929 *
53930 * Signature 2: Axes.draw(gd, '')
53931 * use this to draw all axes on graph w/o the selectAll().remove()
53932 * of the 'redraw' signature
53933 *
53934 * Signature 3: Axes.draw(gd, [axId, axId2, ...])
53935 * where the items are axis id string,
53936 * use this to update multiple axes in one call
53937 *
53938 * N.B draw updates:
53939 * - ax._r (stored range for use by zoom/pan)
53940 * - ax._rl (stored linearized range for use by zoom/pan)
53941 */
53942axes.draw = function(gd, arg, opts) {
53943 var fullLayout = gd._fullLayout;
53944
53945 if(arg === 'redraw') {
53946 fullLayout._paper.selectAll('g.subplot').each(function(d) {
53947 var id = d[0];
53948 var plotinfo = fullLayout._plots[id];
53949 var xa = plotinfo.xaxis;
53950 var ya = plotinfo.yaxis;
53951
53952 plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove();
53953 plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove();
53954 plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick2').remove();
53955 plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick2').remove();
53956 plotinfo.xaxislayer.selectAll('.' + xa._id + 'divider').remove();
53957 plotinfo.yaxislayer.selectAll('.' + ya._id + 'divider').remove();
53958
53959 if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove();
53960 if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove();
53961
53962 fullLayout._infolayer.select('.g-' + xa._id + 'title').remove();
53963 fullLayout._infolayer.select('.g-' + ya._id + 'title').remove();
53964 });
53965 }
53966
53967 var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg;
53968
53969 return Lib.syncOrAsync(axList.map(function(axId) {
53970 return function() {
53971 if(!axId) return;
53972
53973 var ax = axes.getFromId(gd, axId);
53974 var axDone = axes.drawOne(gd, ax, opts);
53975
53976 ax._r = ax.range.slice();
53977 ax._rl = Lib.simpleMap(ax._r, ax.r2l);
53978
53979 return axDone;
53980 };
53981 }));
53982};
53983
53984/**
53985 * Draw one cartesian axis
53986 *
53987 * @param {DOM element} gd
53988 * @param {object} ax (full) axis object
53989 * @param {object} opts
53990 * - @param {boolean} skipTitle (set to true to skip axis title draw call)
53991 *
53992 * Depends on:
53993 * - ax._mainSubplot (from linkSubplots)
53994 * - ax._mainAxis
53995 * - ax._anchorAxis
53996 * - ax._subplotsWith
53997 * - ax._counterDomainMin, ax._counterDomainMax (optionally, from linkSubplots)
53998 * - ax._tickAngles (on redraw only, old value relinked during supplyDefaults)
53999 * - ax._mainLinePosition (from lsInner)
54000 * - ax._mainMirrorPosition
54001 * - ax._linepositions
54002 *
54003 * Fills in:
54004 * - ax._vals:
54005 * - ax._gridVals:
54006 * - ax._selections:
54007 * - ax._tickAngles:
54008 * - ax._depth (when required only):
54009 * - and calls ax.setScale
54010 */
54011axes.drawOne = function(gd, ax, opts) {
54012 opts = opts || {};
54013
54014 var i, sp, plotinfo;
54015
54016 ax.setScale();
54017
54018 var fullLayout = gd._fullLayout;
54019 var axId = ax._id;
54020 var axLetter = axId.charAt(0);
54021 var counterLetter = axes.counterLetter(axId);
54022 var mainPlotinfo = fullLayout._plots[ax._mainSubplot];
54023
54024 // this happens when updating matched group with 'missing' axes
54025 if(!mainPlotinfo) return;
54026
54027 var mainAxLayer = mainPlotinfo[axLetter + 'axislayer'];
54028 var mainLinePosition = ax._mainLinePosition;
54029 var mainMirrorPosition = ax._mainMirrorPosition;
54030
54031 var vals = ax._vals = axes.calcTicks(ax);
54032
54033 // Add a couple of axis properties that should cause us to recreate
54034 // elements. Used in d3 data function.
54035 var axInfo = [ax.mirror, mainLinePosition, mainMirrorPosition].join('_');
54036 for(i = 0; i < vals.length; i++) {
54037 vals[i].axInfo = axInfo;
54038 }
54039
54040 // stash selections to avoid DOM queries e.g.
54041 // - stash tickLabels selection, so that drawTitle can use it to scoot title
54042 ax._selections = {};
54043 // stash tick angle (including the computed 'auto' values) per tick-label class
54044 // linkup 'previous' tick angles on redraws
54045 if(ax._tickAngles) ax._prevTickAngles = ax._tickAngles;
54046 ax._tickAngles = {};
54047 // measure [in px] between axis position and outward-most part of bounding box
54048 // (touching either the tick label or ticks)
54049 // depth can be expansive to compute, so we only do so when required
54050 ax._depth = null;
54051
54052 // calcLabelLevelBbox can be expensive,
54053 // so make sure to not call it twice during the same Axes.drawOne call
54054 // by stashing label-level bounding boxes per tick-label class
54055 var llbboxes = {};
54056 function getLabelLevelBbox(suffix) {
54057 var cls = axId + (suffix || 'tick');
54058 if(!llbboxes[cls]) llbboxes[cls] = calcLabelLevelBbox(ax, cls);
54059 return llbboxes[cls];
54060 }
54061
54062 if(!ax.visible) return;
54063
54064 var transFn = axes.makeTransFn(ax);
54065 var tickVals;
54066 // We remove zero lines, grid lines, and inside ticks if they're within 1px of the end
54067 // The key case here is removing zero lines when the axis bound is zero
54068 var valsClipped;
54069
54070 if(ax.tickson === 'boundaries') {
54071 var boundaryVals = getBoundaryVals(ax, vals);
54072 valsClipped = axes.clipEnds(ax, boundaryVals);
54073 tickVals = ax.ticks === 'inside' ? valsClipped : boundaryVals;
54074 } else {
54075 valsClipped = axes.clipEnds(ax, vals);
54076 tickVals = ax.ticks === 'inside' ? valsClipped : vals;
54077 }
54078
54079 var gridVals = ax._gridVals = valsClipped;
54080 var dividerVals = getDividerVals(ax, vals);
54081
54082 if(!fullLayout._hasOnlyLargeSploms) {
54083 var subplotsWithAx = ax._subplotsWith;
54084
54085 // keep track of which subplots (by main counter axis) we've already
54086 // drawn grids for, so we don't overdraw overlaying subplots
54087 var finishedGrids = {};
54088
54089 for(i = 0; i < subplotsWithAx.length; i++) {
54090 sp = subplotsWithAx[i];
54091 plotinfo = fullLayout._plots[sp];
54092
54093 var counterAxis = plotinfo[counterLetter + 'axis'];
54094 var mainCounterID = counterAxis._mainAxis._id;
54095 if(finishedGrids[mainCounterID]) continue;
54096 finishedGrids[mainCounterID] = 1;
54097
54098 var gridPath = axLetter === 'x' ?
54099 'M0,' + counterAxis._offset + 'v' + counterAxis._length :
54100 'M' + counterAxis._offset + ',0h' + counterAxis._length;
54101
54102 axes.drawGrid(gd, ax, {
54103 vals: gridVals,
54104 counterAxis: counterAxis,
54105 layer: plotinfo.gridlayer.select('.' + axId),
54106 path: gridPath,
54107 transFn: transFn
54108 });
54109 axes.drawZeroLine(gd, ax, {
54110 counterAxis: counterAxis,
54111 layer: plotinfo.zerolinelayer,
54112 path: gridPath,
54113 transFn: transFn
54114 });
54115 }
54116 }
54117
54118 var tickSigns = axes.getTickSigns(ax);
54119 var tickSubplots = [];
54120
54121 if(ax.ticks) {
54122 var mainTickPath = axes.makeTickPath(ax, mainLinePosition, tickSigns[2]);
54123 var mirrorTickPath;
54124 var fullTickPath;
54125 if(ax._anchorAxis && ax.mirror && ax.mirror !== true) {
54126 mirrorTickPath = axes.makeTickPath(ax, mainMirrorPosition, tickSigns[3]);
54127 fullTickPath = mainTickPath + mirrorTickPath;
54128 } else {
54129 mirrorTickPath = '';
54130 fullTickPath = mainTickPath;
54131 }
54132
54133 var tickPath;
54134 if(ax.showdividers && ax.ticks === 'outside' && ax.tickson === 'boundaries') {
54135 var dividerLookup = {};
54136 for(i = 0; i < dividerVals.length; i++) {
54137 dividerLookup[dividerVals[i].x] = 1;
54138 }
54139 tickPath = function(d) {
54140 return dividerLookup[d.x] ? mirrorTickPath : fullTickPath;
54141 };
54142 } else {
54143 tickPath = fullTickPath;
54144 }
54145
54146 axes.drawTicks(gd, ax, {
54147 vals: tickVals,
54148 layer: mainAxLayer,
54149 path: tickPath,
54150 transFn: transFn
54151 });
54152
54153 if(ax.mirror === 'allticks') {
54154 tickSubplots = Object.keys(ax._linepositions || {});
54155 }
54156 }
54157
54158 for(i = 0; i < tickSubplots.length; i++) {
54159 sp = tickSubplots[i];
54160 plotinfo = fullLayout._plots[sp];
54161 // [bottom or left, top or right], free and main are handled above
54162 var linepositions = ax._linepositions[sp] || [];
54163 var spTickPath = axes.makeTickPath(ax, linepositions[0], tickSigns[0]) +
54164 axes.makeTickPath(ax, linepositions[1], tickSigns[1]);
54165
54166 axes.drawTicks(gd, ax, {
54167 vals: tickVals,
54168 layer: plotinfo[axLetter + 'axislayer'],
54169 path: spTickPath,
54170 transFn: transFn
54171 });
54172 }
54173
54174 var seq = [];
54175
54176 // tick labels - for now just the main labels.
54177 // TODO: mirror labels, esp for subplots
54178
54179 seq.push(function() {
54180 return axes.drawLabels(gd, ax, {
54181 vals: vals,
54182 layer: mainAxLayer,
54183 transFn: transFn,
54184 labelFns: axes.makeLabelFns(ax, mainLinePosition)
54185 });
54186 });
54187
54188 if(ax.type === 'multicategory') {
54189 var pad = {x: 2, y: 10}[axLetter];
54190
54191 seq.push(function() {
54192 var bboxKey = {x: 'height', y: 'width'}[axLetter];
54193 var standoff = getLabelLevelBbox()[bboxKey] + pad +
54194 (ax._tickAngles[axId + 'tick'] ? ax.tickfont.size * LINE_SPACING : 0);
54195
54196 return axes.drawLabels(gd, ax, {
54197 vals: getSecondaryLabelVals(ax, vals),
54198 layer: mainAxLayer,
54199 cls: axId + 'tick2',
54200 repositionOnUpdate: true,
54201 secondary: true,
54202 transFn: transFn,
54203 labelFns: axes.makeLabelFns(ax, mainLinePosition + standoff * tickSigns[4])
54204 });
54205 });
54206
54207 seq.push(function() {
54208 ax._depth = tickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePosition);
54209
54210 return drawDividers(gd, ax, {
54211 vals: dividerVals,
54212 layer: mainAxLayer,
54213 path: axes.makeTickPath(ax, mainLinePosition, tickSigns[4], ax._depth),
54214 transFn: transFn
54215 });
54216 });
54217 } else if(ax.title.hasOwnProperty('standoff')) {
54218 seq.push(function() {
54219 ax._depth = tickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePosition);
54220 });
54221 }
54222
54223 var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax);
54224
54225 seq.push(function() {
54226 var s = ax.side.charAt(0);
54227 var sMirror = OPPOSITE_SIDE[ax.side].charAt(0);
54228 var pos = axes.getPxPosition(gd, ax);
54229 var outsideTickLen = ax.ticks === 'outside' ? ax.ticklen : 0;
54230 var llbbox;
54231
54232 var push;
54233 var mirrorPush;
54234 var rangeSliderPush;
54235
54236 if(ax.automargin || hasRangeSlider) {
54237 if(ax.type === 'multicategory') {
54238 llbbox = getLabelLevelBbox('tick2');
54239 } else {
54240 llbbox = getLabelLevelBbox();
54241 if(axLetter === 'x' && s === 'b') {
54242 ax._depth = Math.max(llbbox.width > 0 ? llbbox.bottom - pos : 0, outsideTickLen);
54243 }
54244 }
54245 }
54246
54247 if(ax.automargin) {
54248 push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
54249 var domainIndices = [0, 1];
54250
54251 if(axLetter === 'x') {
54252 if(s === 'b') {
54253 push[s] = ax._depth;
54254 } else {
54255 push[s] = ax._depth = Math.max(llbbox.width > 0 ? pos - llbbox.top : 0, outsideTickLen);
54256 domainIndices.reverse();
54257 }
54258
54259 if(llbbox.width > 0) {
54260 var rExtra = llbbox.right - (ax._offset + ax._length);
54261 if(rExtra > 0) {
54262 push.xr = 1;
54263 push.r = rExtra;
54264 }
54265 var lExtra = ax._offset - llbbox.left;
54266 if(lExtra > 0) {
54267 push.xl = 0;
54268 push.l = lExtra;
54269 }
54270 }
54271 } else {
54272 if(s === 'l') {
54273 push[s] = ax._depth = Math.max(llbbox.height > 0 ? pos - llbbox.left : 0, outsideTickLen);
54274 } else {
54275 push[s] = ax._depth = Math.max(llbbox.height > 0 ? llbbox.right - pos : 0, outsideTickLen);
54276 domainIndices.reverse();
54277 }
54278
54279 if(llbbox.height > 0) {
54280 var bExtra = llbbox.bottom - (ax._offset + ax._length);
54281 if(bExtra > 0) {
54282 push.yb = 0;
54283 push.b = bExtra;
54284 }
54285 var tExtra = ax._offset - llbbox.top;
54286 if(tExtra > 0) {
54287 push.yt = 1;
54288 push.t = tExtra;
54289 }
54290 }
54291 }
54292
54293 push[counterLetter] = ax.anchor === 'free' ?
54294 ax.position :
54295 ax._anchorAxis.domain[domainIndices[0]];
54296
54297 if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
54298 push[s] += approxTitleDepth(ax) + (ax.title.standoff || 0);
54299 }
54300
54301 if(ax.mirror && ax.anchor !== 'free') {
54302 mirrorPush = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
54303
54304 mirrorPush[sMirror] = ax.linewidth;
54305 if(ax.mirror && ax.mirror !== true) mirrorPush[sMirror] += outsideTickLen;
54306
54307 if(ax.mirror === true || ax.mirror === 'ticks') {
54308 mirrorPush[counterLetter] = ax._anchorAxis.domain[domainIndices[1]];
54309 } else if(ax.mirror === 'all' || ax.mirror === 'allticks') {
54310 mirrorPush[counterLetter] = [ax._counterDomainMin, ax._counterDomainMax][domainIndices[1]];
54311 }
54312 }
54313 }
54314
54315 if(hasRangeSlider) {
54316 rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax);
54317 }
54318
54319 Plots.autoMargin(gd, axAutoMarginID(ax), push);
54320 Plots.autoMargin(gd, axMirrorAutoMarginID(ax), mirrorPush);
54321 Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
54322 });
54323
54324 if(!opts.skipTitle &&
54325 !(hasRangeSlider && ax.side === 'bottom')
54326 ) {
54327 seq.push(function() { return drawTitle(gd, ax); });
54328 }
54329
54330 return Lib.syncOrAsync(seq);
54331};
54332
54333function getBoundaryVals(ax, vals) {
54334 var out = [];
54335 var i;
54336
54337 // boundaryVals are never used for labels;
54338 // no need to worry about the other tickTextObj keys
54339 var _push = function(d, bndIndex) {
54340 var xb = d.xbnd[bndIndex];
54341 if(xb !== null) {
54342 out.push(Lib.extendFlat({}, d, {x: xb}));
54343 }
54344 };
54345
54346 if(vals.length) {
54347 for(i = 0; i < vals.length; i++) {
54348 _push(vals[i], 0);
54349 }
54350 _push(vals[i - 1], 1);
54351 }
54352
54353 return out;
54354}
54355
54356function getSecondaryLabelVals(ax, vals) {
54357 var out = [];
54358 var lookup = {};
54359
54360 for(var i = 0; i < vals.length; i++) {
54361 var d = vals[i];
54362 if(lookup[d.text2]) {
54363 lookup[d.text2].push(d.x);
54364 } else {
54365 lookup[d.text2] = [d.x];
54366 }
54367 }
54368
54369 for(var k in lookup) {
54370 out.push(tickTextObj(ax, Lib.interp(lookup[k], 0.5), k));
54371 }
54372
54373 return out;
54374}
54375
54376function getDividerVals(ax, vals) {
54377 var out = [];
54378 var i, current;
54379
54380 // never used for labels;
54381 // no need to worry about the other tickTextObj keys
54382 var _push = function(d, bndIndex) {
54383 var xb = d.xbnd[bndIndex];
54384 if(xb !== null) {
54385 out.push(Lib.extendFlat({}, d, {x: xb}));
54386 }
54387 };
54388
54389 if(ax.showdividers && vals.length) {
54390 for(i = 0; i < vals.length; i++) {
54391 var d = vals[i];
54392 if(d.text2 !== current) {
54393 _push(d, 0);
54394 }
54395 current = d.text2;
54396 }
54397 _push(vals[i - 1], 1);
54398 }
54399
54400 return out;
54401}
54402
54403function calcLabelLevelBbox(ax, cls) {
54404 var top, bottom;
54405 var left, right;
54406
54407 if(ax._selections[cls].size()) {
54408 top = Infinity;
54409 bottom = -Infinity;
54410 left = Infinity;
54411 right = -Infinity;
54412 ax._selections[cls].each(function() {
54413 var thisLabel = selectTickLabel(this);
54414 // Use parent node <g.(x|y)tick>, to make Drawing.bBox
54415 // retrieve a bbox computed with transform info
54416 //
54417 // To improve perf, it would be nice to use `thisLabel.node()`
54418 // (like in fixLabelOverlaps) instead and use Axes.getPxPosition
54419 // together with the makeLabelFns outputs and `tickangle`
54420 // to compute one bbox per (tick value x tick style)
54421 var bb = Drawing.bBox(thisLabel.node().parentNode);
54422 top = Math.min(top, bb.top);
54423 bottom = Math.max(bottom, bb.bottom);
54424 left = Math.min(left, bb.left);
54425 right = Math.max(right, bb.right);
54426 });
54427 } else {
54428 top = 0;
54429 bottom = 0;
54430 left = 0;
54431 right = 0;
54432 }
54433
54434 return {
54435 top: top,
54436 bottom: bottom,
54437 left: left,
54438 right: right,
54439 height: bottom - top,
54440 width: right - left
54441 };
54442}
54443
54444/**
54445 * Which direction do the 'ax.side' values, and free ticks go?
54446 *
54447 * @param {object} ax (full) axis object
54448 * - {string} _id (starting with 'x' or 'y')
54449 * - {string} side
54450 * - {string} ticks
54451 * @return {array} all entries are either -1 or 1
54452 * - [0]: sign for top/right ticks (i.e. negative SVG direction)
54453 * - [1]: sign for bottom/left ticks (i.e. positive SVG direction)
54454 * - [2]: sign for ticks corresponding to 'ax.side'
54455 * - [3]: sign for ticks mirroring 'ax.side'
54456 * - [4]: sign of arrow starting at axis pointing towards margin
54457 */
54458axes.getTickSigns = function(ax) {
54459 var axLetter = ax._id.charAt(0);
54460 var sideOpposite = {x: 'top', y: 'right'}[axLetter];
54461 var main = ax.side === sideOpposite ? 1 : -1;
54462 var out = [-1, 1, main, -main];
54463 // then we flip if outside XOR y axis
54464 if((ax.ticks !== 'inside') === (axLetter === 'x')) {
54465 out = out.map(function(v) { return -v; });
54466 }
54467 // independent of `ticks`; do not flip this one
54468 if(ax.side) {
54469 out.push({l: -1, t: -1, r: 1, b: 1}[ax.side.charAt(0)]);
54470 }
54471 return out;
54472};
54473
54474/**
54475 * Make axis translate transform function
54476 *
54477 * @param {object} ax (full) axis object
54478 * - {string} _id
54479 * - {number} _offset
54480 * - {fn} l2p
54481 * @return {fn} function of calcTicks items
54482 */
54483axes.makeTransFn = function(ax) {
54484 var axLetter = ax._id.charAt(0);
54485 var offset = ax._offset;
54486 return axLetter === 'x' ?
54487 function(d) { return 'translate(' + (offset + ax.l2p(d.x)) + ',0)'; } :
54488 function(d) { return 'translate(0,' + (offset + ax.l2p(d.x)) + ')'; };
54489};
54490
54491/**
54492 * Make axis tick path string
54493 *
54494 * @param {object} ax (full) axis object
54495 * - {string} _id
54496 * - {number} ticklen
54497 * - {number} linewidth
54498 * @param {number} shift along direction of ticklen
54499 * @param {1 or -1} sgn tick sign
54500 * @param {number (optional)} len tick length
54501 * @return {string}
54502 */
54503axes.makeTickPath = function(ax, shift, sgn, len) {
54504 len = len !== undefined ? len : ax.ticklen;
54505
54506 var axLetter = ax._id.charAt(0);
54507 var pad = (ax.linewidth || 1) / 2;
54508
54509 return axLetter === 'x' ?
54510 'M0,' + (shift + pad * sgn) + 'v' + (len * sgn) :
54511 'M' + (shift + pad * sgn) + ',0h' + (len * sgn);
54512};
54513
54514/**
54515 * Make axis tick label x, y and anchor functions
54516 *
54517 * @param {object} ax (full) axis object
54518 * - {string} _id
54519 * - {string} ticks
54520 * - {number} ticklen
54521 * - {string} side
54522 * - {number} linewidth
54523 * - {number} tickfont.size
54524 * - {boolean} showline
54525 * @param {number} shift
54526 * @param {number} angle [in degrees] ...
54527 * @return {object}
54528 * - {fn} xFn
54529 * - {fn} yFn
54530 * - {fn} anchorFn
54531 * - {fn} heightFn
54532 * - {number} labelStandoff (gap parallel to ticks)
54533 * - {number} labelShift (gap perpendicular to ticks)
54534 */
54535axes.makeLabelFns = function(ax, shift, angle) {
54536 var axLetter = ax._id.charAt(0);
54537 var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside';
54538
54539 var labelStandoff = 0;
54540 var labelShift = 0;
54541
54542 if(ticksOnOutsideLabels) {
54543 labelStandoff += ax.ticklen;
54544 }
54545 if(angle && ax.ticks === 'outside') {
54546 var rad = Lib.deg2rad(angle);
54547 labelStandoff = ax.ticklen * Math.cos(rad) + 1;
54548 labelShift = ax.ticklen * Math.sin(rad);
54549 }
54550 if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) {
54551 labelStandoff += 0.2 * ax.tickfont.size;
54552 }
54553 labelStandoff += (ax.linewidth || 1) / 2;
54554
54555 var out = {
54556 labelStandoff: labelStandoff,
54557 labelShift: labelShift
54558 };
54559
54560 var x0, y0, ff, flipIt;
54561
54562 if(axLetter === 'x') {
54563 flipIt = ax.side === 'bottom' ? 1 : -1;
54564 x0 = labelShift * flipIt;
54565 y0 = shift + labelStandoff * flipIt;
54566 ff = ax.side === 'bottom' ? 1 : -0.2;
54567
54568 out.xFn = function(d) { return d.dx + x0; };
54569 out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; };
54570 out.anchorFn = function(d, a) {
54571 if(!isNumeric(a) || a === 0 || a === 180) {
54572 return 'middle';
54573 }
54574 return (a * flipIt < 0) ? 'end' : 'start';
54575 };
54576 out.heightFn = function(d, a, h) {
54577 return (a < -60 || a > 60) ? -0.5 * h :
54578 ax.side === 'top' ? -h :
54579 0;
54580 };
54581 } else if(axLetter === 'y') {
54582 flipIt = ax.side === 'right' ? 1 : -1;
54583 x0 = labelStandoff;
54584 y0 = -labelShift * flipIt;
54585 ff = Math.abs(ax.tickangle) === 90 ? 0.5 : 0;
54586
54587 out.xFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; };
54588 out.yFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; };
54589 out.anchorFn = function(d, a) {
54590 if(isNumeric(a) && Math.abs(a) === 90) {
54591 return 'middle';
54592 }
54593 return ax.side === 'right' ? 'start' : 'end';
54594 };
54595 out.heightFn = function(d, a, h) {
54596 a *= ax.side === 'left' ? 1 : -1;
54597 return a < -30 ? -h :
54598 a < 30 ? -0.5 * h :
54599 0;
54600 };
54601 }
54602
54603 return out;
54604};
54605
54606function tickDataFn(d) {
54607 return [d.text, d.x, d.axInfo, d.font, d.fontSize, d.fontColor].join('_');
54608}
54609
54610/**
54611 * Draw axis ticks
54612 *
54613 * @param {DOM element} gd
54614 * @param {object} ax (full) axis object
54615 * - {string} _id
54616 * - {string} ticks
54617 * - {number} linewidth
54618 * - {string} tickcolor
54619 * @param {object} opts
54620 * - {array of object} vals (calcTicks output-like)
54621 * - {d3 selection} layer
54622 * - {string or fn} path
54623 * - {fn} transFn
54624 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54625 */
54626axes.drawTicks = function(gd, ax, opts) {
54627 opts = opts || {};
54628
54629 var cls = ax._id + 'tick';
54630
54631 var ticks = opts.layer.selectAll('path.' + cls)
54632 .data(ax.ticks ? opts.vals : [], tickDataFn);
54633
54634 ticks.exit().remove();
54635
54636 ticks.enter().append('path')
54637 .classed(cls, 1)
54638 .classed('ticks', 1)
54639 .classed('crisp', opts.crisp !== false)
54640 .call(Color.stroke, ax.tickcolor)
54641 .style('stroke-width', Drawing.crispRound(gd, ax.tickwidth, 1) + 'px')
54642 .attr('d', opts.path);
54643
54644 ticks.attr('transform', opts.transFn);
54645};
54646
54647/**
54648 * Draw axis grid
54649 *
54650 * @param {DOM element} gd
54651 * @param {object} ax (full) axis object
54652 * - {string} _id
54653 * - {boolean} showgrid
54654 * - {string} gridcolor
54655 * - {string} gridwidth
54656 * - {boolean} zeroline
54657 * - {string} type
54658 * - {string} dtick
54659 * @param {object} opts
54660 * - {array of object} vals (calcTicks output-like)
54661 * - {d3 selection} layer
54662 * - {object} counterAxis (full axis object corresponding to counter axis)
54663 * optional - only required if this axis supports zero lines
54664 * - {string or fn} path
54665 * - {fn} transFn
54666 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54667 */
54668axes.drawGrid = function(gd, ax, opts) {
54669 opts = opts || {};
54670
54671 var cls = ax._id + 'grid';
54672 var vals = opts.vals;
54673 var counterAx = opts.counterAxis;
54674 if(ax.showgrid === false) {
54675 vals = [];
54676 } else if(counterAx && axes.shouldShowZeroLine(gd, ax, counterAx)) {
54677 var isArrayMode = ax.tickmode === 'array';
54678 for(var i = 0; i < vals.length; i++) {
54679 var xi = vals[i].x;
54680 if(isArrayMode ? !xi : (Math.abs(xi) < ax.dtick / 100)) {
54681 vals = vals.slice(0, i).concat(vals.slice(i + 1));
54682 // In array mode you can in principle have multiple
54683 // ticks at 0, so test them all. Otherwise once we found
54684 // one we can stop.
54685 if(isArrayMode) i--;
54686 else break;
54687 }
54688 }
54689 }
54690
54691 var grid = opts.layer.selectAll('path.' + cls)
54692 .data(vals, tickDataFn);
54693
54694 grid.exit().remove();
54695
54696 grid.enter().append('path')
54697 .classed(cls, 1)
54698 .classed('crisp', opts.crisp !== false);
54699
54700 ax._gw = Drawing.crispRound(gd, ax.gridwidth, 1);
54701
54702 grid.attr('transform', opts.transFn)
54703 .attr('d', opts.path)
54704 .call(Color.stroke, ax.gridcolor || '#ddd')
54705 .style('stroke-width', ax._gw + 'px');
54706
54707 if(typeof opts.path === 'function') grid.attr('d', opts.path);
54708};
54709
54710/**
54711 * Draw axis zero-line
54712 *
54713 * @param {DOM element} gd
54714 * @param {object} ax (full) axis object
54715 * - {string} _id
54716 * - {boolean} zeroline
54717 * - {number} zerolinewidth
54718 * - {string} zerolinecolor
54719 * - {number (optional)} _gridWidthCrispRound
54720 * @param {object} opts
54721 * - {d3 selection} layer
54722 * - {object} counterAxis (full axis object corresponding to counter axis)
54723 * - {string or fn} path
54724 * - {fn} transFn
54725 * - {boolean} crisp (set to false to unset crisp-edge SVG rendering)
54726 */
54727axes.drawZeroLine = function(gd, ax, opts) {
54728 opts = opts || opts;
54729
54730 var cls = ax._id + 'zl';
54731 var show = axes.shouldShowZeroLine(gd, ax, opts.counterAxis);
54732
54733 var zl = opts.layer.selectAll('path.' + cls)
54734 .data(show ? [{x: 0, id: ax._id}] : []);
54735
54736 zl.exit().remove();
54737
54738 zl.enter().append('path')
54739 .classed(cls, 1)
54740 .classed('zl', 1)
54741 .classed('crisp', opts.crisp !== false)
54742 .each(function() {
54743 // use the fact that only one element can enter to trigger a sort.
54744 // If several zerolines enter at the same time we will sort once per,
54745 // but generally this should be a minimal overhead.
54746 opts.layer.selectAll('path').sort(function(da, db) {
54747 return axisIds.idSort(da.id, db.id);
54748 });
54749 });
54750
54751 zl.attr('transform', opts.transFn)
54752 .attr('d', opts.path)
54753 .call(Color.stroke, ax.zerolinecolor || Color.defaultLine)
54754 .style('stroke-width', Drawing.crispRound(gd, ax.zerolinewidth, ax._gw || 1) + 'px');
54755};
54756
54757/**
54758 * Draw axis tick labels
54759 *
54760 * @param {DOM element} gd
54761 * @param {object} ax (full) axis object
54762 * - {string} _id
54763 * - {boolean} showticklabels
54764 * - {number} tickangle
54765 * - {object (optional)} _selections
54766 * - {object} (optional)} _tickAngles
54767 * - {object} (optional)} _prevTickAngles
54768 * @param {object} opts
54769 * - {array of object} vals (calcTicks output-like)
54770 * - {d3 selection} layer
54771 * - {string (optional)} cls (node className)
54772 * - {boolean} repositionOnUpdate (set to true to reposition update selection)
54773 * - {boolean} secondary
54774 * - {fn} transFn
54775 * - {object} labelFns
54776 * + {fn} xFn
54777 * + {fn} yFn
54778 * + {fn} anchorFn
54779 * + {fn} heightFn
54780 */
54781axes.drawLabels = function(gd, ax, opts) {
54782 opts = opts || {};
54783
54784 var fullLayout = gd._fullLayout;
54785 var axId = ax._id;
54786 var axLetter = axId.charAt(0);
54787 var cls = opts.cls || axId + 'tick';
54788 var vals = opts.vals;
54789 var labelFns = opts.labelFns;
54790 var tickAngle = opts.secondary ? 0 : ax.tickangle;
54791 var prevAngle = (ax._prevTickAngles || {})[cls];
54792
54793 var tickLabels = opts.layer.selectAll('g.' + cls)
54794 .data(ax.showticklabels ? vals : [], tickDataFn);
54795
54796 var labelsReady = [];
54797
54798 tickLabels.enter().append('g')
54799 .classed(cls, 1)
54800 .append('text')
54801 // only so tex has predictable alignment that we can
54802 // alter later
54803 .attr('text-anchor', 'middle')
54804 .each(function(d) {
54805 var thisLabel = d3.select(this);
54806 var newPromise = gd._promises.length;
54807
54808 thisLabel
54809 .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d))
54810 .call(Drawing.font, d.font, d.fontSize, d.fontColor)
54811 .text(d.text)
54812 .call(svgTextUtils.convertToTspans, gd);
54813
54814 if(gd._promises[newPromise]) {
54815 // if we have an async label, we'll deal with that
54816 // all here so take it out of gd._promises and
54817 // instead position the label and promise this in
54818 // labelsReady
54819 labelsReady.push(gd._promises.pop().then(function() {
54820 positionLabels(thisLabel, tickAngle);
54821 }));
54822 } else {
54823 // sync label: just position it now.
54824 positionLabels(thisLabel, tickAngle);
54825 }
54826 });
54827
54828 tickLabels.exit().remove();
54829
54830 if(opts.repositionOnUpdate) {
54831 tickLabels.each(function(d) {
54832 d3.select(this).select('text')
54833 .call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d));
54834 });
54835 }
54836
54837 function positionLabels(s, angle) {
54838 s.each(function(d) {
54839 var thisLabel = d3.select(this);
54840 var mathjaxGroup = thisLabel.select('.text-math-group');
54841 var anchor = labelFns.anchorFn(d, angle);
54842
54843 var transform = opts.transFn.call(thisLabel.node(), d) +
54844 ((isNumeric(angle) && +angle !== 0) ?
54845 (' rotate(' + angle + ',' + labelFns.xFn(d) + ',' +
54846 (labelFns.yFn(d) - d.fontSize / 2) + ')') :
54847 '');
54848
54849 // how much to shift a multi-line label to center it vertically.
54850 var nLines = svgTextUtils.lineCount(thisLabel);
54851 var lineHeight = LINE_SPACING * d.fontSize;
54852 var anchorHeight = labelFns.heightFn(d, isNumeric(angle) ? +angle : 0, (nLines - 1) * lineHeight);
54853
54854 if(anchorHeight) {
54855 transform += ' translate(0, ' + anchorHeight + ')';
54856 }
54857
54858 if(mathjaxGroup.empty()) {
54859 thisLabel.select('text').attr({
54860 transform: transform,
54861 'text-anchor': anchor
54862 });
54863 } else {
54864 var mjWidth = Drawing.bBox(mathjaxGroup.node()).width;
54865 var mjShift = mjWidth * {end: -0.5, start: 0.5}[anchor];
54866 mathjaxGroup.attr('transform', transform + (mjShift ? 'translate(' + mjShift + ',0)' : ''));
54867 }
54868 });
54869 }
54870
54871 // make sure all labels are correctly positioned at their base angle
54872 // the positionLabels call above is only for newly drawn labels.
54873 // do this without waiting, using the last calculated angle to
54874 // minimize flicker, then do it again when we know all labels are
54875 // there, putting back the prescribed angle to check for overlaps.
54876 positionLabels(tickLabels, (prevAngle + 1) ? prevAngle : tickAngle);
54877
54878 function allLabelsReady() {
54879 return labelsReady.length && Promise.all(labelsReady);
54880 }
54881
54882 var autoangle = null;
54883
54884 function fixLabelOverlaps() {
54885 positionLabels(tickLabels, tickAngle);
54886
54887 // check for auto-angling if x labels overlap
54888 // don't auto-angle at all for log axes with
54889 // base and digit format
54890 if(vals.length && axLetter === 'x' && !isNumeric(tickAngle) &&
54891 (ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')
54892 ) {
54893 autoangle = 0;
54894
54895 var maxFontSize = 0;
54896 var lbbArray = [];
54897 var i;
54898
54899 tickLabels.each(function(d) {
54900 maxFontSize = Math.max(maxFontSize, d.fontSize);
54901
54902 var x = ax.l2p(d.x);
54903 var thisLabel = selectTickLabel(this);
54904 var bb = Drawing.bBox(thisLabel.node());
54905
54906 lbbArray.push({
54907 // ignore about y, just deal with x overlaps
54908 top: 0,
54909 bottom: 10,
54910 height: 10,
54911 left: x - bb.width / 2,
54912 // impose a 2px gap
54913 right: x + bb.width / 2 + 2,
54914 width: bb.width + 2
54915 });
54916 });
54917
54918 if((ax.tickson === 'boundaries' || ax.showdividers) && !opts.secondary) {
54919 var gap = 2;
54920 if(ax.ticks) gap += ax.tickwidth / 2;
54921
54922 // TODO should secondary labels also fall into this fix-overlap regime?
54923
54924 for(i = 0; i < lbbArray.length; i++) {
54925 var xbnd = vals[i].xbnd;
54926 var lbb = lbbArray[i];
54927 if(
54928 (xbnd[0] !== null && (lbb.left - ax.l2p(xbnd[0])) < gap) ||
54929 (xbnd[1] !== null && (ax.l2p(xbnd[1]) - lbb.right) < gap)
54930 ) {
54931 autoangle = 90;
54932 break;
54933 }
54934 }
54935 } else {
54936 var vLen = vals.length;
54937 var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1);
54938 var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory';
54939
54940 // any overlap at all - set 30 degrees or 90 degrees
54941 for(i = 0; i < lbbArray.length - 1; i++) {
54942 if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1])) {
54943 autoangle = rotate90 ? 90 : 30;
54944 break;
54945 }
54946 }
54947 }
54948
54949 if(autoangle) {
54950 positionLabels(tickLabels, autoangle);
54951 }
54952 }
54953 }
54954
54955 if(ax._selections) {
54956 ax._selections[cls] = tickLabels;
54957 }
54958
54959 var seq = [allLabelsReady];
54960
54961 // N.B. during auto-margin redraws, if the axis fixed its label overlaps
54962 // by rotating 90 degrees, do not attempt to re-fix its label overlaps
54963 // as this can lead to infinite redraw loops!
54964 if(ax.automargin && fullLayout._redrawFromAutoMarginCount && prevAngle === 90) {
54965 autoangle = 90;
54966 seq.push(function() {
54967 positionLabels(tickLabels, prevAngle);
54968 });
54969 } else {
54970 seq.push(fixLabelOverlaps);
54971 }
54972
54973 // save current tick angle for future redraws
54974 if(ax._tickAngles) {
54975 seq.push(function() {
54976 ax._tickAngles[cls] = autoangle === null ?
54977 (isNumeric(tickAngle) ? tickAngle : 0) :
54978 autoangle;
54979 });
54980 }
54981
54982 var done = Lib.syncOrAsync(seq);
54983 if(done && done.then) gd._promises.push(done);
54984 return done;
54985};
54986
54987/**
54988 * Draw axis dividers
54989 *
54990 * @param {DOM element} gd
54991 * @param {object} ax (full) axis object
54992 * - {string} _id
54993 * - {string} showdividers
54994 * - {number} dividerwidth
54995 * - {string} dividercolor
54996 * @param {object} opts
54997 * - {array of object} vals (calcTicks output-like)
54998 * - {d3 selection} layer
54999 * - {fn} path
55000 * - {fn} transFn
55001 */
55002function drawDividers(gd, ax, opts) {
55003 var cls = ax._id + 'divider';
55004 var vals = opts.vals;
55005
55006 var dividers = opts.layer.selectAll('path.' + cls)
55007 .data(vals, tickDataFn);
55008
55009 dividers.exit().remove();
55010
55011 dividers.enter().insert('path', ':first-child')
55012 .classed(cls, 1)
55013 .classed('crisp', 1)
55014 .call(Color.stroke, ax.dividercolor)
55015 .style('stroke-width', Drawing.crispRound(gd, ax.dividerwidth, 1) + 'px');
55016
55017 dividers
55018 .attr('transform', opts.transFn)
55019 .attr('d', opts.path);
55020}
55021
55022/**
55023 * Get axis position in px, that is the distance for the graph's
55024 * top (left) edge for x (y) axes.
55025 *
55026 * @param {DOM element} gd
55027 * @param {object} ax (full) axis object
55028 * - {string} _id
55029 * - {string} side
55030 * if anchored:
55031 * - {object} _anchorAxis
55032 * Otherwise:
55033 * - {number} position
55034 * @return {number}
55035 */
55036axes.getPxPosition = function(gd, ax) {
55037 var gs = gd._fullLayout._size;
55038 var axLetter = ax._id.charAt(0);
55039 var side = ax.side;
55040 var anchorAxis;
55041
55042 if(ax.anchor !== 'free') {
55043 anchorAxis = ax._anchorAxis;
55044 } else if(axLetter === 'x') {
55045 anchorAxis = {
55046 _offset: gs.t + (1 - (ax.position || 0)) * gs.h,
55047 _length: 0
55048 };
55049 } else if(axLetter === 'y') {
55050 anchorAxis = {
55051 _offset: gs.l + (ax.position || 0) * gs.w,
55052 _length: 0
55053 };
55054 }
55055
55056 if(side === 'top' || side === 'left') {
55057 return anchorAxis._offset;
55058 } else if(side === 'bottom' || side === 'right') {
55059 return anchorAxis._offset + anchorAxis._length;
55060 }
55061};
55062
55063/**
55064 * Approximate axis title depth (w/o computing its bounding box)
55065 *
55066 * @param {object} ax (full) axis object
55067 * - {string} title.text
55068 * - {number} title.font.size
55069 * - {number} title.standoff
55070 * @return {number} (in px)
55071 */
55072function approxTitleDepth(ax) {
55073 var fontSize = ax.title.font.size;
55074 var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
55075 if(ax.title.hasOwnProperty('standoff')) {
55076 return extraLines ?
55077 fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING)) :
55078 fontSize * CAP_SHIFT;
55079 } else {
55080 return extraLines ?
55081 fontSize * (extraLines + 1) * LINE_SPACING :
55082 fontSize;
55083 }
55084}
55085
55086/**
55087 * Draw axis title, compute default standoff if necessary
55088 *
55089 * @param {DOM element} gd
55090 * @param {object} ax (full) axis object
55091 * - {string} _id
55092 * - {string} _name
55093 * - {string} side
55094 * - {number} title.font.size
55095 * - {object} _selections
55096 *
55097 * - {number} _depth
55098 * - {number} title.standoff
55099 * OR
55100 * - {number} linewidth
55101 * - {boolean} showticklabels
55102 */
55103function drawTitle(gd, ax) {
55104 var fullLayout = gd._fullLayout;
55105 var axId = ax._id;
55106 var axLetter = axId.charAt(0);
55107 var fontSize = ax.title.font.size;
55108
55109 var titleStandoff;
55110
55111 if(ax.title.hasOwnProperty('standoff')) {
55112 titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax);
55113 } else {
55114 if(ax.type === 'multicategory') {
55115 titleStandoff = ax._depth;
55116 } else {
55117 var offsetBase = 1.5;
55118 titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
55119 }
55120
55121 if(axLetter === 'x') {
55122 titleStandoff += ax.side === 'top' ?
55123 fontSize * (ax.showticklabels ? 1 : 0) :
55124 fontSize * (ax.showticklabels ? 1.5 : 0.5);
55125 } else {
55126 titleStandoff += ax.side === 'right' ?
55127 fontSize * (ax.showticklabels ? 1 : 0.5) :
55128 fontSize * (ax.showticklabels ? 0.5 : 0);
55129 }
55130 }
55131
55132 var pos = axes.getPxPosition(gd, ax);
55133 var transform, x, y;
55134
55135 if(axLetter === 'x') {
55136 x = ax._offset + ax._length / 2;
55137 y = (ax.side === 'top') ? pos - titleStandoff : pos + titleStandoff;
55138 } else {
55139 y = ax._offset + ax._length / 2;
55140 x = (ax.side === 'right') ? pos + titleStandoff : pos - titleStandoff;
55141 transform = {rotate: '-90', offset: 0};
55142 }
55143
55144 var avoid;
55145
55146 if(ax.type !== 'multicategory') {
55147 var tickLabels = ax._selections[ax._id + 'tick'];
55148
55149 avoid = {
55150 selection: tickLabels,
55151 side: ax.side
55152 };
55153
55154 if(tickLabels && tickLabels.node() && tickLabels.node().parentNode) {
55155 var translation = Drawing.getTranslate(tickLabels.node().parentNode);
55156 avoid.offsetLeft = translation.x;
55157 avoid.offsetTop = translation.y;
55158 }
55159
55160 if(ax.title.hasOwnProperty('standoff')) {
55161 avoid.pad = 0;
55162 }
55163 }
55164
55165 return Titles.draw(gd, axId + 'title', {
55166 propContainer: ax,
55167 propName: ax._name + '.title.text',
55168 placeholder: fullLayout._dfltTitle[axLetter],
55169 avoid: avoid,
55170 transform: transform,
55171 attributes: {x: x, y: y, 'text-anchor': 'middle'}
55172 });
55173}
55174
55175axes.shouldShowZeroLine = function(gd, ax, counterAxis) {
55176 var rng = Lib.simpleMap(ax.range, ax.r2l);
55177 return (
55178 (rng[0] * rng[1] <= 0) &&
55179 ax.zeroline &&
55180 (ax.type === 'linear' || ax.type === '-') &&
55181 !(ax.rangebreaks && ax.maskBreaks(0) === BADNUM) &&
55182 (
55183 clipEnds(ax, 0) ||
55184 !anyCounterAxLineAtZero(gd, ax, counterAxis, rng) ||
55185 hasBarsOrFill(gd, ax)
55186 )
55187 );
55188};
55189
55190axes.clipEnds = function(ax, vals) {
55191 return vals.filter(function(d) { return clipEnds(ax, d.x); });
55192};
55193
55194function clipEnds(ax, l) {
55195 var p = ax.l2p(l);
55196 return (p > 1 && p < ax._length - 1);
55197}
55198
55199function anyCounterAxLineAtZero(gd, ax, counterAxis, rng) {
55200 var mainCounterAxis = counterAxis._mainAxis;
55201 if(!mainCounterAxis) return;
55202
55203 var fullLayout = gd._fullLayout;
55204 var axLetter = ax._id.charAt(0);
55205 var counterLetter = axes.counterLetter(ax._id);
55206
55207 var zeroPosition = ax._offset + (
55208 ((Math.abs(rng[0]) < Math.abs(rng[1])) === (axLetter === 'x')) ?
55209 0 : ax._length
55210 );
55211
55212 function lineNearZero(ax2) {
55213 if(!ax2.showline || !ax2.linewidth) return false;
55214 var tolerance = Math.max((ax2.linewidth + ax.zerolinewidth) / 2, 1);
55215
55216 function closeEnough(pos2) {
55217 return typeof pos2 === 'number' && Math.abs(pos2 - zeroPosition) < tolerance;
55218 }
55219
55220 if(closeEnough(ax2._mainLinePosition) || closeEnough(ax2._mainMirrorPosition)) {
55221 return true;
55222 }
55223 var linePositions = ax2._linepositions || {};
55224 for(var k in linePositions) {
55225 if(closeEnough(linePositions[k][0]) || closeEnough(linePositions[k][1])) {
55226 return true;
55227 }
55228 }
55229 }
55230
55231 var plotinfo = fullLayout._plots[counterAxis._mainSubplot];
55232 if(!(plotinfo.mainplotinfo || plotinfo).overlays.length) {
55233 return lineNearZero(counterAxis, zeroPosition);
55234 }
55235
55236 var counterLetterAxes = axes.list(gd, counterLetter);
55237 for(var i = 0; i < counterLetterAxes.length; i++) {
55238 var counterAxis2 = counterLetterAxes[i];
55239 if(
55240 counterAxis2._mainAxis === mainCounterAxis &&
55241 lineNearZero(counterAxis2, zeroPosition)
55242 ) {
55243 return true;
55244 }
55245 }
55246}
55247
55248function hasBarsOrFill(gd, ax) {
55249 var fullData = gd._fullData;
55250 var subplot = ax._mainSubplot;
55251 var axLetter = ax._id.charAt(0);
55252
55253 for(var i = 0; i < fullData.length; i++) {
55254 var trace = fullData[i];
55255
55256 if(trace.visible === true && (trace.xaxis + trace.yaxis) === subplot) {
55257 if(
55258 Registry.traceIs(trace, 'bar-like') &&
55259 trace.orientation === {x: 'h', y: 'v'}[axLetter]
55260 ) return true;
55261
55262 if(
55263 trace.fill &&
55264 trace.fill.charAt(trace.fill.length - 1) === axLetter
55265 ) return true;
55266 }
55267 }
55268 return false;
55269}
55270
55271function selectTickLabel(gTick) {
55272 var s = d3.select(gTick);
55273 var mj = s.select('.text-math-group');
55274 return mj.empty() ? s.select('text') : mj;
55275}
55276
55277/**
55278 * Find all margin pushers for 2D axes and reserve them for later use
55279 * Both label and rangeslider automargin calculations happen later so
55280 * we need to explicitly allow their ids in order to not delete them.
55281 *
55282 * TODO: can we pull the actual automargin calls forward to avoid this hack?
55283 * We're probably also doing multiple redraws in this case, would be faster
55284 * if we can just do the whole calculation ahead of time and draw once.
55285 */
55286axes.allowAutoMargin = function(gd) {
55287 var axList = axes.list(gd, '', true);
55288 for(var i = 0; i < axList.length; i++) {
55289 var ax = axList[i];
55290 if(ax.automargin) {
55291 Plots.allowAutoMargin(gd, axAutoMarginID(ax));
55292 if(ax.mirror) {
55293 Plots.allowAutoMargin(gd, axMirrorAutoMarginID(ax));
55294 }
55295 }
55296 if(Registry.getComponentMethod('rangeslider', 'isVisible')(ax)) {
55297 Plots.allowAutoMargin(gd, rangeSliderAutoMarginID(ax));
55298 }
55299 }
55300};
55301
55302function axAutoMarginID(ax) { return ax._id + '.automargin'; }
55303function axMirrorAutoMarginID(ax) { return axAutoMarginID(ax) + '.mirror'; }
55304function rangeSliderAutoMarginID(ax) { return ax._id + '.rangeslider'; }
55305
55306// swap all the presentation attributes of the axes showing these traces
55307axes.swap = function(gd, traces) {
55308 var axGroups = makeAxisGroups(gd, traces);
55309
55310 for(var i = 0; i < axGroups.length; i++) {
55311 swapAxisGroup(gd, axGroups[i].x, axGroups[i].y);
55312 }
55313};
55314
55315function makeAxisGroups(gd, traces) {
55316 var groups = [];
55317 var i, j;
55318
55319 for(i = 0; i < traces.length; i++) {
55320 var groupsi = [];
55321 var xi = gd._fullData[traces[i]].xaxis;
55322 var yi = gd._fullData[traces[i]].yaxis;
55323 if(!xi || !yi) continue; // not a 2D cartesian trace?
55324
55325 for(j = 0; j < groups.length; j++) {
55326 if(groups[j].x.indexOf(xi) !== -1 || groups[j].y.indexOf(yi) !== -1) {
55327 groupsi.push(j);
55328 }
55329 }
55330
55331 if(!groupsi.length) {
55332 groups.push({x: [xi], y: [yi]});
55333 continue;
55334 }
55335
55336 var group0 = groups[groupsi[0]];
55337 var groupj;
55338
55339 if(groupsi.length > 1) {
55340 for(j = 1; j < groupsi.length; j++) {
55341 groupj = groups[groupsi[j]];
55342 mergeAxisGroups(group0.x, groupj.x);
55343 mergeAxisGroups(group0.y, groupj.y);
55344 }
55345 }
55346 mergeAxisGroups(group0.x, [xi]);
55347 mergeAxisGroups(group0.y, [yi]);
55348 }
55349
55350 return groups;
55351}
55352
55353function mergeAxisGroups(intoSet, fromSet) {
55354 for(var i = 0; i < fromSet.length; i++) {
55355 if(intoSet.indexOf(fromSet[i]) === -1) intoSet.push(fromSet[i]);
55356 }
55357}
55358
55359function swapAxisGroup(gd, xIds, yIds) {
55360 var xFullAxes = [];
55361 var yFullAxes = [];
55362 var layout = gd.layout;
55363 var i, j;
55364
55365 for(i = 0; i < xIds.length; i++) xFullAxes.push(axes.getFromId(gd, xIds[i]));
55366 for(i = 0; i < yIds.length; i++) yFullAxes.push(axes.getFromId(gd, yIds[i]));
55367
55368 var allAxKeys = Object.keys(axAttrs);
55369
55370 var noSwapAttrs = [
55371 'anchor', 'domain', 'overlaying', 'position', 'side', 'tickangle', 'editType'
55372 ];
55373 var numericTypes = ['linear', 'log'];
55374
55375 for(i = 0; i < allAxKeys.length; i++) {
55376 var keyi = allAxKeys[i];
55377 var xVal = xFullAxes[0][keyi];
55378 var yVal = yFullAxes[0][keyi];
55379 var allEqual = true;
55380 var coerceLinearX = false;
55381 var coerceLinearY = false;
55382 if(keyi.charAt(0) === '_' || typeof xVal === 'function' ||
55383 noSwapAttrs.indexOf(keyi) !== -1) {
55384 continue;
55385 }
55386 for(j = 1; j < xFullAxes.length && allEqual; j++) {
55387 var xVali = xFullAxes[j][keyi];
55388 if(keyi === 'type' && numericTypes.indexOf(xVal) !== -1 &&
55389 numericTypes.indexOf(xVali) !== -1 && xVal !== xVali) {
55390 // type is special - if we find a mixture of linear and log,
55391 // coerce them all to linear on flipping
55392 coerceLinearX = true;
55393 } else if(xVali !== xVal) allEqual = false;
55394 }
55395 for(j = 1; j < yFullAxes.length && allEqual; j++) {
55396 var yVali = yFullAxes[j][keyi];
55397 if(keyi === 'type' && numericTypes.indexOf(yVal) !== -1 &&
55398 numericTypes.indexOf(yVali) !== -1 && yVal !== yVali) {
55399 // type is special - if we find a mixture of linear and log,
55400 // coerce them all to linear on flipping
55401 coerceLinearY = true;
55402 } else if(yFullAxes[j][keyi] !== yVal) allEqual = false;
55403 }
55404 if(allEqual) {
55405 if(coerceLinearX) layout[xFullAxes[0]._name].type = 'linear';
55406 if(coerceLinearY) layout[yFullAxes[0]._name].type = 'linear';
55407 swapAxisAttrs(layout, keyi, xFullAxes, yFullAxes, gd._fullLayout._dfltTitle);
55408 }
55409 }
55410
55411 // now swap x&y for any annotations anchored to these x & y
55412 for(i = 0; i < gd._fullLayout.annotations.length; i++) {
55413 var ann = gd._fullLayout.annotations[i];
55414 if(xIds.indexOf(ann.xref) !== -1 &&
55415 yIds.indexOf(ann.yref) !== -1) {
55416 Lib.swapAttrs(layout.annotations[i], ['?']);
55417 }
55418 }
55419}
55420
55421function swapAxisAttrs(layout, key, xFullAxes, yFullAxes, dfltTitle) {
55422 // in case the value is the default for either axis,
55423 // look at the first axis in each list and see if
55424 // this key's value is undefined
55425 var np = Lib.nestedProperty;
55426 var xVal = np(layout[xFullAxes[0]._name], key).get();
55427 var yVal = np(layout[yFullAxes[0]._name], key).get();
55428 var i;
55429
55430 if(key === 'title') {
55431 // special handling of placeholder titles
55432 if(xVal && xVal.text === dfltTitle.x) {
55433 xVal.text = dfltTitle.y;
55434 }
55435 if(yVal && yVal.text === dfltTitle.y) {
55436 yVal.text = dfltTitle.x;
55437 }
55438 }
55439
55440 for(i = 0; i < xFullAxes.length; i++) {
55441 np(layout, xFullAxes[i]._name + '.' + key).set(yVal);
55442 }
55443 for(i = 0; i < yFullAxes.length; i++) {
55444 np(layout, yFullAxes[i]._name + '.' + key).set(xVal);
55445 }
55446}
55447
55448function isAngular(ax) {
55449 return ax._id === 'angularaxis';
55450}
55451
55452function moveOutsideBreak(v, ax) {
55453 var len = ax._rangebreaks.length;
55454 for(var k = 0; k < len; k++) {
55455 var brk = ax._rangebreaks[k];
55456 if(v >= brk.min && v < brk.max) {
55457 return brk.max;
55458 }
55459 }
55460 return v;
55461}
55462
55463},{"../../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){
55464/**
55465* Copyright 2012-2020, Plotly, Inc.
55466* All rights reserved.
55467*
55468* This source code is licensed under the MIT license found in the
55469* LICENSE file in the root directory of this source tree.
55470*/
55471
55472
55473'use strict';
55474
55475var isNumeric = _dereq_('fast-isnumeric');
55476
55477var Lib = _dereq_('../../lib');
55478var BADNUM = _dereq_('../../constants/numerical').BADNUM;
55479
55480module.exports = function autoType(array, calendar, opts) {
55481 opts = opts || {};
55482
55483 if(!opts.noMultiCategory && multiCategory(array)) return 'multicategory';
55484 if(moreDates(array, calendar)) return 'date';
55485 if(category(array)) return 'category';
55486 if(linearOK(array)) return 'linear';
55487 else return '-';
55488};
55489
55490// is there at least one number in array? If not, we should leave
55491// ax.type empty so it can be autoset later
55492function linearOK(array) {
55493 if(!array) return false;
55494
55495 for(var i = 0; i < array.length; i++) {
55496 if(isNumeric(array[i])) return true;
55497 }
55498
55499 return false;
55500}
55501
55502// does the array a have mostly dates rather than numbers?
55503// note: some values can be neither (such as blanks, text)
55504// 2- or 4-digit integers can be both, so require twice as many
55505// dates as non-dates, to exclude cases with mostly 2 & 4 digit
55506// numbers and a few dates
55507// as with categories, consider DISTINCT values only.
55508function moreDates(a, calendar) {
55509 // test at most 1000 points, evenly spaced
55510 var inc = Math.max(1, (a.length - 1) / 1000);
55511 var dcnt = 0;
55512 var ncnt = 0;
55513 var seen = {};
55514
55515 for(var i = 0; i < a.length; i += inc) {
55516 var ai = a[Math.round(i)];
55517 var stri = String(ai);
55518 if(seen[stri]) continue;
55519 seen[stri] = 1;
55520
55521 if(Lib.isDateTime(ai, calendar)) dcnt += 1;
55522 if(isNumeric(ai)) ncnt += 1;
55523 }
55524
55525 return (dcnt > ncnt * 2);
55526}
55527
55528// are the (x,y)-values in gd.data mostly text?
55529// require twice as many DISTINCT categories as distinct numbers
55530function category(a) {
55531 // test at most 1000 points
55532 var inc = Math.max(1, (a.length - 1) / 1000);
55533 var curvenums = 0;
55534 var curvecats = 0;
55535 var seen = {};
55536
55537 for(var i = 0; i < a.length; i += inc) {
55538 var ai = a[Math.round(i)];
55539 var stri = String(ai);
55540 if(seen[stri]) continue;
55541 seen[stri] = 1;
55542
55543 if(typeof ai === 'boolean') curvecats++;
55544 else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
55545 else if(typeof ai === 'string') curvecats++;
55546 }
55547
55548 return curvecats > curvenums * 2;
55549}
55550
55551// very-loose requirements for multicategory,
55552// trace modules that should never auto-type to multicategory
55553// should be declared with 'noMultiCategory'
55554function multiCategory(a) {
55555 return Lib.isArrayOrTypedArray(a[0]) && Lib.isArrayOrTypedArray(a[1]);
55556}
55557
55558},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],224:[function(_dereq_,module,exports){
55559/**
55560* Copyright 2012-2020, Plotly, Inc.
55561* All rights reserved.
55562*
55563* This source code is licensed under the MIT license found in the
55564* LICENSE file in the root directory of this source tree.
55565*/
55566
55567'use strict';
55568
55569var isNumeric = _dereq_('fast-isnumeric');
55570
55571var Registry = _dereq_('../../registry');
55572var Lib = _dereq_('../../lib');
55573
55574var handleArrayContainerDefaults = _dereq_('../array_container_defaults');
55575
55576var layoutAttributes = _dereq_('./layout_attributes');
55577var handleTickValueDefaults = _dereq_('./tick_value_defaults');
55578var handleTickMarkDefaults = _dereq_('./tick_mark_defaults');
55579var handleTickLabelDefaults = _dereq_('./tick_label_defaults');
55580var handleCategoryOrderDefaults = _dereq_('./category_order_defaults');
55581var handleLineGridDefaults = _dereq_('./line_grid_defaults');
55582var setConvert = _dereq_('./set_convert');
55583
55584var DAY_OF_WEEK = _dereq_('./constants').WEEKDAY_PATTERN;
55585var HOUR = _dereq_('./constants').HOUR_PATTERN;
55586
55587/**
55588 * options: object containing:
55589 *
55590 * letter: 'x' or 'y'
55591 * title: name of the axis (ie 'Colorbar') to go in default title
55592 * font: the default font to inherit
55593 * outerTicks: boolean, should ticks default to outside?
55594 * showGrid: boolean, should gridlines be shown by default?
55595 * noHover: boolean, this axis doesn't support hover effects?
55596 * noTickson: boolean, this axis doesn't support 'tickson'
55597 * data: the plot data, used to manage categories
55598 * bgColor: the plot background color, to calculate default gridline colors
55599 * calendar:
55600 * splomStash:
55601 * visibleDflt: boolean
55602 * reverseDflt: boolean
55603 * automargin: boolean
55604 */
55605module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, options, layoutOut) {
55606 var letter = options.letter;
55607 var font = options.font || {};
55608 var splomStash = options.splomStash || {};
55609
55610 var visible = coerce('visible', !options.visibleDflt);
55611
55612 var axTemplate = containerOut._template || {};
55613 var axType = containerOut.type || axTemplate.type || '-';
55614
55615 if(axType === 'date') {
55616 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
55617 handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar);
55618 }
55619
55620 setConvert(containerOut, layoutOut);
55621
55622 var autorangeDflt = !containerOut.isValidRange(containerIn.range);
55623 if(autorangeDflt && options.reverseDflt) autorangeDflt = 'reversed';
55624 var autoRange = coerce('autorange', autorangeDflt);
55625 if(autoRange && (axType === 'linear' || axType === '-')) coerce('rangemode');
55626
55627 coerce('range');
55628 containerOut.cleanRange();
55629
55630 handleCategoryOrderDefaults(containerIn, containerOut, coerce, options);
55631
55632 if(axType !== 'category' && !options.noHover) coerce('hoverformat');
55633
55634 var dfltColor = coerce('color');
55635 // if axis.color was provided, use it for fonts too; otherwise,
55636 // inherit from global font color in case that was provided.
55637 // Compare to dflt rather than to containerIn, so we can provide color via
55638 // template too.
55639 var dfltFontColor = (dfltColor !== layoutAttributes.color.dflt) ? dfltColor : font.color;
55640 // try to get default title from splom trace, fallback to graph-wide value
55641 var dfltTitle = splomStash.label || layoutOut._dfltTitle[letter];
55642
55643 handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 1});
55644 if(!visible) return containerOut;
55645
55646 coerce('title.text', dfltTitle);
55647 Lib.coerceFont(coerce, 'title.font', {
55648 family: font.family,
55649 size: Math.round(font.size * 1.2),
55650 color: dfltFontColor
55651 });
55652
55653 handleTickValueDefaults(containerIn, containerOut, coerce, axType);
55654 handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, {pass: 2});
55655 handleTickMarkDefaults(containerIn, containerOut, coerce, options);
55656 handleLineGridDefaults(containerIn, containerOut, coerce, {
55657 dfltColor: dfltColor,
55658 bgColor: options.bgColor,
55659 showGrid: options.showGrid,
55660 attributes: layoutAttributes
55661 });
55662
55663 if(containerOut.showline || containerOut.ticks) coerce('mirror');
55664
55665 if(options.automargin) coerce('automargin');
55666
55667 var isMultiCategory = axType === 'multicategory';
55668
55669 if(!options.noTickson &&
55670 (axType === 'category' || isMultiCategory) &&
55671 (containerOut.ticks || containerOut.showgrid)
55672 ) {
55673 var ticksonDflt;
55674 if(isMultiCategory) ticksonDflt = 'boundaries';
55675 coerce('tickson', ticksonDflt);
55676 }
55677
55678 if(isMultiCategory) {
55679 var showDividers = coerce('showdividers');
55680 if(showDividers) {
55681 coerce('dividercolor');
55682 coerce('dividerwidth');
55683 }
55684 }
55685
55686 if(axType === 'date') {
55687 handleArrayContainerDefaults(containerIn, containerOut, {
55688 name: 'rangebreaks',
55689 inclusionAttr: 'enabled',
55690 handleItemDefaults: rangebreaksDefaults
55691 });
55692
55693 if(!containerOut.rangebreaks.length) {
55694 delete containerOut.rangebreaks;
55695 } else {
55696 for(var k = 0; k < containerOut.rangebreaks.length; k++) {
55697 if(containerOut.rangebreaks[k].pattern === DAY_OF_WEEK) {
55698 containerOut._hasDayOfWeekBreaks = true;
55699 break;
55700 }
55701 }
55702
55703 setConvert(containerOut, layoutOut);
55704
55705 if(layoutOut._has('scattergl') || layoutOut._has('splom')) {
55706 for(var i = 0; i < options.data.length; i++) {
55707 var trace = options.data[i];
55708 if(trace.type === 'scattergl' || trace.type === 'splom') {
55709 trace.visible = false;
55710 Lib.warn(trace.type +
55711 ' traces do not work on axes with rangebreaks.' +
55712 ' Setting trace ' + trace.index + ' to `visible: false`.');
55713 }
55714 }
55715 }
55716 }
55717 }
55718
55719 return containerOut;
55720};
55721
55722function rangebreaksDefaults(itemIn, itemOut, containerOut) {
55723 function coerce(attr, dflt) {
55724 return Lib.coerce(itemIn, itemOut, layoutAttributes.rangebreaks, attr, dflt);
55725 }
55726
55727 var enabled = coerce('enabled');
55728
55729 if(enabled) {
55730 var bnds = coerce('bounds');
55731 if(bnds && bnds.length >= 2) {
55732 var dfltPattern = '';
55733 var i, q;
55734 if(bnds.length === 2) {
55735 for(i = 0; i < 2; i++) {
55736 q = indexOfDay(bnds[i]);
55737 if(q) {
55738 dfltPattern = DAY_OF_WEEK;
55739 break;
55740 }
55741 }
55742 }
55743 var pattern = coerce('pattern', dfltPattern);
55744 if(pattern === DAY_OF_WEEK) {
55745 for(i = 0; i < 2; i++) {
55746 q = indexOfDay(bnds[i]);
55747 if(q) {
55748 // convert to integers i.e 'Sunday' --> 0
55749 itemOut.bounds[i] = bnds[i] = q - 1;
55750 }
55751 }
55752 }
55753 if(pattern) {
55754 // ensure types and ranges
55755 for(i = 0; i < 2; i++) {
55756 q = bnds[i];
55757 switch(pattern) {
55758 case DAY_OF_WEEK :
55759 if(!isNumeric(q)) {
55760 itemOut.enabled = false;
55761 return;
55762 }
55763 q = +q;
55764
55765 if(
55766 q !== Math.floor(q) || // don't accept fractional days for mow
55767 q < 0 || q >= 7
55768 ) {
55769 itemOut.enabled = false;
55770 return;
55771 }
55772 // use number
55773 itemOut.bounds[i] = bnds[i] = q;
55774 break;
55775
55776 case HOUR :
55777 if(!isNumeric(q)) {
55778 itemOut.enabled = false;
55779 return;
55780 }
55781 q = +q;
55782
55783 if(q < 0 || q > 24) { // accept 24
55784 itemOut.enabled = false;
55785 return;
55786 }
55787 // use number
55788 itemOut.bounds[i] = bnds[i] = q;
55789 break;
55790 }
55791 }
55792 }
55793
55794 if(containerOut.autorange === false) {
55795 var rng = containerOut.range;
55796
55797 // if bounds are bigger than the (set) range, disable break
55798 if(rng[0] < rng[1]) {
55799 if(bnds[0] < rng[0] && bnds[1] > rng[1]) {
55800 itemOut.enabled = false;
55801 return;
55802 }
55803 } else if(bnds[0] > rng[0] && bnds[1] < rng[1]) {
55804 itemOut.enabled = false;
55805 return;
55806 }
55807 }
55808 } else {
55809 var values = coerce('values');
55810
55811 if(values && values.length) {
55812 coerce('dvalue');
55813 } else {
55814 itemOut.enabled = false;
55815 return;
55816 }
55817 }
55818 }
55819}
55820
55821// these numbers are one more than what bounds would be mapped to
55822var dayStrToNum = {
55823 sun: 1,
55824 mon: 2,
55825 tue: 3,
55826 wed: 4,
55827 thu: 5,
55828 fri: 6,
55829 sat: 7
55830};
55831
55832function indexOfDay(v) {
55833 if(typeof v !== 'string') return;
55834 return dayStrToNum[
55835 v.substr(0, 3).toLowerCase()
55836 ];
55837}
55838
55839},{"../../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){
55840/**
55841* Copyright 2012-2020, Plotly, Inc.
55842* All rights reserved.
55843*
55844* This source code is licensed under the MIT license found in the
55845* LICENSE file in the root directory of this source tree.
55846*/
55847
55848'use strict';
55849
55850var Registry = _dereq_('../../registry');
55851
55852var constants = _dereq_('./constants');
55853
55854
55855// convert between axis names (xaxis, xaxis2, etc, elements of gd.layout)
55856// and axis id's (x, x2, etc). Would probably have ditched 'xaxis'
55857// completely in favor of just 'x' if it weren't ingrained in the API etc.
55858exports.id2name = function id2name(id) {
55859 if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
55860 var axNum = id.substr(1);
55861 if(axNum === '1') axNum = '';
55862 return id.charAt(0) + 'axis' + axNum;
55863};
55864
55865exports.name2id = function name2id(name) {
55866 if(!name.match(constants.AX_NAME_PATTERN)) return;
55867 var axNum = name.substr(5);
55868 if(axNum === '1') axNum = '';
55869 return name.charAt(0) + axNum;
55870};
55871
55872exports.cleanId = function cleanId(id, axLetter) {
55873 if(typeof id !== 'string' || !id.match(constants.AX_ID_PATTERN)) return;
55874 if(axLetter && id.charAt(0) !== axLetter) return;
55875
55876 var axNum = id.substr(1).replace(/^0+/, '');
55877 if(axNum === '1') axNum = '';
55878 return id.charAt(0) + axNum;
55879};
55880
55881// get all axis objects, as restricted in listNames
55882exports.list = function(gd, axLetter, only2d) {
55883 var fullLayout = gd._fullLayout;
55884 if(!fullLayout) return [];
55885
55886 var idList = exports.listIds(gd, axLetter);
55887 var out = new Array(idList.length);
55888 var i;
55889
55890 for(i = 0; i < idList.length; i++) {
55891 var idi = idList[i];
55892 out[i] = fullLayout[idi.charAt(0) + 'axis' + idi.substr(1)];
55893 }
55894
55895 if(!only2d) {
55896 var sceneIds3D = fullLayout._subplots.gl3d || [];
55897
55898 for(i = 0; i < sceneIds3D.length; i++) {
55899 var scene = fullLayout[sceneIds3D[i]];
55900
55901 if(axLetter) out.push(scene[axLetter + 'axis']);
55902 else out.push(scene.xaxis, scene.yaxis, scene.zaxis);
55903 }
55904 }
55905
55906 return out;
55907};
55908
55909// get all axis ids, optionally restricted by letter
55910// this only makes sense for 2d axes
55911exports.listIds = function(gd, axLetter) {
55912 var fullLayout = gd._fullLayout;
55913 if(!fullLayout) return [];
55914
55915 var subplotLists = fullLayout._subplots;
55916 if(axLetter) return subplotLists[axLetter + 'axis'];
55917 return subplotLists.xaxis.concat(subplotLists.yaxis);
55918};
55919
55920// get an axis object from its id 'x','x2' etc
55921// optionally, id can be a subplot (ie 'x2y3') and type gets x or y from it
55922exports.getFromId = function(gd, id, type) {
55923 var fullLayout = gd._fullLayout;
55924
55925 if(type === 'x') id = id.replace(/y[0-9]*/, '');
55926 else if(type === 'y') id = id.replace(/x[0-9]*/, '');
55927
55928 return fullLayout[exports.id2name(id)];
55929};
55930
55931// get an axis object of specified type from the containing trace
55932exports.getFromTrace = function(gd, fullTrace, type) {
55933 var fullLayout = gd._fullLayout;
55934 var ax = null;
55935
55936 if(Registry.traceIs(fullTrace, 'gl3d')) {
55937 var scene = fullTrace.scene;
55938 if(scene.substr(0, 5) === 'scene') {
55939 ax = fullLayout[scene][type + 'axis'];
55940 }
55941 } else {
55942 ax = exports.getFromId(gd, fullTrace[type + 'axis'] || type);
55943 }
55944
55945 return ax;
55946};
55947
55948// sort x, x2, x10, y, y2, y10...
55949exports.idSort = function(id1, id2) {
55950 var letter1 = id1.charAt(0);
55951 var letter2 = id2.charAt(0);
55952 if(letter1 !== letter2) return letter1 > letter2 ? 1 : -1;
55953 return +(id1.substr(1) || 1) - +(id2.substr(1) || 1);
55954};
55955
55956exports.getAxisGroup = function getAxisGroup(fullLayout, axId) {
55957 var matchGroups = fullLayout._axisMatchGroups;
55958
55959 for(var i = 0; i < matchGroups.length; i++) {
55960 var group = matchGroups[i];
55961 if(group[axId]) return 'g' + i;
55962 }
55963 return axId;
55964};
55965
55966},{"../../registry":269,"./constants":228}],226:[function(_dereq_,module,exports){
55967/**
55968* Copyright 2012-2020, Plotly, Inc.
55969* All rights reserved.
55970*
55971* This source code is licensed under the MIT license found in the
55972* LICENSE file in the root directory of this source tree.
55973*/
55974
55975'use strict';
55976
55977function findCategories(ax, opts) {
55978 var dataAttr = opts.dataAttr || ax._id.charAt(0);
55979 var lookup = {};
55980 var axData;
55981 var i, j;
55982
55983 if(opts.axData) {
55984 // non-x/y case
55985 axData = opts.axData;
55986 } else {
55987 // x/y case
55988 axData = [];
55989 for(i = 0; i < opts.data.length; i++) {
55990 var trace = opts.data[i];
55991 if(trace[dataAttr + 'axis'] === ax._id) {
55992 axData.push(trace);
55993 }
55994 }
55995 }
55996
55997 for(i = 0; i < axData.length; i++) {
55998 var vals = axData[i][dataAttr];
55999 for(j = 0; j < vals.length; j++) {
56000 var v = vals[j];
56001 if(v !== null && v !== undefined) {
56002 lookup[v] = 1;
56003 }
56004 }
56005 }
56006
56007 return Object.keys(lookup);
56008}
56009
56010/**
56011 * Fills in category* default and initial categories.
56012 *
56013 * @param {object} containerIn : input axis object
56014 * @param {object} containerOut : full axis object
56015 * @param {function} coerce : Lib.coerce fn wrapper
56016 * @param {object} opts :
56017 * - data {array} : (full) data trace
56018 * OR
56019 * - axData {array} : (full) data associated with axis being coerced here
56020 * - dataAttr {string} : attribute name corresponding to coordinate array
56021 */
56022module.exports = function handleCategoryOrderDefaults(containerIn, containerOut, coerce, opts) {
56023 if(containerOut.type !== 'category') return;
56024
56025 var arrayIn = containerIn.categoryarray;
56026 var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0);
56027
56028 // override default 'categoryorder' value when non-empty array is supplied
56029 var orderDefault;
56030 if(isValidArray) orderDefault = 'array';
56031
56032 var order = coerce('categoryorder', orderDefault);
56033 var array;
56034
56035 // coerce 'categoryarray' only in array order case
56036 if(order === 'array') {
56037 array = coerce('categoryarray');
56038 }
56039
56040 // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'
56041 if(!isValidArray && order === 'array') {
56042 order = containerOut.categoryorder = 'trace';
56043 }
56044
56045 // set up things for makeCalcdata
56046 if(order === 'trace') {
56047 containerOut._initialCategories = [];
56048 } else if(order === 'array') {
56049 containerOut._initialCategories = array.slice();
56050 } else {
56051 array = findCategories(containerOut, opts).sort();
56052 if(order === 'category ascending') {
56053 containerOut._initialCategories = array;
56054 } else if(order === 'category descending') {
56055 containerOut._initialCategories = array.reverse();
56056 }
56057 }
56058};
56059
56060},{}],227:[function(_dereq_,module,exports){
56061/**
56062* Copyright 2012-2020, Plotly, Inc.
56063* All rights reserved.
56064*
56065* This source code is licensed under the MIT license found in the
56066* LICENSE file in the root directory of this source tree.
56067*/
56068
56069'use strict';
56070
56071var isNumeric = _dereq_('fast-isnumeric');
56072var Lib = _dereq_('../../lib');
56073var ONEDAY = _dereq_('../../constants/numerical').ONEDAY;
56074
56075/**
56076 * Return a validated dtick value for this axis
56077 *
56078 * @param {any} dtick: the candidate dtick. valid values are numbers and strings,
56079 * and further constrained depending on the axis type.
56080 * @param {string} axType: the axis type
56081 */
56082exports.dtick = function(dtick, axType) {
56083 var isLog = axType === 'log';
56084 var isDate = axType === 'date';
56085 var isCat = axType === 'category';
56086 var dtickDflt = isDate ? ONEDAY : 1;
56087
56088 if(!dtick) return dtickDflt;
56089
56090 if(isNumeric(dtick)) {
56091 dtick = Number(dtick);
56092 if(dtick <= 0) return dtickDflt;
56093 if(isCat) {
56094 // category dtick must be positive integers
56095 return Math.max(1, Math.round(dtick));
56096 }
56097 if(isDate) {
56098 // date dtick must be at least 0.1ms (our current precision)
56099 return Math.max(0.1, dtick);
56100 }
56101 return dtick;
56102 }
56103
56104 if(typeof dtick !== 'string' || !(isDate || isLog)) {
56105 return dtickDflt;
56106 }
56107
56108 var prefix = dtick.charAt(0);
56109 var dtickNum = dtick.substr(1);
56110 dtickNum = isNumeric(dtickNum) ? Number(dtickNum) : 0;
56111
56112 if((dtickNum <= 0) || !(
56113 // "M<n>" gives ticks every (integer) n months
56114 (isDate && prefix === 'M' && dtickNum === Math.round(dtickNum)) ||
56115 // "L<f>" gives ticks linearly spaced in data (not in position) every (float) f
56116 (isLog && prefix === 'L') ||
56117 // "D1" gives powers of 10 with all small digits between, "D2" gives only 2 and 5
56118 (isLog && prefix === 'D' && (dtickNum === 1 || dtickNum === 2))
56119 )) {
56120 return dtickDflt;
56121 }
56122
56123 return dtick;
56124};
56125
56126/**
56127 * Return a validated tick0 for this axis
56128 *
56129 * @param {any} tick0: the candidate tick0. Valid values are numbers and strings,
56130 * further constrained depending on the axis type
56131 * @param {string} axType: the axis type
56132 * @param {string} calendar: for date axes, the calendar to validate/convert with
56133 * @param {any} dtick: an already valid dtick. Only used for D1 and D2 log dticks,
56134 * which do not support tick0 at all.
56135 */
56136exports.tick0 = function(tick0, axType, calendar, dtick) {
56137 if(axType === 'date') {
56138 return Lib.cleanDate(tick0, Lib.dateTick0(calendar));
56139 }
56140 if(dtick === 'D1' || dtick === 'D2') {
56141 // D1 and D2 modes ignore tick0 entirely
56142 return undefined;
56143 }
56144 // Aside from date axes, tick0 must be numeric
56145 return isNumeric(tick0) ? Number(tick0) : 0;
56146};
56147
56148},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],228:[function(_dereq_,module,exports){
56149/**
56150* Copyright 2012-2020, Plotly, Inc.
56151* All rights reserved.
56152*
56153* This source code is licensed under the MIT license found in the
56154* LICENSE file in the root directory of this source tree.
56155*/
56156
56157'use strict';
56158
56159var counterRegex = _dereq_('../../lib/regex').counter;
56160
56161module.exports = {
56162 idRegex: {
56163 x: counterRegex('x'),
56164 y: counterRegex('y')
56165 },
56166
56167 attrRegex: counterRegex('[xy]axis'),
56168
56169 // axis match regular expression
56170 xAxisMatch: counterRegex('xaxis'),
56171 yAxisMatch: counterRegex('yaxis'),
56172
56173 // pattern matching axis ids and names
56174 // note that this is more permissive than counterRegex, as
56175 // id2name, name2id, and cleanId accept "x1" etc
56176 AX_ID_PATTERN: /^[xyz][0-9]*$/,
56177 AX_NAME_PATTERN: /^[xyz]axis[0-9]*$/,
56178
56179 // and for 2D subplots
56180 SUBPLOT_PATTERN: /^x([0-9]*)y([0-9]*)$/,
56181
56182 HOUR_PATTERN: 'hour',
56183 WEEKDAY_PATTERN: 'day of week',
56184
56185 // pixels to move mouse before you stop clamping to starting point
56186 MINDRAG: 8,
56187
56188 // smallest dimension allowed for a select box
56189 MINSELECT: 12,
56190
56191 // smallest dimension allowed for a zoombox
56192 MINZOOM: 20,
56193
56194 // width of axis drag regions
56195 DRAGGERSIZE: 20,
56196
56197 // max pixels off straight before a lasso select line counts as bent
56198 BENDPX: 1.5,
56199
56200 // delay before a redraw (relayout) after smooth panning and zooming
56201 REDRAWDELAY: 50,
56202
56203 // throttling limit (ms) for selectPoints calls
56204 SELECTDELAY: 100,
56205
56206 // cache ID suffix for throttle
56207 SELECTID: '-select',
56208
56209 // last resort axis ranges for x and y axes if we have no data
56210 DFLTRANGEX: [-1, 6],
56211 DFLTRANGEY: [-1, 4],
56212
56213 // Layers to keep trace types in the right order
56214 // N.B. each 'unique' plot method must have its own layer
56215 traceLayerClasses: [
56216 'imagelayer',
56217 'heatmaplayer',
56218 'contourcarpetlayer', 'contourlayer',
56219 'funnellayer', 'waterfalllayer', 'barlayer',
56220 'carpetlayer',
56221 'violinlayer',
56222 'boxlayer',
56223 'ohlclayer',
56224 'scattercarpetlayer', 'scatterlayer'
56225 ],
56226
56227 clipOnAxisFalseQuery: [
56228 '.scatterlayer',
56229 '.barlayer',
56230 '.funnellayer',
56231 '.waterfalllayer'
56232 ],
56233
56234 layerValue2layerClass: {
56235 'above traces': 'above',
56236 'below traces': 'below'
56237 }
56238};
56239
56240},{"../../lib/regex":193}],229:[function(_dereq_,module,exports){
56241/**
56242* Copyright 2012-2020, Plotly, Inc.
56243* All rights reserved.
56244*
56245* This source code is licensed under the MIT license found in the
56246* LICENSE file in the root directory of this source tree.
56247*/
56248
56249'use strict';
56250
56251var Lib = _dereq_('../../lib');
56252var id2name = _dereq_('./axis_ids').id2name;
56253var scaleZoom = _dereq_('./scale_zoom');
56254var makePadFn = _dereq_('./autorange').makePadFn;
56255var concatExtremes = _dereq_('./autorange').concatExtremes;
56256
56257var ALMOST_EQUAL = _dereq_('../../constants/numerical').ALMOST_EQUAL;
56258var FROM_BL = _dereq_('../../constants/alignment').FROM_BL;
56259
56260exports.handleConstraintDefaults = function(containerIn, containerOut, coerce, opts) {
56261 var allAxisIds = opts.allAxisIds;
56262 var layoutOut = opts.layoutOut;
56263 var scaleanchorDflt = opts.scaleanchorDflt;
56264 var constrainDflt = opts.constrainDflt;
56265 var constraintGroups = layoutOut._axisConstraintGroups;
56266 var matchGroups = layoutOut._axisMatchGroups;
56267 var axId = containerOut._id;
56268 var axLetter = axId.charAt(0);
56269 var splomStash = ((layoutOut._splomAxes || {})[axLetter] || {})[axId] || {};
56270 var thisID = containerOut._id;
56271 var letter = thisID.charAt(0);
56272
56273 // coerce the constraint mechanics even if this axis has no scaleanchor
56274 // because it may be the anchor of another axis.
56275 var constrain = coerce('constrain', constrainDflt);
56276 Lib.coerce(containerIn, containerOut, {
56277 constraintoward: {
56278 valType: 'enumerated',
56279 values: letter === 'x' ? ['left', 'center', 'right'] : ['bottom', 'middle', 'top'],
56280 dflt: letter === 'x' ? 'center' : 'middle'
56281 }
56282 }, 'constraintoward');
56283
56284 var matches, matchOpts;
56285
56286 if((containerIn.matches || splomStash.matches) && !containerOut.fixedrange) {
56287 matchOpts = getConstraintOpts(matchGroups, thisID, allAxisIds, layoutOut);
56288 matches = Lib.coerce(containerIn, containerOut, {
56289 matches: {
56290 valType: 'enumerated',
56291 values: matchOpts.linkableAxes || [],
56292 dflt: splomStash.matches
56293 }
56294 }, 'matches');
56295 }
56296
56297 // 'matches' wins over 'scaleanchor' (for now)
56298 var scaleanchor, scaleOpts;
56299
56300 if(!matches &&
56301 !(containerOut.fixedrange && constrain !== 'domain') &&
56302 (containerIn.scaleanchor || scaleanchorDflt)
56303 ) {
56304 scaleOpts = getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut, constrain);
56305 scaleanchor = Lib.coerce(containerIn, containerOut, {
56306 scaleanchor: {
56307 valType: 'enumerated',
56308 values: scaleOpts.linkableAxes || []
56309 }
56310 }, 'scaleanchor', scaleanchorDflt);
56311 }
56312
56313 if(matches) {
56314 delete containerOut.constrain;
56315 updateConstraintGroups(matchGroups, matchOpts.thisGroup, thisID, matches, 1);
56316 } else if(allAxisIds.indexOf(containerIn.matches) !== -1) {
56317 Lib.warn('ignored ' + containerOut._name + '.matches: "' +
56318 containerIn.matches + '" to avoid either an infinite loop ' +
56319 'or because the target axis has fixed range.');
56320 }
56321
56322 if(scaleanchor) {
56323 var scaleratio = coerce('scaleratio');
56324
56325 // TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero,
56326 // but that seems hacky. Better way to say "must be a positive number"?
56327 // Of course if you use several super-tiny values you could eventually
56328 // force a product of these to zero and all hell would break loose...
56329 // Likewise with super-huge values.
56330 if(!scaleratio) scaleratio = containerOut.scaleratio = 1;
56331
56332 updateConstraintGroups(constraintGroups, scaleOpts.thisGroup, thisID, scaleanchor, scaleratio);
56333 } else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
56334 Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
56335 containerIn.scaleanchor + '" to avoid either an infinite loop ' +
56336 'and possibly inconsistent scaleratios, or because the target ' +
56337 'axis has fixed range or this axis declares a *matches* constraint.');
56338 }
56339};
56340
56341// If this axis is already part of a constraint group, we can't
56342// scaleanchor any other axis in that group, or we'd make a loop.
56343// Filter allAxisIds to enforce this, also matching axis types.
56344function getConstraintOpts(groups, thisID, allAxisIds, layoutOut, constrain) {
56345 var doesNotConstrainRange = constrain !== 'range';
56346 var thisType = layoutOut[id2name(thisID)].type;
56347 var i, j, idj, axj;
56348
56349 var linkableAxes = [];
56350 for(j = 0; j < allAxisIds.length; j++) {
56351 idj = allAxisIds[j];
56352 if(idj === thisID) continue;
56353
56354 axj = layoutOut[id2name(idj)];
56355 if(axj.type === thisType) {
56356 if(!axj.fixedrange) {
56357 linkableAxes.push(idj);
56358 } else if(doesNotConstrainRange && axj.anchor) {
56359 // allow domain constraints on subplots where
56360 // BOTH axes have fixedrange:true and constrain:domain
56361 var counterAxj = layoutOut[id2name(axj.anchor)];
56362 if(counterAxj.fixedrange) {
56363 linkableAxes.push(idj);
56364 }
56365 }
56366 }
56367 }
56368
56369 for(i = 0; i < groups.length; i++) {
56370 if(groups[i][thisID]) {
56371 var thisGroup = groups[i];
56372
56373 var linkableAxesNoLoops = [];
56374 for(j = 0; j < linkableAxes.length; j++) {
56375 idj = linkableAxes[j];
56376 if(!thisGroup[idj]) linkableAxesNoLoops.push(idj);
56377 }
56378 return {linkableAxes: linkableAxesNoLoops, thisGroup: thisGroup};
56379 }
56380 }
56381
56382 return {linkableAxes: linkableAxes, thisGroup: null};
56383}
56384
56385/*
56386 * Add this axis to the axis constraint groups, which is the collection
56387 * of axes that are all constrained together on scale.
56388 *
56389 * constraintGroups: a list of objects. each object is
56390 * {axis_id: scale_within_group}, where scale_within_group is
56391 * only important relative to the rest of the group, and defines
56392 * the relative scales between all axes in the group
56393 *
56394 * thisGroup: the group the current axis is already in
56395 * thisID: the id if the current axis
56396 * scaleanchor: the id of the axis to scale it with
56397 * scaleratio: the ratio of this axis to the scaleanchor axis
56398 */
56399function updateConstraintGroups(constraintGroups, thisGroup, thisID, scaleanchor, scaleratio) {
56400 var i, j, groupi, keyj, thisGroupIndex;
56401
56402 if(thisGroup === null) {
56403 thisGroup = {};
56404 thisGroup[thisID] = 1;
56405 thisGroupIndex = constraintGroups.length;
56406 constraintGroups.push(thisGroup);
56407 } else {
56408 thisGroupIndex = constraintGroups.indexOf(thisGroup);
56409 }
56410
56411 var thisGroupKeys = Object.keys(thisGroup);
56412
56413 // we know that this axis isn't in any other groups, but we don't know
56414 // about the scaleanchor axis. If it is, we need to merge the groups.
56415 for(i = 0; i < constraintGroups.length; i++) {
56416 groupi = constraintGroups[i];
56417 if(i !== thisGroupIndex && groupi[scaleanchor]) {
56418 var baseScale = groupi[scaleanchor];
56419 for(j = 0; j < thisGroupKeys.length; j++) {
56420 keyj = thisGroupKeys[j];
56421 groupi[keyj] = baseScale * scaleratio * thisGroup[keyj];
56422 }
56423 constraintGroups.splice(thisGroupIndex, 1);
56424 return;
56425 }
56426 }
56427
56428 // otherwise, we insert the new scaleanchor axis as the base scale (1)
56429 // in its group, and scale the rest of the group to it
56430 if(scaleratio !== 1) {
56431 for(j = 0; j < thisGroupKeys.length; j++) {
56432 thisGroup[thisGroupKeys[j]] *= scaleratio;
56433 }
56434 }
56435 thisGroup[scaleanchor] = 1;
56436}
56437
56438exports.enforce = function enforce(gd) {
56439 var fullLayout = gd._fullLayout;
56440 var constraintGroups = fullLayout._axisConstraintGroups || [];
56441
56442 var i, j, axisID, ax, normScale, mode, factor;
56443
56444 for(i = 0; i < constraintGroups.length; i++) {
56445 var group = constraintGroups[i];
56446 var axisIDs = Object.keys(group);
56447
56448 var minScale = Infinity;
56449 var maxScale = 0;
56450 // mostly matchScale will be the same as minScale
56451 // ie we expand axis ranges to encompass *everything*
56452 // that's currently in any of their ranges, but during
56453 // autorange of a subset of axes we will ignore other
56454 // axes for this purpose.
56455 var matchScale = Infinity;
56456 var normScales = {};
56457 var axes = {};
56458 var hasAnyDomainConstraint = false;
56459
56460 // find the (normalized) scale of each axis in the group
56461 for(j = 0; j < axisIDs.length; j++) {
56462 axisID = axisIDs[j];
56463 axes[axisID] = ax = fullLayout[id2name(axisID)];
56464
56465 if(ax._inputDomain) ax.domain = ax._inputDomain.slice();
56466 else ax._inputDomain = ax.domain.slice();
56467
56468 if(!ax._inputRange) ax._inputRange = ax.range.slice();
56469
56470 // set axis scale here so we can use _m rather than
56471 // having to calculate it from length and range
56472 ax.setScale();
56473
56474 // abs: inverted scales still satisfy the constraint
56475 normScales[axisID] = normScale = Math.abs(ax._m) / group[axisID];
56476 minScale = Math.min(minScale, normScale);
56477 if(ax.constrain === 'domain' || !ax._constraintShrinkable) {
56478 matchScale = Math.min(matchScale, normScale);
56479 }
56480
56481 // this has served its purpose, so remove it
56482 delete ax._constraintShrinkable;
56483 maxScale = Math.max(maxScale, normScale);
56484
56485 if(ax.constrain === 'domain') hasAnyDomainConstraint = true;
56486 }
56487
56488 // Do we have a constraint mismatch? Give a small buffer for rounding errors
56489 if(minScale > ALMOST_EQUAL * maxScale && !hasAnyDomainConstraint) continue;
56490
56491 // now increase any ranges we need to until all normalized scales are equal
56492 for(j = 0; j < axisIDs.length; j++) {
56493 axisID = axisIDs[j];
56494 normScale = normScales[axisID];
56495 ax = axes[axisID];
56496 mode = ax.constrain;
56497
56498 // even if the scale didn't change, if we're shrinking domain
56499 // we need to recalculate in case `constraintoward` changed
56500 if(normScale !== matchScale || mode === 'domain') {
56501 factor = normScale / matchScale;
56502
56503 if(mode === 'range') {
56504 scaleZoom(ax, factor);
56505 } else {
56506 // mode === 'domain'
56507
56508 var inputDomain = ax._inputDomain;
56509 var domainShrunk = (ax.domain[1] - ax.domain[0]) /
56510 (inputDomain[1] - inputDomain[0]);
56511 var rangeShrunk = (ax.r2l(ax.range[1]) - ax.r2l(ax.range[0])) /
56512 (ax.r2l(ax._inputRange[1]) - ax.r2l(ax._inputRange[0]));
56513
56514 factor /= domainShrunk;
56515
56516 if(factor * rangeShrunk < 1) {
56517 // we've asked to magnify the axis more than we can just by
56518 // enlarging the domain - so we need to constrict range
56519 ax.domain = ax._input.domain = inputDomain.slice();
56520 scaleZoom(ax, factor);
56521 continue;
56522 }
56523
56524 if(rangeShrunk < 1) {
56525 // the range has previously been constricted by ^^, but we've
56526 // switched to the domain-constricted regime, so reset range
56527 ax.range = ax._input.range = ax._inputRange.slice();
56528 factor *= rangeShrunk;
56529 }
56530
56531 if(ax.autorange) {
56532 /*
56533 * range & factor may need to change because range was
56534 * calculated for the larger scaling, so some pixel
56535 * paddings may get cut off when we reduce the domain.
56536 *
56537 * This is easier than the regular autorange calculation
56538 * because we already know the scaling `m`, but we still
56539 * need to cut out impossible constraints (like
56540 * annotations with super-long arrows). That's what
56541 * outerMin/Max are for - if the expansion was going to
56542 * go beyond the original domain, it must be impossible
56543 */
56544 var rl0 = ax.r2l(ax.range[0]);
56545 var rl1 = ax.r2l(ax.range[1]);
56546 var rangeCenter = (rl0 + rl1) / 2;
56547 var rangeMin = rangeCenter;
56548 var rangeMax = rangeCenter;
56549 var halfRange = Math.abs(rl1 - rangeCenter);
56550 // extra tiny bit for rounding errors, in case we actually
56551 // *are* expanding to the full domain
56552 var outerMin = rangeCenter - halfRange * factor * 1.0001;
56553 var outerMax = rangeCenter + halfRange * factor * 1.0001;
56554 var getPad = makePadFn(ax);
56555
56556 updateDomain(ax, factor);
56557 var m = Math.abs(ax._m);
56558 var extremes = concatExtremes(gd, ax);
56559 var minArray = extremes.min;
56560 var maxArray = extremes.max;
56561 var newVal;
56562 var k;
56563
56564 for(k = 0; k < minArray.length; k++) {
56565 newVal = minArray[k].val - getPad(minArray[k]) / m;
56566 if(newVal > outerMin && newVal < rangeMin) {
56567 rangeMin = newVal;
56568 }
56569 }
56570
56571 for(k = 0; k < maxArray.length; k++) {
56572 newVal = maxArray[k].val + getPad(maxArray[k]) / m;
56573 if(newVal < outerMax && newVal > rangeMax) {
56574 rangeMax = newVal;
56575 }
56576 }
56577
56578 var domainExpand = (rangeMax - rangeMin) / (2 * halfRange);
56579 factor /= domainExpand;
56580
56581 rangeMin = ax.l2r(rangeMin);
56582 rangeMax = ax.l2r(rangeMax);
56583 ax.range = ax._input.range = (rl0 < rl1) ?
56584 [rangeMin, rangeMax] : [rangeMax, rangeMin];
56585 }
56586
56587 updateDomain(ax, factor);
56588 }
56589 }
56590 }
56591 }
56592};
56593
56594// For use before autoranging, check if this axis was previously constrained
56595// by domain but no longer is
56596exports.clean = function clean(gd, ax) {
56597 if(ax._inputDomain) {
56598 var isConstrained = false;
56599 var axId = ax._id;
56600 var constraintGroups = gd._fullLayout._axisConstraintGroups;
56601 for(var j = 0; j < constraintGroups.length; j++) {
56602 if(constraintGroups[j][axId]) {
56603 isConstrained = true;
56604 break;
56605 }
56606 }
56607 if(!isConstrained || ax.constrain !== 'domain') {
56608 ax._input.domain = ax.domain = ax._inputDomain;
56609 delete ax._inputDomain;
56610 }
56611 }
56612};
56613
56614function updateDomain(ax, factor) {
56615 var inputDomain = ax._inputDomain;
56616 var centerFraction = FROM_BL[ax.constraintoward];
56617 var center = inputDomain[0] + (inputDomain[1] - inputDomain[0]) * centerFraction;
56618
56619 ax.domain = ax._input.domain = [
56620 center + (inputDomain[0] - center) / factor,
56621 center + (inputDomain[1] - center) / factor
56622 ];
56623 ax.setScale();
56624}
56625
56626},{"../../constants/alignment":154,"../../constants/numerical":158,"../../lib":178,"./autorange":221,"./axis_ids":225,"./scale_zoom":240}],230:[function(_dereq_,module,exports){
56627/**
56628* Copyright 2012-2020, Plotly, Inc.
56629* All rights reserved.
56630*
56631* This source code is licensed under the MIT license found in the
56632* LICENSE file in the root directory of this source tree.
56633*/
56634
56635'use strict';
56636
56637var d3 = _dereq_('d3');
56638var tinycolor = _dereq_('tinycolor2');
56639var supportsPassive = _dereq_('has-passive-events');
56640
56641var Registry = _dereq_('../../registry');
56642var Lib = _dereq_('../../lib');
56643var svgTextUtils = _dereq_('../../lib/svg_text_utils');
56644var Color = _dereq_('../../components/color');
56645var Drawing = _dereq_('../../components/drawing');
56646var Fx = _dereq_('../../components/fx');
56647var Axes = _dereq_('./axes');
56648var setCursor = _dereq_('../../lib/setcursor');
56649var dragElement = _dereq_('../../components/dragelement');
56650var helpers = _dereq_('../../components/dragelement/helpers');
56651var selectingOrDrawing = helpers.selectingOrDrawing;
56652var freeMode = helpers.freeMode;
56653
56654var FROM_TL = _dereq_('../../constants/alignment').FROM_TL;
56655var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases');
56656var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces;
56657
56658var Plots = _dereq_('../plots');
56659
56660var getFromId = _dereq_('./axis_ids').getFromId;
56661var prepSelect = _dereq_('./select').prepSelect;
56662var clearSelect = _dereq_('./select').clearSelect;
56663var selectOnClick = _dereq_('./select').selectOnClick;
56664var scaleZoom = _dereq_('./scale_zoom');
56665
56666var constants = _dereq_('./constants');
56667var MINDRAG = constants.MINDRAG;
56668var MINZOOM = constants.MINZOOM;
56669
56670// flag for showing "doubleclick to zoom out" only at the beginning
56671var SHOWZOOMOUTTIP = true;
56672
56673// dragBox: create an element to drag one or more axis ends
56674// inputs:
56675// plotinfo - which subplot are we making dragboxes on?
56676// x,y,w,h - left, top, width, height of the box
56677// ns - how does this drag the vertical axis?
56678// 'n' - top only
56679// 's' - bottom only
56680// 'ns' - top and bottom together, difference unchanged
56681// ew - same for horizontal axis
56682function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
56683 // mouseDown stores ms of first mousedown event in the last
56684 // `gd._context.doubleClickDelay` ms on the drag bars
56685 // numClicks stores how many mousedowns have been seen
56686 // within `gd._context.doubleClickDelay` so we can check for click or doubleclick events
56687 // dragged stores whether a drag has occurred, so we don't have to
56688 // redraw unnecessarily, ie if no move bigger than MINDRAG or MINZOOM px
56689 var zoomlayer = gd._fullLayout._zoomlayer;
56690 var isMainDrag = (ns + ew === 'nsew');
56691 var singleEnd = (ns + ew).length === 1;
56692
56693 // main subplot x and y (i.e. found in plotinfo - the main ones)
56694 var xa0, ya0;
56695 // {ax._id: ax} hash objects
56696 var xaHash, yaHash;
56697 // xaHash/yaHash values (arrays)
56698 var xaxes, yaxes;
56699 // main axis offsets
56700 var xs, ys;
56701 // main axis lengths
56702 var pw, ph;
56703 // contains keys 'xaHash', 'yaHash', 'xaxes', and 'yaxes'
56704 // which are the x/y {ax._id: ax} hash objects and their values
56705 // for linked axis relative to this subplot
56706 var links;
56707 // similar to `links` but for matching axes
56708 var matches;
56709 // set to ew/ns val when active, set to '' when inactive
56710 var xActive, yActive;
56711 // are all axes in this subplot are fixed?
56712 var allFixedRanges;
56713 // do we need to edit x/y ranges?
56714 var editX, editY;
56715 // graph-wide optimization flags
56716 var hasScatterGl, hasSplom, hasSVG;
56717 // collected changes to be made to the plot by relayout at the end
56718 var updates;
56719
56720 function recomputeAxisLists() {
56721 xa0 = plotinfo.xaxis;
56722 ya0 = plotinfo.yaxis;
56723 pw = xa0._length;
56724 ph = ya0._length;
56725 xs = xa0._offset;
56726 ys = ya0._offset;
56727
56728 xaHash = {};
56729 xaHash[xa0._id] = xa0;
56730 yaHash = {};
56731 yaHash[ya0._id] = ya0;
56732
56733 // if we're dragging two axes at once, also drag overlays
56734 if(ns && ew) {
56735 var overlays = plotinfo.overlays;
56736 for(var i = 0; i < overlays.length; i++) {
56737 var xa = overlays[i].xaxis;
56738 xaHash[xa._id] = xa;
56739 var ya = overlays[i].yaxis;
56740 yaHash[ya._id] = ya;
56741 }
56742 }
56743
56744 xaxes = hashValues(xaHash);
56745 yaxes = hashValues(yaHash);
56746 xActive = isDirectionActive(xaxes, ew);
56747 yActive = isDirectionActive(yaxes, ns);
56748 allFixedRanges = !yActive && !xActive;
56749
56750 links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash);
56751 matches = calcLinks(gd, gd._fullLayout._axisMatchGroups, xaHash, yaHash);
56752 editX = ew || links.isSubplotConstrained || matches.isSubplotConstrained;
56753 editY = ns || links.isSubplotConstrained || matches.isSubplotConstrained;
56754
56755 var fullLayout = gd._fullLayout;
56756 hasScatterGl = fullLayout._has('scattergl');
56757 hasSplom = fullLayout._has('splom');
56758 hasSVG = fullLayout._has('svg');
56759 }
56760
56761 recomputeAxisLists();
56762
56763 var cursor = getDragCursor(yActive + xActive, gd._fullLayout.dragmode, isMainDrag);
56764 var dragger = makeRectDragger(plotinfo, ns + ew + 'drag', cursor, x, y, w, h);
56765
56766 // still need to make the element if the axes are disabled
56767 // but nuke its events (except for maindrag which needs them for hover)
56768 // and stop there
56769 if(allFixedRanges && !isMainDrag) {
56770 dragger.onmousedown = null;
56771 dragger.style.pointerEvents = 'none';
56772 return dragger;
56773 }
56774
56775 var dragOptions = {
56776 element: dragger,
56777 gd: gd,
56778 plotinfo: plotinfo
56779 };
56780
56781 dragOptions.prepFn = function(e, startX, startY) {
56782 var dragModePrev = dragOptions.dragmode;
56783 var dragModeNow = gd._fullLayout.dragmode;
56784 if(dragModeNow !== dragModePrev) {
56785 dragOptions.dragmode = dragModeNow;
56786 }
56787
56788 recomputeAxisLists();
56789
56790 if(!allFixedRanges) {
56791 if(isMainDrag) {
56792 // main dragger handles all drag modes, and changes
56793 // to pan (or to zoom if it already is pan) on shift
56794 if(e.shiftKey) {
56795 if(dragModeNow === 'pan') dragModeNow = 'zoom';
56796 else if(!selectingOrDrawing(dragModeNow)) dragModeNow = 'pan';
56797 } else if(e.ctrlKey) {
56798 dragModeNow = 'pan';
56799 }
56800 } else {
56801 // all other draggers just pan
56802 dragModeNow = 'pan';
56803 }
56804 }
56805
56806 if(freeMode(dragModeNow)) dragOptions.minDrag = 1;
56807 else dragOptions.minDrag = undefined;
56808
56809 if(selectingOrDrawing(dragModeNow)) {
56810 dragOptions.xaxes = xaxes;
56811 dragOptions.yaxes = yaxes;
56812 // this attaches moveFn, clickFn, doneFn on dragOptions
56813 prepSelect(e, startX, startY, dragOptions, dragModeNow);
56814 } else {
56815 dragOptions.clickFn = clickFn;
56816 if(selectingOrDrawing(dragModePrev)) {
56817 // TODO Fix potential bug
56818 // Note: clearing / resetting selection state only happens, when user
56819 // triggers at least one interaction in pan/zoom mode. Otherwise, the
56820 // select/lasso outlines are deleted (in plots.js.cleanPlot) but the selection
56821 // cache isn't cleared. So when the user switches back to select/lasso and
56822 // 'adds to a selection' with Shift, the "old", seemingly removed outlines
56823 // are redrawn again because the selection cache still holds their coordinates.
56824 // However, this isn't easily solved, since plots.js would need
56825 // to have a reference to the dragOptions object (which holds the
56826 // selection cache).
56827 clearAndResetSelect();
56828 }
56829
56830 if(!allFixedRanges) {
56831 if(dragModeNow === 'zoom') {
56832 dragOptions.moveFn = zoomMove;
56833 dragOptions.doneFn = zoomDone;
56834
56835 // zoomMove takes care of the threshold, but we need to
56836 // minimize this so that constrained zoom boxes will flip
56837 // orientation at the right place
56838 dragOptions.minDrag = 1;
56839
56840 zoomPrep(e, startX, startY);
56841 } else if(dragModeNow === 'pan') {
56842 dragOptions.moveFn = plotDrag;
56843 dragOptions.doneFn = dragTail;
56844 }
56845 }
56846 }
56847
56848 gd._fullLayout._redrag = function() {
56849 var dragDataNow = gd._dragdata;
56850
56851 if(dragDataNow && dragDataNow.element === dragger) {
56852 var dragModeNow = gd._fullLayout.dragmode;
56853
56854 if(!selectingOrDrawing(dragModeNow)) {
56855 recomputeAxisLists();
56856 updateSubplots([0, 0, pw, ph]);
56857 dragOptions.moveFn(dragDataNow.dx, dragDataNow.dy);
56858 }
56859
56860 // TODO should we try to "re-select" under select/lasso modes?
56861 // probably best to wait for https://github.com/plotly/plotly.js/issues/1851
56862 }
56863 };
56864 };
56865
56866 function clearAndResetSelect() {
56867 // clear selection polygon cache (if any)
56868 dragOptions.plotinfo.selection = false;
56869 // clear selection outlines
56870 clearSelect(gd);
56871 }
56872
56873 function clickFn(numClicks, evt) {
56874 var gd = dragOptions.gd;
56875 if(gd._fullLayout._activeShapeIndex >= 0) {
56876 gd._fullLayout._deactivateShape(gd);
56877 return;
56878 }
56879
56880 var clickmode = gd._fullLayout.clickmode;
56881
56882 removeZoombox(gd);
56883
56884 if(numClicks === 2 && !singleEnd) doubleClick();
56885
56886 if(isMainDrag) {
56887 if(clickmode.indexOf('select') > -1) {
56888 selectOnClick(evt, gd, xaxes, yaxes, plotinfo.id, dragOptions);
56889 }
56890
56891 if(clickmode.indexOf('event') > -1) {
56892 Fx.click(gd, evt, plotinfo.id);
56893 }
56894 } else if(numClicks === 1 && singleEnd) {
56895 var ax = ns ? ya0 : xa0;
56896 var end = (ns === 's' || ew === 'w') ? 0 : 1;
56897 var attrStr = ax._name + '.range[' + end + ']';
56898 var initialText = getEndText(ax, end);
56899 var hAlign = 'left';
56900 var vAlign = 'middle';
56901
56902 if(ax.fixedrange) return;
56903
56904 if(ns) {
56905 vAlign = (ns === 'n') ? 'top' : 'bottom';
56906 if(ax.side === 'right') hAlign = 'right';
56907 } else if(ew === 'e') hAlign = 'right';
56908
56909 if(gd._context.showAxisRangeEntryBoxes) {
56910 d3.select(dragger)
56911 .call(svgTextUtils.makeEditable, {
56912 gd: gd,
56913 immediate: true,
56914 background: gd._fullLayout.paper_bgcolor,
56915 text: String(initialText),
56916 fill: ax.tickfont ? ax.tickfont.color : '#444',
56917 horizontalAlign: hAlign,
56918 verticalAlign: vAlign
56919 })
56920 .on('edit', function(text) {
56921 var v = ax.d2r(text);
56922 if(v !== undefined) {
56923 Registry.call('_guiRelayout', gd, attrStr, v);
56924 }
56925 });
56926 }
56927 }
56928 }
56929
56930 dragElement.init(dragOptions);
56931
56932 // x/y px position at start of drag
56933 var x0, y0;
56934 // bbox object of the zoombox
56935 var box;
56936 // luminance of bg behind zoombox
56937 var lum;
56938 // zoombox path outline
56939 var path0;
56940 // is zoombox dimmed (during drag)
56941 var dimmed;
56942 // 'x'-only, 'y' or 'xy' zooming
56943 var zoomMode;
56944 // zoombox d3 selection
56945 var zb;
56946 // zoombox corner d3 selection
56947 var corners;
56948 // zoom takes over minDrag, so it also has to take over gd._dragged
56949 var zoomDragged;
56950
56951 function zoomPrep(e, startX, startY) {
56952 var dragBBox = dragger.getBoundingClientRect();
56953 x0 = startX - dragBBox.left;
56954 y0 = startY - dragBBox.top;
56955 box = {l: x0, r: x0, w: 0, t: y0, b: y0, h: 0};
56956 lum = gd._hmpixcount ?
56957 (gd._hmlumcount / gd._hmpixcount) :
56958 tinycolor(gd._fullLayout.plot_bgcolor).getLuminance();
56959 path0 = 'M0,0H' + pw + 'V' + ph + 'H0V0';
56960 dimmed = false;
56961 zoomMode = 'xy';
56962 zoomDragged = false;
56963 zb = makeZoombox(zoomlayer, lum, xs, ys, path0);
56964 corners = makeCorners(zoomlayer, xs, ys);
56965 }
56966
56967 function zoomMove(dx0, dy0) {
56968 if(gd._transitioningWithDuration) {
56969 return false;
56970 }
56971
56972 var x1 = Math.max(0, Math.min(pw, dx0 + x0));
56973 var y1 = Math.max(0, Math.min(ph, dy0 + y0));
56974 var dx = Math.abs(x1 - x0);
56975 var dy = Math.abs(y1 - y0);
56976
56977 box.l = Math.min(x0, x1);
56978 box.r = Math.max(x0, x1);
56979 box.t = Math.min(y0, y1);
56980 box.b = Math.max(y0, y1);
56981
56982 function noZoom() {
56983 zoomMode = '';
56984 box.r = box.l;
56985 box.t = box.b;
56986 corners.attr('d', 'M0,0Z');
56987 }
56988
56989 if(links.isSubplotConstrained) {
56990 if(dx > MINZOOM || dy > MINZOOM) {
56991 zoomMode = 'xy';
56992 if(dx / pw > dy / ph) {
56993 dy = dx * ph / pw;
56994 if(y0 > y1) box.t = y0 - dy;
56995 else box.b = y0 + dy;
56996 } else {
56997 dx = dy * pw / ph;
56998 if(x0 > x1) box.l = x0 - dx;
56999 else box.r = x0 + dx;
57000 }
57001 corners.attr('d', xyCorners(box));
57002 } else {
57003 noZoom();
57004 }
57005 } else if(matches.isSubplotConstrained) {
57006 if(dx > MINZOOM || dy > MINZOOM) {
57007 zoomMode = 'xy';
57008
57009 var r0 = Math.min(box.l / pw, (ph - box.b) / ph);
57010 var r1 = Math.max(box.r / pw, (ph - box.t) / ph);
57011
57012 box.l = r0 * pw;
57013 box.r = r1 * pw;
57014 box.b = (1 - r0) * ph;
57015 box.t = (1 - r1) * ph;
57016 corners.attr('d', xyCorners(box));
57017 } else {
57018 noZoom();
57019 }
57020 } else if(!yActive || dy < Math.min(Math.max(dx * 0.6, MINDRAG), MINZOOM)) {
57021 // look for small drags in one direction or the other,
57022 // and only drag the other axis
57023
57024 if(dx < MINDRAG || !xActive) {
57025 noZoom();
57026 } else {
57027 box.t = 0;
57028 box.b = ph;
57029 zoomMode = 'x';
57030 corners.attr('d', xCorners(box, y0));
57031 }
57032 } else if(!xActive || dx < Math.min(dy * 0.6, MINZOOM)) {
57033 box.l = 0;
57034 box.r = pw;
57035 zoomMode = 'y';
57036 corners.attr('d', yCorners(box, x0));
57037 } else {
57038 zoomMode = 'xy';
57039 corners.attr('d', xyCorners(box));
57040 }
57041 box.w = box.r - box.l;
57042 box.h = box.b - box.t;
57043
57044 if(zoomMode) zoomDragged = true;
57045 gd._dragged = zoomDragged;
57046
57047 updateZoombox(zb, corners, box, path0, dimmed, lum);
57048 computeZoomUpdates();
57049 gd.emit('plotly_relayouting', updates);
57050 dimmed = true;
57051 }
57052
57053 function computeZoomUpdates() {
57054 updates = {};
57055
57056 // TODO: edit linked axes in zoomAxRanges and in dragTail
57057 if(zoomMode === 'xy' || zoomMode === 'x') {
57058 zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes);
57059 updateMatchedAxRange('x', updates);
57060 }
57061 if(zoomMode === 'xy' || zoomMode === 'y') {
57062 zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes);
57063 updateMatchedAxRange('y', updates);
57064 }
57065 }
57066
57067 function zoomDone() {
57068 computeZoomUpdates();
57069 removeZoombox(gd);
57070 dragTail();
57071 showDoubleClickNotifier(gd);
57072 }
57073
57074 // scroll zoom, on all draggers except corners
57075 var scrollViewBox = [0, 0, pw, ph];
57076 // wait a little after scrolling before redrawing
57077 var redrawTimer = null;
57078 var REDRAWDELAY = constants.REDRAWDELAY;
57079 var mainplot = plotinfo.mainplot ? gd._fullLayout._plots[plotinfo.mainplot] : plotinfo;
57080
57081 function zoomWheel(e) {
57082 // deactivate mousewheel scrolling on embedded graphs
57083 // devs can override this with layout._enablescrollzoom,
57084 // but _ ensures this setting won't leave their page
57085 if(!gd._context._scrollZoom.cartesian && !gd._fullLayout._enablescrollzoom) {
57086 return;
57087 }
57088
57089 clearAndResetSelect();
57090
57091 // If a transition is in progress, then disable any behavior:
57092 if(gd._transitioningWithDuration) {
57093 e.preventDefault();
57094 e.stopPropagation();
57095 return;
57096 }
57097
57098 recomputeAxisLists();
57099
57100 clearTimeout(redrawTimer);
57101
57102 var wheelDelta = -e.deltaY;
57103 if(!isFinite(wheelDelta)) wheelDelta = e.wheelDelta / 10;
57104 if(!isFinite(wheelDelta)) {
57105 Lib.log('Did not find wheel motion attributes: ', e);
57106 return;
57107 }
57108
57109 var zoom = Math.exp(-Math.min(Math.max(wheelDelta, -20), 20) / 200);
57110 var gbb = mainplot.draglayer.select('.nsewdrag').node().getBoundingClientRect();
57111 var xfrac = (e.clientX - gbb.left) / gbb.width;
57112 var yfrac = (gbb.bottom - e.clientY) / gbb.height;
57113 var i;
57114
57115 function zoomWheelOneAxis(ax, centerFraction, zoom) {
57116 if(ax.fixedrange) return;
57117
57118 var axRange = Lib.simpleMap(ax.range, ax.r2l);
57119 var v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction;
57120 function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); }
57121 ax.range = axRange.map(doZoom);
57122 }
57123
57124 if(editX) {
57125 // if we're only zooming this axis because of constraints,
57126 // zoom it about the center
57127 if(!ew) xfrac = 0.5;
57128
57129 for(i = 0; i < xaxes.length; i++) {
57130 zoomWheelOneAxis(xaxes[i], xfrac, zoom);
57131 }
57132 updateMatchedAxRange('x');
57133
57134 scrollViewBox[2] *= zoom;
57135 scrollViewBox[0] += scrollViewBox[2] * xfrac * (1 / zoom - 1);
57136 }
57137 if(editY) {
57138 if(!ns) yfrac = 0.5;
57139
57140 for(i = 0; i < yaxes.length; i++) {
57141 zoomWheelOneAxis(yaxes[i], yfrac, zoom);
57142 }
57143 updateMatchedAxRange('y');
57144
57145 scrollViewBox[3] *= zoom;
57146 scrollViewBox[1] += scrollViewBox[3] * (1 - yfrac) * (1 / zoom - 1);
57147 }
57148
57149 // viewbox redraw at first
57150 updateSubplots(scrollViewBox);
57151 ticksAndAnnotations();
57152
57153 gd.emit('plotly_relayouting', updates);
57154
57155 // then replot after a delay to make sure
57156 // no more scrolling is coming
57157 redrawTimer = setTimeout(function() {
57158 scrollViewBox = [0, 0, pw, ph];
57159 dragTail();
57160 }, REDRAWDELAY);
57161
57162 e.preventDefault();
57163 return;
57164 }
57165
57166 // everything but the corners gets wheel zoom
57167 if(ns.length * ew.length !== 1) {
57168 attachWheelEventHandler(dragger, zoomWheel);
57169 }
57170
57171 // plotDrag: move the plot in response to a drag
57172 function plotDrag(dx, dy) {
57173 // If a transition is in progress, then disable any behavior:
57174 if(gd._transitioningWithDuration) {
57175 return;
57176 }
57177
57178 // prevent axis drawing from monkeying with margins until we're done
57179 gd._fullLayout._replotting = true;
57180
57181 if(xActive === 'ew' || yActive === 'ns') {
57182 if(xActive) {
57183 dragAxList(xaxes, dx);
57184 updateMatchedAxRange('x');
57185 }
57186 if(yActive) {
57187 dragAxList(yaxes, dy);
57188 updateMatchedAxRange('y');
57189 }
57190 updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
57191 ticksAndAnnotations();
57192 gd.emit('plotly_relayouting', updates);
57193 return;
57194 }
57195
57196 // dz: set a new value for one end (0 or 1) of an axis array axArray,
57197 // and return a pixel shift for that end for the viewbox
57198 // based on pixel drag distance d
57199 // TODO: this makes (generally non-fatal) errors when you get
57200 // near floating point limits
57201 function dz(axArray, end, d) {
57202 var otherEnd = 1 - end;
57203 var movedAx;
57204 var newLinearizedEnd;
57205 for(var i = 0; i < axArray.length; i++) {
57206 var axi = axArray[i];
57207 if(axi.fixedrange) continue;
57208 movedAx = axi;
57209 newLinearizedEnd = axi._rl[otherEnd] +
57210 (axi._rl[end] - axi._rl[otherEnd]) / dZoom(d / axi._length);
57211 var newEnd = axi.l2r(newLinearizedEnd);
57212
57213 // if l2r comes back false or undefined, it means we've dragged off
57214 // the end of valid ranges - so stop.
57215 if(newEnd !== false && newEnd !== undefined) axi.range[end] = newEnd;
57216 }
57217 return movedAx._length * (movedAx._rl[end] - newLinearizedEnd) /
57218 (movedAx._rl[end] - movedAx._rl[otherEnd]);
57219 }
57220
57221 if(links.isSubplotConstrained && xActive && yActive) {
57222 // dragging a corner of a constrained subplot:
57223 // respect the fixed corner, but harmonize dx and dy
57224 var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1;
57225 var dxyFraction = (dx / pw + dxySign * dy / ph) / 2;
57226 dx = dxyFraction * pw;
57227 dy = dxySign * dxyFraction * ph;
57228 }
57229
57230 if(xActive === 'w') dx = dz(xaxes, 0, dx);
57231 else if(xActive === 'e') dx = dz(xaxes, 1, -dx);
57232 else if(!xActive) dx = 0;
57233
57234 if(yActive === 'n') dy = dz(yaxes, 1, dy);
57235 else if(yActive === 's') dy = dz(yaxes, 0, -dy);
57236 else if(!yActive) dy = 0;
57237
57238 var xStart = (xActive === 'w') ? dx : 0;
57239 var yStart = (yActive === 'n') ? dy : 0;
57240
57241 if(links.isSubplotConstrained) {
57242 var i;
57243 if(!xActive && yActive.length === 1) {
57244 // dragging one end of the y axis of a constrained subplot
57245 // scale the other axis the same about its middle
57246 for(i = 0; i < xaxes.length; i++) {
57247 xaxes[i].range = xaxes[i]._r.slice();
57248 scaleZoom(xaxes[i], 1 - dy / ph);
57249 }
57250 dx = dy * pw / ph;
57251 xStart = dx / 2;
57252 }
57253 if(!yActive && xActive.length === 1) {
57254 for(i = 0; i < yaxes.length; i++) {
57255 yaxes[i].range = yaxes[i]._r.slice();
57256 scaleZoom(yaxes[i], 1 - dx / pw);
57257 }
57258 dy = dx * ph / pw;
57259 yStart = dy / 2;
57260 }
57261 }
57262
57263 updateMatchedAxRange('x');
57264 updateMatchedAxRange('y');
57265 updateSubplots([xStart, yStart, pw - dx, ph - dy]);
57266 ticksAndAnnotations();
57267 gd.emit('plotly_relayouting', updates);
57268 }
57269
57270 function updateMatchedAxRange(axLetter, out) {
57271 var matchedAxes = matches.isSubplotConstrained ?
57272 {x: yaxes, y: xaxes}[axLetter] :
57273 matches[axLetter + 'axes'];
57274
57275 var constrainedAxes = matches.isSubplotConstrained ?
57276 {x: xaxes, y: yaxes}[axLetter] :
57277 [];
57278
57279 for(var i = 0; i < matchedAxes.length; i++) {
57280 var ax = matchedAxes[i];
57281 var axId = ax._id;
57282 var axId2 = matches.xLinks[axId] || matches.yLinks[axId];
57283 var ax2 = constrainedAxes[0] || xaHash[axId2] || yaHash[axId2];
57284
57285 if(ax2) {
57286 if(out) {
57287 // zoombox case - don't mutate 'range', just add keys in 'updates'
57288 out[ax._name + '.range[0]'] = out[ax2._name + '.range[0]'];
57289 out[ax._name + '.range[1]'] = out[ax2._name + '.range[1]'];
57290 } else {
57291 ax.range = ax2.range.slice();
57292 }
57293 }
57294 }
57295 }
57296
57297 // Draw ticks and annotations (and other components) when ranges change.
57298 // Also records the ranges that have changed for use by update at the end.
57299 function ticksAndAnnotations() {
57300 var activeAxIds = [];
57301 var i;
57302
57303 function pushActiveAxIds(axList) {
57304 for(i = 0; i < axList.length; i++) {
57305 if(!axList[i].fixedrange) activeAxIds.push(axList[i]._id);
57306 }
57307 }
57308
57309 if(editX) {
57310 pushActiveAxIds(xaxes);
57311 pushActiveAxIds(links.xaxes);
57312 pushActiveAxIds(matches.xaxes);
57313 }
57314 if(editY) {
57315 pushActiveAxIds(yaxes);
57316 pushActiveAxIds(links.yaxes);
57317 pushActiveAxIds(matches.yaxes);
57318 }
57319
57320 updates = {};
57321 for(i = 0; i < activeAxIds.length; i++) {
57322 var axId = activeAxIds[i];
57323 var ax = getFromId(gd, axId);
57324 Axes.drawOne(gd, ax, {skipTitle: true});
57325 updates[ax._name + '.range[0]'] = ax.range[0];
57326 updates[ax._name + '.range[1]'] = ax.range[1];
57327 }
57328
57329 Axes.redrawComponents(gd, activeAxIds);
57330 }
57331
57332 function doubleClick() {
57333 if(gd._transitioningWithDuration) return;
57334
57335 var doubleClickConfig = gd._context.doubleClick;
57336
57337 var axList = [];
57338 if(xActive) axList = axList.concat(xaxes);
57339 if(yActive) axList = axList.concat(yaxes);
57340 if(matches.xaxes) axList = axList.concat(matches.xaxes);
57341 if(matches.yaxes) axList = axList.concat(matches.yaxes);
57342
57343 var attrs = {};
57344 var ax, i, rangeInitial;
57345
57346 // For reset+autosize mode:
57347 // If *any* of the main axes is not at its initial range
57348 // (or autoranged, if we have no initial range, to match the logic in
57349 // doubleClickConfig === 'reset' below), we reset.
57350 // If they are *all* at their initial ranges, then we autosize.
57351 if(doubleClickConfig === 'reset+autosize') {
57352 doubleClickConfig = 'autosize';
57353
57354 for(i = 0; i < axList.length; i++) {
57355 ax = axList[i];
57356 if((ax._rangeInitial && (
57357 ax.range[0] !== ax._rangeInitial[0] ||
57358 ax.range[1] !== ax._rangeInitial[1]
57359 )) ||
57360 (!ax._rangeInitial && !ax.autorange)
57361 ) {
57362 doubleClickConfig = 'reset';
57363 break;
57364 }
57365 }
57366 }
57367
57368 if(doubleClickConfig === 'autosize') {
57369 // don't set the linked axes here, so relayout marks them as shrinkable
57370 // and we autosize just to the requested axis/axes
57371 for(i = 0; i < axList.length; i++) {
57372 ax = axList[i];
57373 if(!ax.fixedrange) attrs[ax._name + '.autorange'] = true;
57374 }
57375 } else if(doubleClickConfig === 'reset') {
57376 // when we're resetting, reset all linked axes too, so we get back
57377 // to the fully-auto-with-constraints situation
57378 if(xActive || links.isSubplotConstrained) axList = axList.concat(links.xaxes);
57379 if(yActive && !links.isSubplotConstrained) axList = axList.concat(links.yaxes);
57380
57381 if(links.isSubplotConstrained) {
57382 if(!xActive) axList = axList.concat(xaxes);
57383 else if(!yActive) axList = axList.concat(yaxes);
57384 }
57385
57386 for(i = 0; i < axList.length; i++) {
57387 ax = axList[i];
57388
57389 if(!ax.fixedrange) {
57390 if(!ax._rangeInitial) {
57391 attrs[ax._name + '.autorange'] = true;
57392 } else {
57393 rangeInitial = ax._rangeInitial;
57394 attrs[ax._name + '.range[0]'] = rangeInitial[0];
57395 attrs[ax._name + '.range[1]'] = rangeInitial[1];
57396 }
57397 }
57398 }
57399 }
57400
57401 gd.emit('plotly_doubleclick', null);
57402 Registry.call('_guiRelayout', gd, attrs);
57403 }
57404
57405 // dragTail - finish a drag event with a redraw
57406 function dragTail() {
57407 // put the subplot viewboxes back to default (Because we're going to)
57408 // be repositioning the data in the relayout. But DON'T call
57409 // ticksAndAnnotations again - it's unnecessary and would overwrite `updates`
57410 updateSubplots([0, 0, pw, ph]);
57411
57412 // since we may have been redrawing some things during the drag, we may have
57413 // accumulated MathJax promises - wait for them before we relayout.
57414 Lib.syncOrAsync([
57415 Plots.previousPromises,
57416 function() {
57417 gd._fullLayout._replotting = false;
57418 Registry.call('_guiRelayout', gd, updates);
57419 }
57420 ], gd);
57421 }
57422
57423 // updateSubplots - find all plot viewboxes that should be
57424 // affected by this drag, and update them. look for all plots
57425 // sharing an affected axis (including the one being dragged),
57426 // includes also scattergl and splom logic.
57427 function updateSubplots(viewBox) {
57428 var fullLayout = gd._fullLayout;
57429 var plotinfos = fullLayout._plots;
57430 var subplots = fullLayout._subplots.cartesian;
57431 var i, sp, xa, ya;
57432
57433 if(hasSplom) {
57434 Registry.subplotsRegistry.splom.drag(gd);
57435 }
57436
57437 if(hasScatterGl) {
57438 for(i = 0; i < subplots.length; i++) {
57439 sp = plotinfos[subplots[i]];
57440 xa = sp.xaxis;
57441 ya = sp.yaxis;
57442
57443 if(sp._scene) {
57444 var xrng = Lib.simpleMap(xa.range, xa.r2l);
57445 var yrng = Lib.simpleMap(ya.range, ya.r2l);
57446 sp._scene.update({range: [xrng[0], yrng[0], xrng[1], yrng[1]]});
57447 }
57448 }
57449 }
57450
57451 if(hasSplom || hasScatterGl) {
57452 clearGlCanvases(gd);
57453 redrawReglTraces(gd);
57454 }
57455
57456 if(hasSVG) {
57457 var xScaleFactor = viewBox[2] / xa0._length;
57458 var yScaleFactor = viewBox[3] / ya0._length;
57459
57460 for(i = 0; i < subplots.length; i++) {
57461 sp = plotinfos[subplots[i]];
57462 xa = sp.xaxis;
57463 ya = sp.yaxis;
57464
57465 var editX2 = editX && !xa.fixedrange && xaHash[xa._id];
57466 var editY2 = editY && !ya.fixedrange && yaHash[ya._id];
57467
57468 var xScaleFactor2, yScaleFactor2;
57469 var clipDx, clipDy;
57470
57471 if(editX2) {
57472 xScaleFactor2 = xScaleFactor;
57473 clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2);
57474 } else if(matches.xaHash[xa._id]) {
57475 xScaleFactor2 = xScaleFactor;
57476 clipDx = viewBox[0] * xa._length / xa0._length;
57477 } else if(matches.yaHash[xa._id]) {
57478 xScaleFactor2 = yScaleFactor;
57479 clipDx = yActive === 'ns' ?
57480 -viewBox[1] * xa._length / ya0._length :
57481 getShift(xa, xScaleFactor2, {n: 'top', s: 'bottom'}[yActive]);
57482 } else {
57483 xScaleFactor2 = getLinkedScaleFactor(xa, xScaleFactor, yScaleFactor);
57484 clipDx = scaleAndGetShift(xa, xScaleFactor2);
57485 }
57486
57487 if(editY2) {
57488 yScaleFactor2 = yScaleFactor;
57489 clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2);
57490 } else if(matches.yaHash[ya._id]) {
57491 yScaleFactor2 = yScaleFactor;
57492 clipDy = viewBox[1] * ya._length / ya0._length;
57493 } else if(matches.xaHash[ya._id]) {
57494 yScaleFactor2 = xScaleFactor;
57495 clipDy = xActive === 'ew' ?
57496 -viewBox[0] * ya._length / xa0._length :
57497 getShift(ya, yScaleFactor2, {e: 'right', w: 'left'}[xActive]);
57498 } else {
57499 yScaleFactor2 = getLinkedScaleFactor(ya, xScaleFactor, yScaleFactor);
57500 clipDy = scaleAndGetShift(ya, yScaleFactor2);
57501 }
57502
57503 // don't scale at all if neither axis is scalable here
57504 if(!xScaleFactor2 && !yScaleFactor2) {
57505 continue;
57506 }
57507
57508 // but if only one is, reset the other axis scaling
57509 if(!xScaleFactor2) xScaleFactor2 = 1;
57510 if(!yScaleFactor2) yScaleFactor2 = 1;
57511
57512 var plotDx = xa._offset - clipDx / xScaleFactor2;
57513 var plotDy = ya._offset - clipDy / yScaleFactor2;
57514
57515 // TODO could be more efficient here:
57516 // setTranslate and setScale do a lot of extra work
57517 // when working independently, should perhaps combine
57518 // them into a single routine.
57519 sp.clipRect
57520 .call(Drawing.setTranslate, clipDx, clipDy)
57521 .call(Drawing.setScale, xScaleFactor2, yScaleFactor2);
57522
57523 sp.plot
57524 .call(Drawing.setTranslate, plotDx, plotDy)
57525 .call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2);
57526
57527 // apply an inverse scale to individual points to counteract
57528 // the scale of the trace group.
57529 // apply only when scale changes, as adjusting the scale of
57530 // all the points can be expansive.
57531 if(xScaleFactor2 !== sp.xScaleFactor || yScaleFactor2 !== sp.yScaleFactor) {
57532 Drawing.setPointGroupScale(sp.zoomScalePts, xScaleFactor2, yScaleFactor2);
57533 Drawing.setTextPointsScale(sp.zoomScaleTxt, xScaleFactor2, yScaleFactor2);
57534 }
57535
57536 Drawing.hideOutsideRangePoints(sp.clipOnAxisFalseTraces, sp);
57537
57538 // update x/y scaleFactor stash
57539 sp.xScaleFactor = xScaleFactor2;
57540 sp.yScaleFactor = yScaleFactor2;
57541 }
57542 }
57543 }
57544
57545 // Find the appropriate scaling for this axis, if it's linked to the
57546 // dragged axes by constraints. 0 is special, it means this axis shouldn't
57547 // ever be scaled (will be converted to 1 if the other axis is scaled)
57548 function getLinkedScaleFactor(ax, xScaleFactor, yScaleFactor) {
57549 if(ax.fixedrange) return 0;
57550
57551 if(editX && links.xaHash[ax._id]) {
57552 return xScaleFactor;
57553 }
57554 if(editY && (links.isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) {
57555 return yScaleFactor;
57556 }
57557 return 0;
57558 }
57559
57560 function scaleAndGetShift(ax, scaleFactor) {
57561 if(scaleFactor) {
57562 ax.range = ax._r.slice();
57563 scaleZoom(ax, scaleFactor);
57564 return getShift(ax, scaleFactor);
57565 }
57566 return 0;
57567 }
57568
57569 function getShift(ax, scaleFactor, from) {
57570 return ax._length * (1 - scaleFactor) * FROM_TL[from || ax.constraintoward || 'middle'];
57571 }
57572
57573 return dragger;
57574}
57575
57576function makeDragger(plotinfo, nodeName, dragClass, cursor) {
57577 var dragger3 = Lib.ensureSingle(plotinfo.draglayer, nodeName, dragClass, function(s) {
57578 s.classed('drag', true)
57579 .style({fill: 'transparent', 'stroke-width': 0})
57580 .attr('data-subplot', plotinfo.id);
57581 });
57582
57583 dragger3.call(setCursor, cursor);
57584
57585 return dragger3.node();
57586}
57587
57588function makeRectDragger(plotinfo, dragClass, cursor, x, y, w, h) {
57589 var dragger = makeDragger(plotinfo, 'rect', dragClass, cursor);
57590 d3.select(dragger).call(Drawing.setRect, x, y, w, h);
57591 return dragger;
57592}
57593
57594function isDirectionActive(axList, activeVal) {
57595 for(var i = 0; i < axList.length; i++) {
57596 if(!axList[i].fixedrange) return activeVal;
57597 }
57598 return '';
57599}
57600
57601function getEndText(ax, end) {
57602 var initialVal = ax.range[end];
57603 var diff = Math.abs(initialVal - ax.range[1 - end]);
57604 var dig;
57605
57606 // TODO: this should basically be ax.r2d but we're doing extra
57607 // rounding here... can we clean up at all?
57608 if(ax.type === 'date') {
57609 return initialVal;
57610 } else if(ax.type === 'log') {
57611 dig = Math.ceil(Math.max(0, -Math.log(diff) / Math.LN10)) + 3;
57612 return d3.format('.' + dig + 'g')(Math.pow(10, initialVal));
57613 } else { // linear numeric (or category... but just show numbers here)
57614 dig = Math.floor(Math.log(Math.abs(initialVal)) / Math.LN10) -
57615 Math.floor(Math.log(diff) / Math.LN10) + 4;
57616 return d3.format('.' + String(dig) + 'g')(initialVal);
57617 }
57618}
57619
57620function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) {
57621 for(var i = 0; i < axList.length; i++) {
57622 var axi = axList[i];
57623 if(axi.fixedrange) continue;
57624
57625 if(axi.rangebreaks) {
57626 var isY = axi._id.charAt(0) === 'y';
57627 var r0F = isY ? (1 - r0Fraction) : r0Fraction;
57628 var r1F = isY ? (1 - r1Fraction) : r1Fraction;
57629
57630 updates[axi._name + '.range[0]'] = axi.l2r(axi.p2l(r0F * axi._length));
57631 updates[axi._name + '.range[1]'] = axi.l2r(axi.p2l(r1F * axi._length));
57632 } else {
57633 var axRangeLinear0 = axi._rl[0];
57634 var axRangeLinearSpan = axi._rl[1] - axRangeLinear0;
57635 updates[axi._name + '.range[0]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r0Fraction);
57636 updates[axi._name + '.range[1]'] = axi.l2r(axRangeLinear0 + axRangeLinearSpan * r1Fraction);
57637 }
57638 }
57639
57640 // zoom linked axes about their centers
57641 if(linkedAxes && linkedAxes.length) {
57642 var linkedR0Fraction = (r0Fraction + (1 - r1Fraction)) / 2;
57643 zoomAxRanges(linkedAxes, linkedR0Fraction, 1 - linkedR0Fraction, updates, []);
57644 }
57645}
57646
57647function dragAxList(axList, pix) {
57648 for(var i = 0; i < axList.length; i++) {
57649 var axi = axList[i];
57650 if(!axi.fixedrange) {
57651 if(axi.rangebreaks) {
57652 var p0 = 0;
57653 var p1 = axi._length;
57654 var d0 = axi.p2l(p0 + pix) - axi.p2l(p0);
57655 var d1 = axi.p2l(p1 + pix) - axi.p2l(p1);
57656 var delta = (d0 + d1) / 2;
57657
57658 axi.range = [
57659 axi.l2r(axi._rl[0] - delta),
57660 axi.l2r(axi._rl[1] - delta)
57661 ];
57662 } else {
57663 axi.range = [
57664 axi.l2r(axi._rl[0] - pix / axi._m),
57665 axi.l2r(axi._rl[1] - pix / axi._m)
57666 ];
57667 }
57668 }
57669 }
57670}
57671
57672// common transform for dragging one end of an axis
57673// d>0 is compressing scale (cursor is over the plot,
57674// the axis end should move with the cursor)
57675// d<0 is expanding (cursor is off the plot, axis end moves
57676// nonlinearly so you can expand far)
57677function dZoom(d) {
57678 return 1 - ((d >= 0) ? Math.min(d, 0.9) :
57679 1 / (1 / Math.max(d, -0.3) + 3.222));
57680}
57681
57682function getDragCursor(nsew, dragmode, isMainDrag) {
57683 if(!nsew) return 'pointer';
57684 if(nsew === 'nsew') {
57685 // in this case here, clear cursor and
57686 // use the cursor style set on <g .draglayer>
57687 if(isMainDrag) return '';
57688 if(dragmode === 'pan') return 'move';
57689 return 'crosshair';
57690 }
57691 return nsew.toLowerCase() + '-resize';
57692}
57693
57694function makeZoombox(zoomlayer, lum, xs, ys, path0) {
57695 return zoomlayer.append('path')
57696 .attr('class', 'zoombox')
57697 .style({
57698 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
57699 'stroke-width': 0
57700 })
57701 .attr('transform', 'translate(' + xs + ', ' + ys + ')')
57702 .attr('d', path0 + 'Z');
57703}
57704
57705function makeCorners(zoomlayer, xs, ys) {
57706 return zoomlayer.append('path')
57707 .attr('class', 'zoombox-corners')
57708 .style({
57709 fill: Color.background,
57710 stroke: Color.defaultLine,
57711 'stroke-width': 1,
57712 opacity: 0
57713 })
57714 .attr('transform', 'translate(' + xs + ', ' + ys + ')')
57715 .attr('d', 'M0,0Z');
57716}
57717
57718function updateZoombox(zb, corners, box, path0, dimmed, lum) {
57719 zb.attr('d',
57720 path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) +
57721 'h' + (box.w) + 'v-' + (box.h) + 'h-' + (box.w) + 'Z');
57722 transitionZoombox(zb, corners, dimmed, lum);
57723}
57724
57725function transitionZoombox(zb, corners, dimmed, lum) {
57726 if(!dimmed) {
57727 zb.transition()
57728 .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' :
57729 'rgba(255,255,255,0.3)')
57730 .duration(200);
57731 corners.transition()
57732 .style('opacity', 1)
57733 .duration(200);
57734 }
57735}
57736
57737function removeZoombox(gd) {
57738 d3.select(gd)
57739 .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
57740 .remove();
57741}
57742
57743function showDoubleClickNotifier(gd) {
57744 if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
57745 Lib.notifier(Lib._(gd, 'Double-click to zoom back out'), 'long');
57746 SHOWZOOMOUTTIP = false;
57747 }
57748}
57749
57750function xCorners(box, y0) {
57751 return 'M' +
57752 (box.l - 0.5) + ',' + (y0 - MINZOOM - 0.5) +
57753 'h-3v' + (2 * MINZOOM + 1) + 'h3ZM' +
57754 (box.r + 0.5) + ',' + (y0 - MINZOOM - 0.5) +
57755 'h3v' + (2 * MINZOOM + 1) + 'h-3Z';
57756}
57757
57758function yCorners(box, x0) {
57759 return 'M' +
57760 (x0 - MINZOOM - 0.5) + ',' + (box.t - 0.5) +
57761 'v-3h' + (2 * MINZOOM + 1) + 'v3ZM' +
57762 (x0 - MINZOOM - 0.5) + ',' + (box.b + 0.5) +
57763 'v3h' + (2 * MINZOOM + 1) + 'v-3Z';
57764}
57765
57766function xyCorners(box) {
57767 var clen = Math.floor(Math.min(box.b - box.t, box.r - box.l, MINZOOM) / 2);
57768 return 'M' +
57769 (box.l - 3.5) + ',' + (box.t - 0.5 + clen) + 'h3v' + (-clen) +
57770 'h' + clen + 'v-3h-' + (clen + 3) + 'ZM' +
57771 (box.r + 3.5) + ',' + (box.t - 0.5 + clen) + 'h-3v' + (-clen) +
57772 'h' + (-clen) + 'v-3h' + (clen + 3) + 'ZM' +
57773 (box.r + 3.5) + ',' + (box.b + 0.5 - clen) + 'h-3v' + clen +
57774 'h' + (-clen) + 'v3h' + (clen + 3) + 'ZM' +
57775 (box.l - 3.5) + ',' + (box.b + 0.5 - clen) + 'h3v' + clen +
57776 'h' + clen + 'v3h-' + (clen + 3) + 'Z';
57777}
57778
57779function calcLinks(gd, groups, xaHash, yaHash) {
57780 var isSubplotConstrained = false;
57781 var xLinks = {};
57782 var yLinks = {};
57783 var xID, yID, xLinkID, yLinkID;
57784
57785 for(var i = 0; i < groups.length; i++) {
57786 var group = groups[i];
57787 // check if any of the x axes we're dragging is in this constraint group
57788 for(xID in xaHash) {
57789 if(group[xID]) {
57790 // put the rest of these axes into xLinks, if we're not already
57791 // dragging them, so we know to scale these axes automatically too
57792 // to match the changes in the dragged x axes
57793 for(xLinkID in group) {
57794 if(!(xLinkID.charAt(0) === 'x' ? xaHash : yaHash)[xLinkID]) {
57795 xLinks[xLinkID] = xID;
57796 }
57797 }
57798
57799 // check if the x and y axes of THIS drag are linked
57800 for(yID in yaHash) {
57801 if(group[yID]) isSubplotConstrained = true;
57802 }
57803 }
57804 }
57805
57806 // now check if any of the y axes we're dragging is in this constraint group
57807 // only look for outside links, as we've already checked for links within the dragger
57808 for(yID in yaHash) {
57809 if(group[yID]) {
57810 for(yLinkID in group) {
57811 if(!(yLinkID.charAt(0) === 'x' ? xaHash : yaHash)[yLinkID]) {
57812 yLinks[yLinkID] = yID;
57813 }
57814 }
57815 }
57816 }
57817 }
57818
57819 if(isSubplotConstrained) {
57820 // merge xLinks and yLinks if the subplot is constrained,
57821 // since we'll always apply both anyway and the two will contain
57822 // duplicates
57823 Lib.extendFlat(xLinks, yLinks);
57824 yLinks = {};
57825 }
57826
57827 var xaHashLinked = {};
57828 var xaxesLinked = [];
57829 for(xLinkID in xLinks) {
57830 var xa = getFromId(gd, xLinkID);
57831 xaxesLinked.push(xa);
57832 xaHashLinked[xa._id] = xa;
57833 }
57834
57835 var yaHashLinked = {};
57836 var yaxesLinked = [];
57837 for(yLinkID in yLinks) {
57838 var ya = getFromId(gd, yLinkID);
57839 yaxesLinked.push(ya);
57840 yaHashLinked[ya._id] = ya;
57841 }
57842
57843 return {
57844 xaHash: xaHashLinked,
57845 yaHash: yaHashLinked,
57846 xaxes: xaxesLinked,
57847 yaxes: yaxesLinked,
57848 xLinks: xLinks,
57849 yLinks: yLinks,
57850 isSubplotConstrained: isSubplotConstrained
57851 };
57852}
57853
57854// still seems to be some confusion about onwheel vs onmousewheel...
57855function attachWheelEventHandler(element, handler) {
57856 if(!supportsPassive) {
57857 if(element.onwheel !== undefined) element.onwheel = handler;
57858 else if(element.onmousewheel !== undefined) element.onmousewheel = handler;
57859 else if(!element.isAddedWheelEvent) {
57860 element.isAddedWheelEvent = true;
57861 element.addEventListener('wheel', handler, {passive: false});
57862 }
57863 } else {
57864 var wheelEventName = element.onwheel !== undefined ? 'wheel' : 'mousewheel';
57865
57866 if(element._onwheel) {
57867 element.removeEventListener(wheelEventName, element._onwheel);
57868 }
57869 element._onwheel = handler;
57870
57871 element.addEventListener(wheelEventName, handler, {passive: false});
57872 }
57873}
57874
57875function hashValues(hash) {
57876 var out = [];
57877 for(var k in hash) out.push(hash[k]);
57878 return out;
57879}
57880
57881module.exports = {
57882 makeDragBox: makeDragBox,
57883
57884 makeDragger: makeDragger,
57885 makeRectDragger: makeRectDragger,
57886 makeZoombox: makeZoombox,
57887 makeCorners: makeCorners,
57888
57889 updateZoombox: updateZoombox,
57890 xyCorners: xyCorners,
57891 transitionZoombox: transitionZoombox,
57892 removeZoombox: removeZoombox,
57893 showDoubleClickNotifier: showDoubleClickNotifier,
57894
57895 attachWheelEventHandler: attachWheelEventHandler
57896};
57897
57898},{"../../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){
57899/**
57900* Copyright 2012-2020, Plotly, Inc.
57901* All rights reserved.
57902*
57903* This source code is licensed under the MIT license found in the
57904* LICENSE file in the root directory of this source tree.
57905*/
57906
57907
57908'use strict';
57909
57910var d3 = _dereq_('d3');
57911
57912var Fx = _dereq_('../../components/fx');
57913var dragElement = _dereq_('../../components/dragelement');
57914var setCursor = _dereq_('../../lib/setcursor');
57915
57916var makeDragBox = _dereq_('./dragbox').makeDragBox;
57917var DRAGGERSIZE = _dereq_('./constants').DRAGGERSIZE;
57918
57919exports.initInteractions = function initInteractions(gd) {
57920 var fullLayout = gd._fullLayout;
57921
57922 if(gd._context.staticPlot) {
57923 // this sweeps up more than just cartesian drag elements...
57924 d3.select(gd).selectAll('.drag').remove();
57925 return;
57926 }
57927
57928 if(!fullLayout._has('cartesian') && !fullLayout._has('splom')) return;
57929
57930 var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
57931 // sort overlays last, then by x axis number, then y axis number
57932 if((fullLayout._plots[a].mainplot && true) ===
57933 (fullLayout._plots[b].mainplot && true)) {
57934 var aParts = a.split('y');
57935 var bParts = b.split('y');
57936 return (aParts[0] === bParts[0]) ?
57937 (Number(aParts[1] || 1) - Number(bParts[1] || 1)) :
57938 (Number(aParts[0] || 1) - Number(bParts[0] || 1));
57939 }
57940 return fullLayout._plots[a].mainplot ? 1 : -1;
57941 });
57942
57943 subplots.forEach(function(subplot) {
57944 var plotinfo = fullLayout._plots[subplot];
57945 var xa = plotinfo.xaxis;
57946 var ya = plotinfo.yaxis;
57947
57948 // main and corner draggers need not be repeated for
57949 // overlaid subplots - these draggers drag them all
57950 if(!plotinfo.mainplot) {
57951 // main dragger goes over the grids and data, so we use its
57952 // mousemove events for all data hover effects
57953 var maindrag = makeDragBox(gd, plotinfo, xa._offset, ya._offset,
57954 xa._length, ya._length, 'ns', 'ew');
57955
57956 maindrag.onmousemove = function(evt) {
57957 // This is on `gd._fullLayout`, *not* fullLayout because the reference
57958 // changes by the time this is called again.
57959 gd._fullLayout._rehover = function() {
57960 if((gd._fullLayout._hoversubplot === subplot) && gd._fullLayout._plots[subplot]) {
57961 Fx.hover(gd, evt, subplot);
57962 }
57963 };
57964
57965 Fx.hover(gd, evt, subplot);
57966
57967 // Note that we have *not* used the cached fullLayout variable here
57968 // since that may be outdated when this is called as a callback later on
57969 gd._fullLayout._lasthover = maindrag;
57970 gd._fullLayout._hoversubplot = subplot;
57971 };
57972
57973 /*
57974 * IMPORTANT:
57975 * We must check for the presence of the drag cover here.
57976 * If we don't, a 'mouseout' event is triggered on the
57977 * maindrag before each 'click' event, which has the effect
57978 * of clearing the hoverdata; thus, cancelling the click event.
57979 */
57980 maindrag.onmouseout = function(evt) {
57981 if(gd._dragging) return;
57982
57983 // When the mouse leaves this maindrag, unset the hovered subplot.
57984 // This may cause problems if it leaves the subplot directly *onto*
57985 // another subplot, but that's a tiny corner case at the moment.
57986 gd._fullLayout._hoversubplot = null;
57987
57988 dragElement.unhover(gd, evt);
57989 };
57990
57991 // corner draggers
57992 if(gd._context.showAxisDragHandles) {
57993 makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset - DRAGGERSIZE,
57994 DRAGGERSIZE, DRAGGERSIZE, 'n', 'w');
57995 makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset - DRAGGERSIZE,
57996 DRAGGERSIZE, DRAGGERSIZE, 'n', 'e');
57997 makeDragBox(gd, plotinfo, xa._offset - DRAGGERSIZE, ya._offset + ya._length,
57998 DRAGGERSIZE, DRAGGERSIZE, 's', 'w');
57999 makeDragBox(gd, plotinfo, xa._offset + xa._length, ya._offset + ya._length,
58000 DRAGGERSIZE, DRAGGERSIZE, 's', 'e');
58001 }
58002 }
58003 if(gd._context.showAxisDragHandles) {
58004 // x axis draggers - if you have overlaid plots,
58005 // these drag each axis separately
58006 if(subplot === xa._mainSubplot) {
58007 // the y position of the main x axis line
58008 var y0 = xa._mainLinePosition;
58009 if(xa.side === 'top') y0 -= DRAGGERSIZE;
58010 makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.1, y0,
58011 xa._length * 0.8, DRAGGERSIZE, '', 'ew');
58012 makeDragBox(gd, plotinfo, xa._offset, y0,
58013 xa._length * 0.1, DRAGGERSIZE, '', 'w');
58014 makeDragBox(gd, plotinfo, xa._offset + xa._length * 0.9, y0,
58015 xa._length * 0.1, DRAGGERSIZE, '', 'e');
58016 }
58017 // y axis draggers
58018 if(subplot === ya._mainSubplot) {
58019 // the x position of the main y axis line
58020 var x0 = ya._mainLinePosition;
58021 if(ya.side !== 'right') x0 -= DRAGGERSIZE;
58022 makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.1,
58023 DRAGGERSIZE, ya._length * 0.8, 'ns', '');
58024 makeDragBox(gd, plotinfo, x0, ya._offset + ya._length * 0.9,
58025 DRAGGERSIZE, ya._length * 0.1, 's', '');
58026 makeDragBox(gd, plotinfo, x0, ya._offset,
58027 DRAGGERSIZE, ya._length * 0.1, 'n', '');
58028 }
58029 }
58030 });
58031
58032 // In case you mousemove over some hovertext, send it to Fx.hover too
58033 // we do this so that we can put the hover text in front of everything,
58034 // but still be able to interact with everything as if it isn't there
58035 var hoverLayer = fullLayout._hoverlayer.node();
58036
58037 hoverLayer.onmousemove = function(evt) {
58038 evt.target = gd._fullLayout._lasthover;
58039 Fx.hover(gd, evt, fullLayout._hoversubplot);
58040 };
58041
58042 hoverLayer.onclick = function(evt) {
58043 evt.target = gd._fullLayout._lasthover;
58044 Fx.click(gd, evt);
58045 };
58046
58047 // also delegate mousedowns... TODO: does this actually work?
58048 hoverLayer.onmousedown = function(evt) {
58049 gd._fullLayout._lasthover.onmousedown(evt);
58050 };
58051
58052 exports.updateFx(gd);
58053};
58054
58055// Minimal set of update needed on 'modebar' edits.
58056// We only need to update the <g .draglayer> cursor style.
58057//
58058// Note that changing the axis configuration and/or the fixedrange attribute
58059// should trigger a full initInteractions.
58060exports.updateFx = function(gd) {
58061 var fullLayout = gd._fullLayout;
58062 var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair';
58063 setCursor(fullLayout._draggers, cursor);
58064};
58065
58066},{"../../components/dragelement":71,"../../components/fx":92,"../../lib/setcursor":197,"./constants":228,"./dragbox":230,"d3":16}],232:[function(_dereq_,module,exports){
58067/**
58068* Copyright 2012-2020, Plotly, Inc.
58069* All rights reserved.
58070*
58071* This source code is licensed under the MIT license found in the
58072* LICENSE file in the root directory of this source tree.
58073*/
58074
58075
58076'use strict';
58077
58078function clearOutlineControllers(gd) {
58079 var zoomLayer = gd._fullLayout._zoomlayer;
58080 if(zoomLayer) {
58081 zoomLayer.selectAll('.outline-controllers').remove();
58082 }
58083}
58084
58085function clearSelect(gd) {
58086 var zoomLayer = gd._fullLayout._zoomlayer;
58087 if(zoomLayer) {
58088 // until we get around to persistent selections, remove the outline
58089 // here. The selection itself will be removed when the plot redraws
58090 // at the end.
58091 zoomLayer.selectAll('.select-outline').remove();
58092 }
58093
58094 gd._fullLayout._drawing = false;
58095}
58096
58097module.exports = {
58098 clearOutlineControllers: clearOutlineControllers,
58099 clearSelect: clearSelect
58100};
58101
58102},{}],233:[function(_dereq_,module,exports){
58103/**
58104* Copyright 2012-2020, Plotly, Inc.
58105* All rights reserved.
58106*
58107* This source code is licensed under the MIT license found in the
58108* LICENSE file in the root directory of this source tree.
58109*/
58110
58111
58112'use strict';
58113
58114// in v2 (once log ranges are fixed),
58115// we'll be able to p2r here for all axis types
58116function p2r(ax, v) {
58117 switch(ax.type) {
58118 case 'log':
58119 return ax.p2d(v);
58120 case 'date':
58121 return ax.p2r(v, 0, ax.calendar);
58122 default:
58123 return ax.p2r(v);
58124 }
58125}
58126
58127function r2p(ax, v) {
58128 switch(ax.type) {
58129 case 'log':
58130 return ax.d2p(v);
58131 case 'date':
58132 return ax.r2p(v, 0, ax.calendar);
58133 default:
58134 return ax.r2p(v);
58135 }
58136}
58137
58138function axValue(ax) {
58139 var index = (ax._id.charAt(0) === 'y') ? 1 : 0;
58140 return function(v) { return p2r(ax, v[index]); };
58141}
58142
58143function getTransform(plotinfo) {
58144 return 'translate(' +
58145 plotinfo.xaxis._offset + ',' +
58146 plotinfo.yaxis._offset + ')';
58147}
58148
58149module.exports = {
58150 p2r: p2r,
58151 r2p: r2p,
58152 axValue: axValue,
58153 getTransform: getTransform
58154};
58155
58156},{}],234:[function(_dereq_,module,exports){
58157/**
58158* Copyright 2012-2020, Plotly, Inc.
58159* All rights reserved.
58160*
58161* This source code is licensed under the MIT license found in the
58162* LICENSE file in the root directory of this source tree.
58163*/
58164
58165
58166'use strict';
58167
58168var Registry = _dereq_('../../registry');
58169var Lib = _dereq_('../../lib');
58170
58171/**
58172 * Factory function for checking component arrays for subplot references.
58173 *
58174 * @param {string} containerArrayName: the top-level array in gd.layout to check
58175 * If an item in this container is found that references a cartesian x and/or y axis,
58176 * ensure cartesian is marked as a base plot module and record the axes (and subplot
58177 * if both refs are axes) in gd._fullLayout
58178 *
58179 * @return {function}: with args layoutIn (gd.layout) and layoutOut (gd._fullLayout)
58180 * as expected of a component includeBasePlot method
58181 */
58182module.exports = function makeIncludeComponents(containerArrayName) {
58183 return function includeComponents(layoutIn, layoutOut) {
58184 var array = layoutIn[containerArrayName];
58185 if(!Array.isArray(array)) return;
58186
58187 var Cartesian = Registry.subplotsRegistry.cartesian;
58188 var idRegex = Cartesian.idRegex;
58189 var subplots = layoutOut._subplots;
58190 var xaList = subplots.xaxis;
58191 var yaList = subplots.yaxis;
58192 var cartesianList = subplots.cartesian;
58193 var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d');
58194
58195 for(var i = 0; i < array.length; i++) {
58196 var itemi = array[i];
58197 if(!Lib.isPlainObject(itemi)) continue;
58198
58199 var xref = itemi.xref;
58200 var yref = itemi.yref;
58201
58202 var hasXref = idRegex.x.test(xref);
58203 var hasYref = idRegex.y.test(yref);
58204 if(hasXref || hasYref) {
58205 if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian);
58206
58207 var newAxis = false;
58208 if(hasXref && xaList.indexOf(xref) === -1) {
58209 xaList.push(xref);
58210 newAxis = true;
58211 }
58212 if(hasYref && yaList.indexOf(yref) === -1) {
58213 yaList.push(yref);
58214 newAxis = true;
58215 }
58216
58217 /*
58218 * Notice the logic here: only add a subplot for a component if
58219 * it's referencing both x and y axes AND it's creating a new axis
58220 * so for example if your plot already has xy and x2y2, an annotation
58221 * on x2y or xy2 will not create a new subplot.
58222 */
58223 if(newAxis && hasXref && hasYref) {
58224 cartesianList.push(xref + yref);
58225 }
58226 }
58227 }
58228 };
58229};
58230
58231},{"../../lib":178,"../../registry":269}],235:[function(_dereq_,module,exports){
58232/**
58233* Copyright 2012-2020, Plotly, Inc.
58234* All rights reserved.
58235*
58236* This source code is licensed under the MIT license found in the
58237* LICENSE file in the root directory of this source tree.
58238*/
58239
58240
58241'use strict';
58242
58243var d3 = _dereq_('d3');
58244
58245var Registry = _dereq_('../../registry');
58246var Lib = _dereq_('../../lib');
58247var Plots = _dereq_('../plots');
58248var Drawing = _dereq_('../../components/drawing');
58249
58250var getModuleCalcData = _dereq_('../get_data').getModuleCalcData;
58251var axisIds = _dereq_('./axis_ids');
58252var constants = _dereq_('./constants');
58253var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
58254
58255var ensureSingle = Lib.ensureSingle;
58256
58257function ensureSingleAndAddDatum(parent, nodeType, className) {
58258 return Lib.ensureSingle(parent, nodeType, className, function(s) {
58259 s.datum(className);
58260 });
58261}
58262
58263exports.name = 'cartesian';
58264
58265exports.attr = ['xaxis', 'yaxis'];
58266
58267exports.idRoot = ['x', 'y'];
58268
58269exports.idRegex = constants.idRegex;
58270
58271exports.attrRegex = constants.attrRegex;
58272
58273exports.attributes = _dereq_('./attributes');
58274
58275exports.layoutAttributes = _dereq_('./layout_attributes');
58276
58277exports.supplyLayoutDefaults = _dereq_('./layout_defaults');
58278
58279exports.transitionAxes = _dereq_('./transition_axes');
58280
58281exports.finalizeSubplots = function(layoutIn, layoutOut) {
58282 var subplots = layoutOut._subplots;
58283 var xList = subplots.xaxis;
58284 var yList = subplots.yaxis;
58285 var spSVG = subplots.cartesian;
58286 var spAll = spSVG.concat(subplots.gl2d || []);
58287 var allX = {};
58288 var allY = {};
58289 var i, xi, yi;
58290
58291 for(i = 0; i < spAll.length; i++) {
58292 var parts = spAll[i].split('y');
58293 allX[parts[0]] = 1;
58294 allY['y' + parts[1]] = 1;
58295 }
58296
58297 // check for x axes with no subplot, and make one from the anchor of that x axis
58298 for(i = 0; i < xList.length; i++) {
58299 xi = xList[i];
58300 if(!allX[xi]) {
58301 yi = (layoutIn[axisIds.id2name(xi)] || {}).anchor;
58302 if(!constants.idRegex.y.test(yi)) yi = 'y';
58303 spSVG.push(xi + yi);
58304 spAll.push(xi + yi);
58305
58306 if(!allY[yi]) {
58307 allY[yi] = 1;
58308 Lib.pushUnique(yList, yi);
58309 }
58310 }
58311 }
58312
58313 // same for y axes with no subplot
58314 for(i = 0; i < yList.length; i++) {
58315 yi = yList[i];
58316 if(!allY[yi]) {
58317 xi = (layoutIn[axisIds.id2name(yi)] || {}).anchor;
58318 if(!constants.idRegex.x.test(xi)) xi = 'x';
58319 spSVG.push(xi + yi);
58320 spAll.push(xi + yi);
58321
58322 if(!allX[xi]) {
58323 allX[xi] = 1;
58324 Lib.pushUnique(xList, xi);
58325 }
58326 }
58327 }
58328
58329 // finally, if we've gotten here we're supposed to show cartesian...
58330 // so if there are NO subplots at all, make one from the first
58331 // x & y axes in the input layout
58332 if(!spAll.length) {
58333 xi = '';
58334 yi = '';
58335 for(var ki in layoutIn) {
58336 if(constants.attrRegex.test(ki)) {
58337 var axLetter = ki.charAt(0);
58338 if(axLetter === 'x') {
58339 if(!xi || (+ki.substr(5) < +xi.substr(5))) {
58340 xi = ki;
58341 }
58342 } else if(!yi || (+ki.substr(5) < +yi.substr(5))) {
58343 yi = ki;
58344 }
58345 }
58346 }
58347 xi = xi ? axisIds.name2id(xi) : 'x';
58348 yi = yi ? axisIds.name2id(yi) : 'y';
58349 xList.push(xi);
58350 yList.push(yi);
58351 spSVG.push(xi + yi);
58352 }
58353};
58354
58355/**
58356 * Cartesian.plot
58357 *
58358 * @param {DOM div | object} gd
58359 * @param {array (optional)} traces
58360 * array of traces indices to plot
58361 * if undefined, plots all cartesian traces,
58362 * @param {object} (optional) transitionOpts
58363 * transition option object
58364 * @param {function} (optional) makeOnCompleteCallback
58365 * transition make callback function from Plots.transition
58366 */
58367exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
58368 var fullLayout = gd._fullLayout;
58369 var subplots = fullLayout._subplots.cartesian;
58370 var calcdata = gd.calcdata;
58371 var i;
58372
58373 if(!Array.isArray(traces)) {
58374 // If traces is not provided, then it's a complete replot and missing
58375 // traces are removed
58376 traces = [];
58377 for(i = 0; i < calcdata.length; i++) traces.push(i);
58378 }
58379
58380 for(i = 0; i < subplots.length; i++) {
58381 var subplot = subplots[i];
58382 var subplotInfo = fullLayout._plots[subplot];
58383
58384 // Get all calcdata for this subplot:
58385 var cdSubplot = [];
58386 var pcd;
58387
58388 for(var j = 0; j < calcdata.length; j++) {
58389 var cd = calcdata[j];
58390 var trace = cd[0].trace;
58391
58392 // Skip trace if whitelist provided and it's not whitelisted:
58393 // if (Array.isArray(traces) && traces.indexOf(i) === -1) continue;
58394 if(trace.xaxis + trace.yaxis === subplot) {
58395 // XXX: Should trace carpet dependencies. Only replot all carpet plots if the carpet
58396 // axis has actually changed:
58397 //
58398 // If this trace is specifically requested, add it to the list:
58399 if(traces.indexOf(trace.index) !== -1 || trace.carpet) {
58400 // Okay, so example: traces 0, 1, and 2 have fill = tonext. You animate
58401 // traces 0 and 2. Trace 1 also needs to be updated, otherwise its fill
58402 // is outdated. So this retroactively adds the previous trace if the
58403 // traces are interdependent.
58404 if(
58405 pcd &&
58406 pcd[0].trace.xaxis + pcd[0].trace.yaxis === subplot &&
58407 ['tonextx', 'tonexty', 'tonext'].indexOf(trace.fill) !== -1 &&
58408 cdSubplot.indexOf(pcd) === -1
58409 ) {
58410 cdSubplot.push(pcd);
58411 }
58412
58413 cdSubplot.push(cd);
58414 }
58415
58416 // Track the previous trace on this subplot for the retroactive-add step
58417 // above:
58418 pcd = cd;
58419 }
58420 }
58421
58422 plotOne(gd, subplotInfo, cdSubplot, transitionOpts, makeOnCompleteCallback);
58423 }
58424};
58425
58426function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) {
58427 var traceLayerClasses = constants.traceLayerClasses;
58428 var fullLayout = gd._fullLayout;
58429 var modules = fullLayout._modules;
58430 var _module, cdModuleAndOthers, cdModule;
58431
58432 var layerData = [];
58433 var zoomScaleQueryParts = [];
58434
58435 for(var i = 0; i < modules.length; i++) {
58436 _module = modules[i];
58437 var name = _module.name;
58438 var categories = Registry.modules[name].categories;
58439
58440 if(categories.svg) {
58441 var className = (_module.layerName || name + 'layer');
58442 var plotMethod = _module.plot;
58443
58444 // plot all visible traces of this type on this subplot at once
58445 cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod);
58446 cdModule = cdModuleAndOthers[0];
58447 // don't need to search the found traces again - in fact we need to NOT
58448 // so that if two modules share the same plotter we don't double-plot
58449 cdSubplot = cdModuleAndOthers[1];
58450
58451 if(cdModule.length) {
58452 layerData.push({
58453 i: traceLayerClasses.indexOf(className),
58454 className: className,
58455 plotMethod: plotMethod,
58456 cdModule: cdModule
58457 });
58458 }
58459
58460 if(categories.zoomScale) {
58461 zoomScaleQueryParts.push('.' + className);
58462 }
58463 }
58464 }
58465
58466 layerData.sort(function(a, b) { return a.i - b.i; });
58467
58468 var layers = plotinfo.plot.selectAll('g.mlayer')
58469 .data(layerData, function(d) { return d.className; });
58470
58471 layers.enter().append('g')
58472 .attr('class', function(d) { return d.className; })
58473 .classed('mlayer', true)
58474 .classed('rangeplot', plotinfo.isRangePlot);
58475
58476 layers.exit().remove();
58477
58478 layers.order();
58479
58480 layers.each(function(d) {
58481 var sel = d3.select(this);
58482 var className = d.className;
58483
58484 d.plotMethod(
58485 gd, plotinfo, d.cdModule, sel,
58486 transitionOpts, makeOnCompleteCallback
58487 );
58488
58489 // layers that allow `cliponaxis: false`
58490 if(constants.clipOnAxisFalseQuery.indexOf('.' + className) === -1) {
58491 Drawing.setClipUrl(sel, plotinfo.layerClipId, gd);
58492 }
58493 });
58494
58495 // call Scattergl.plot separately
58496 if(fullLayout._has('scattergl')) {
58497 _module = Registry.getModule('scattergl');
58498 cdModule = getModuleCalcData(cdSubplot, _module)[0];
58499 _module.plot(gd, plotinfo, cdModule);
58500 }
58501
58502 // stash "hot" selections for faster interaction on drag and scroll
58503 if(!gd._context.staticPlot) {
58504 if(plotinfo._hasClipOnAxisFalse) {
58505 plotinfo.clipOnAxisFalseTraces = plotinfo.plot
58506 .selectAll(constants.clipOnAxisFalseQuery.join(','))
58507 .selectAll('.trace');
58508 }
58509
58510 if(zoomScaleQueryParts.length) {
58511 var traces = plotinfo.plot
58512 .selectAll(zoomScaleQueryParts.join(','))
58513 .selectAll('.trace');
58514
58515 plotinfo.zoomScalePts = traces.selectAll('path.point');
58516 plotinfo.zoomScaleTxt = traces.selectAll('.textpoint');
58517 }
58518 }
58519}
58520
58521exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
58522 var oldPlots = oldFullLayout._plots || {};
58523 var newPlots = newFullLayout._plots || {};
58524 var oldSubplotList = oldFullLayout._subplots || {};
58525 var plotinfo;
58526 var i, k;
58527
58528 // when going from a large splom graph to something else,
58529 // we need to clear <g subplot> so that the new cartesian subplot
58530 // can have the correct layer ordering
58531 if(oldFullLayout._hasOnlyLargeSploms && !newFullLayout._hasOnlyLargeSploms) {
58532 for(k in oldPlots) {
58533 plotinfo = oldPlots[k];
58534 if(plotinfo.plotgroup) plotinfo.plotgroup.remove();
58535 }
58536 }
58537
58538 var hadGl = (oldFullLayout._has && oldFullLayout._has('gl'));
58539 var hasGl = (newFullLayout._has && newFullLayout._has('gl'));
58540
58541 if(hadGl && !hasGl) {
58542 for(k in oldPlots) {
58543 plotinfo = oldPlots[k];
58544 if(plotinfo._scene) plotinfo._scene.destroy();
58545 }
58546 }
58547
58548 // delete any titles we don't need anymore
58549 // check if axis list has changed, and if so clear old titles
58550 if(oldSubplotList.xaxis && oldSubplotList.yaxis) {
58551 var oldAxIDs = axisIds.listIds({_fullLayout: oldFullLayout});
58552 for(i = 0; i < oldAxIDs.length; i++) {
58553 var oldAxId = oldAxIDs[i];
58554 if(!newFullLayout[axisIds.id2name(oldAxId)]) {
58555 oldFullLayout._infolayer.selectAll('.g-' + oldAxId + 'title').remove();
58556 }
58557 }
58558 }
58559
58560 var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian'));
58561 var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian'));
58562
58563 if(hadCartesian && !hasCartesian) {
58564 // if we've gotten rid of all cartesian traces, remove all the subplot svg items
58565
58566 purgeSubplotLayers(oldFullLayout._cartesianlayer.selectAll('.subplot'), oldFullLayout);
58567 oldFullLayout._defs.selectAll('.axesclip').remove();
58568 delete oldFullLayout._axisConstraintGroups;
58569 } else if(oldSubplotList.cartesian) {
58570 // otherwise look for subplots we need to remove
58571
58572 for(i = 0; i < oldSubplotList.cartesian.length; i++) {
58573 var oldSubplotId = oldSubplotList.cartesian[i];
58574 if(!newPlots[oldSubplotId]) {
58575 var selector = '.' + oldSubplotId + ',.' + oldSubplotId + '-x,.' + oldSubplotId + '-y';
58576 oldFullLayout._cartesianlayer.selectAll(selector).remove();
58577 removeSubplotExtras(oldSubplotId, oldFullLayout);
58578 }
58579 }
58580 }
58581};
58582
58583exports.drawFramework = function(gd) {
58584 var fullLayout = gd._fullLayout;
58585 var subplotData = makeSubplotData(gd);
58586
58587 var subplotLayers = fullLayout._cartesianlayer.selectAll('.subplot')
58588 .data(subplotData, String);
58589
58590 subplotLayers.enter().append('g')
58591 .attr('class', function(d) { return 'subplot ' + d[0]; });
58592
58593 subplotLayers.order();
58594
58595 subplotLayers.exit()
58596 .call(purgeSubplotLayers, fullLayout);
58597
58598 subplotLayers.each(function(d) {
58599 var id = d[0];
58600 var plotinfo = fullLayout._plots[id];
58601
58602 plotinfo.plotgroup = d3.select(this);
58603 makeSubplotLayer(gd, plotinfo);
58604
58605 // make separate drag layers for each subplot,
58606 // but append them to paper rather than the plot groups,
58607 // so they end up on top of the rest
58608 plotinfo.draglayer = ensureSingle(fullLayout._draggers, 'g', id);
58609 });
58610};
58611
58612exports.rangePlot = function(gd, plotinfo, cdSubplot) {
58613 makeSubplotLayer(gd, plotinfo);
58614 plotOne(gd, plotinfo, cdSubplot);
58615 Plots.style(gd);
58616};
58617
58618function makeSubplotData(gd) {
58619 var fullLayout = gd._fullLayout;
58620 var ids = fullLayout._subplots.cartesian;
58621 var len = ids.length;
58622 var i, j, id, plotinfo, xa, ya;
58623
58624 // split 'regular' and 'overlaying' subplots
58625 var regulars = [];
58626 var overlays = [];
58627
58628 for(i = 0; i < len; i++) {
58629 id = ids[i];
58630 plotinfo = fullLayout._plots[id];
58631 xa = plotinfo.xaxis;
58632 ya = plotinfo.yaxis;
58633
58634 var xa2 = xa._mainAxis;
58635 var ya2 = ya._mainAxis;
58636 var mainplot = xa2._id + ya2._id;
58637 var mainplotinfo = fullLayout._plots[mainplot];
58638 plotinfo.overlays = [];
58639
58640 if(mainplot !== id && mainplotinfo) {
58641 plotinfo.mainplot = mainplot;
58642 plotinfo.mainplotinfo = mainplotinfo;
58643 overlays.push(id);
58644 } else {
58645 plotinfo.mainplot = undefined;
58646 plotinfo.mainPlotinfo = undefined;
58647 regulars.push(id);
58648 }
58649 }
58650
58651 // fill in list of overlaying subplots in 'main plot'
58652 for(i = 0; i < overlays.length; i++) {
58653 id = overlays[i];
58654 plotinfo = fullLayout._plots[id];
58655 plotinfo.mainplotinfo.overlays.push(plotinfo);
58656 }
58657
58658 // put 'regular' subplot data before 'overlaying'
58659 var subplotIds = regulars.concat(overlays);
58660 var subplotData = new Array(len);
58661
58662 for(i = 0; i < len; i++) {
58663 id = subplotIds[i];
58664 plotinfo = fullLayout._plots[id];
58665 xa = plotinfo.xaxis;
58666 ya = plotinfo.yaxis;
58667
58668 // use info about axis layer and overlaying pattern
58669 // to clean what need to be cleaned up in exit selection
58670 var d = [id, xa.layer, ya.layer, xa.overlaying || '', ya.overlaying || ''];
58671 for(j = 0; j < plotinfo.overlays.length; j++) {
58672 d.push(plotinfo.overlays[j].id);
58673 }
58674 subplotData[i] = d;
58675 }
58676
58677 return subplotData;
58678}
58679
58680function makeSubplotLayer(gd, plotinfo) {
58681 var plotgroup = plotinfo.plotgroup;
58682 var id = plotinfo.id;
58683 var xLayer = constants.layerValue2layerClass[plotinfo.xaxis.layer];
58684 var yLayer = constants.layerValue2layerClass[plotinfo.yaxis.layer];
58685 var hasOnlyLargeSploms = gd._fullLayout._hasOnlyLargeSploms;
58686
58687 if(!plotinfo.mainplot) {
58688 if(hasOnlyLargeSploms) {
58689 // TODO could do even better
58690 // - we don't need plot (but we would have to mock it in lsInner
58691 // and other places
58692 // - we don't (x|y)lines and (x|y)axislayer for most subplots
58693 // usually just the bottom x and left y axes.
58694 plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
58695 plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
58696 plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above');
58697 plotinfo.yaxislayer = ensureSingle(plotgroup, 'g', 'yaxislayer-above');
58698 } else {
58699 var backLayer = ensureSingle(plotgroup, 'g', 'layer-subplot');
58700 plotinfo.shapelayer = ensureSingle(backLayer, 'g', 'shapelayer');
58701 plotinfo.imagelayer = ensureSingle(backLayer, 'g', 'imagelayer');
58702
58703 plotinfo.gridlayer = ensureSingle(plotgroup, 'g', 'gridlayer');
58704 plotinfo.zerolinelayer = ensureSingle(plotgroup, 'g', 'zerolinelayer');
58705
58706 ensureSingle(plotgroup, 'path', 'xlines-below');
58707 ensureSingle(plotgroup, 'path', 'ylines-below');
58708 plotinfo.overlinesBelow = ensureSingle(plotgroup, 'g', 'overlines-below');
58709
58710 ensureSingle(plotgroup, 'g', 'xaxislayer-below');
58711 ensureSingle(plotgroup, 'g', 'yaxislayer-below');
58712 plotinfo.overaxesBelow = ensureSingle(plotgroup, 'g', 'overaxes-below');
58713
58714 plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot');
58715 plotinfo.overplot = ensureSingle(plotgroup, 'g', 'overplot');
58716
58717 plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
58718 plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
58719 plotinfo.overlinesAbove = ensureSingle(plotgroup, 'g', 'overlines-above');
58720
58721 ensureSingle(plotgroup, 'g', 'xaxislayer-above');
58722 ensureSingle(plotgroup, 'g', 'yaxislayer-above');
58723 plotinfo.overaxesAbove = ensureSingle(plotgroup, 'g', 'overaxes-above');
58724
58725 // set refs to correct layers as determined by 'axis.layer'
58726 plotinfo.xlines = plotgroup.select('.xlines-' + xLayer);
58727 plotinfo.ylines = plotgroup.select('.ylines-' + yLayer);
58728 plotinfo.xaxislayer = plotgroup.select('.xaxislayer-' + xLayer);
58729 plotinfo.yaxislayer = plotgroup.select('.yaxislayer-' + yLayer);
58730 }
58731 } else {
58732 var mainplotinfo = plotinfo.mainplotinfo;
58733 var mainplotgroup = mainplotinfo.plotgroup;
58734 var xId = id + '-x';
58735 var yId = id + '-y';
58736
58737 // now make the components of overlaid subplots
58738 // overlays don't have backgrounds, and append all
58739 // their other components to the corresponding
58740 // extra groups of their main plots.
58741
58742 plotinfo.gridlayer = mainplotinfo.gridlayer;
58743 plotinfo.zerolinelayer = mainplotinfo.zerolinelayer;
58744
58745 ensureSingle(mainplotinfo.overlinesBelow, 'path', xId);
58746 ensureSingle(mainplotinfo.overlinesBelow, 'path', yId);
58747 ensureSingle(mainplotinfo.overaxesBelow, 'g', xId);
58748 ensureSingle(mainplotinfo.overaxesBelow, 'g', yId);
58749
58750 plotinfo.plot = ensureSingle(mainplotinfo.overplot, 'g', id);
58751
58752 ensureSingle(mainplotinfo.overlinesAbove, 'path', xId);
58753 ensureSingle(mainplotinfo.overlinesAbove, 'path', yId);
58754 ensureSingle(mainplotinfo.overaxesAbove, 'g', xId);
58755 ensureSingle(mainplotinfo.overaxesAbove, 'g', yId);
58756
58757 // set refs to correct layers as determined by 'abovetraces'
58758 plotinfo.xlines = mainplotgroup.select('.overlines-' + xLayer).select('.' + xId);
58759 plotinfo.ylines = mainplotgroup.select('.overlines-' + yLayer).select('.' + yId);
58760 plotinfo.xaxislayer = mainplotgroup.select('.overaxes-' + xLayer).select('.' + xId);
58761 plotinfo.yaxislayer = mainplotgroup.select('.overaxes-' + yLayer).select('.' + yId);
58762 }
58763
58764 // common attributes for all subplots, overlays or not
58765
58766 if(!hasOnlyLargeSploms) {
58767 ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id);
58768 ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id);
58769 plotinfo.gridlayer.selectAll('g')
58770 .map(function(d) { return d[0]; })
58771 .sort(axisIds.idSort);
58772 }
58773
58774 plotinfo.xlines
58775 .style('fill', 'none')
58776 .classed('crisp', true);
58777
58778 plotinfo.ylines
58779 .style('fill', 'none')
58780 .classed('crisp', true);
58781}
58782
58783function purgeSubplotLayers(layers, fullLayout) {
58784 if(!layers) return;
58785
58786 var overlayIdsToRemove = {};
58787
58788 layers.each(function(d) {
58789 var id = d[0];
58790 var plotgroup = d3.select(this);
58791
58792 plotgroup.remove();
58793 removeSubplotExtras(id, fullLayout);
58794 overlayIdsToRemove[id] = true;
58795
58796 // do not remove individual axis <clipPath>s here
58797 // as other subplots may need them
58798 });
58799
58800 // must remove overlaid subplot trace layers 'manually'
58801
58802 for(var k in fullLayout._plots) {
58803 var subplotInfo = fullLayout._plots[k];
58804 var overlays = subplotInfo.overlays || [];
58805
58806 for(var j = 0; j < overlays.length; j++) {
58807 var overlayInfo = overlays[j];
58808
58809 if(overlayIdsToRemove[overlayInfo.id]) {
58810 overlayInfo.plot.selectAll('.trace').remove();
58811 }
58812 }
58813 }
58814}
58815
58816function removeSubplotExtras(subplotId, fullLayout) {
58817 fullLayout._draggers.selectAll('g.' + subplotId).remove();
58818 fullLayout._defs.select('#clip' + fullLayout._uid + subplotId + 'plot').remove();
58819}
58820
58821exports.toSVG = function(gd) {
58822 var imageRoot = gd._fullLayout._glimages;
58823 var root = d3.select(gd).selectAll('.svg-container');
58824 var canvases = root.filter(function(d, i) {return i === root.size() - 1;})
58825 .selectAll('.gl-canvas-context, .gl-canvas-focus');
58826
58827 function canvasToImage() {
58828 var canvas = this;
58829 var imageData = canvas.toDataURL('image/png');
58830 var image = imageRoot.append('svg:image');
58831
58832 image.attr({
58833 xmlns: xmlnsNamespaces.svg,
58834 'xlink:href': imageData,
58835 preserveAspectRatio: 'none',
58836 x: 0,
58837 y: 0,
58838 width: canvas.width,
58839 height: canvas.height
58840 });
58841 }
58842
58843 canvases.each(canvasToImage);
58844};
58845
58846exports.updateFx = _dereq_('./graph_interact').updateFx;
58847
58848},{"../../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){
58849/**
58850* Copyright 2012-2020, Plotly, Inc.
58851* All rights reserved.
58852*
58853* This source code is licensed under the MIT license found in the
58854* LICENSE file in the root directory of this source tree.
58855*/
58856
58857'use strict';
58858
58859var fontAttrs = _dereq_('../font_attributes');
58860var colorAttrs = _dereq_('../../components/color/attributes');
58861var dash = _dereq_('../../components/drawing/attributes').dash;
58862var extendFlat = _dereq_('../../lib/extend').extendFlat;
58863var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
58864
58865var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
58866var DATE_FORMAT_LINK = _dereq_('../../constants/docs').DATE_FORMAT_LINK;
58867var ONEDAY = _dereq_('../../constants/numerical').ONEDAY;
58868var constants = _dereq_('./constants');
58869var HOUR = constants.HOUR_PATTERN;
58870var DAY_OF_WEEK = constants.WEEKDAY_PATTERN;
58871
58872module.exports = {
58873 visible: {
58874 valType: 'boolean',
58875
58876 editType: 'plot',
58877
58878 },
58879 color: {
58880 valType: 'color',
58881 dflt: colorAttrs.defaultLine,
58882
58883 editType: 'ticks',
58884
58885 },
58886 title: {
58887 text: {
58888 valType: 'string',
58889
58890 editType: 'ticks',
58891
58892 },
58893 font: fontAttrs({
58894 editType: 'ticks',
58895
58896 }),
58897 standoff: {
58898 valType: 'number',
58899
58900 min: 0,
58901 editType: 'ticks',
58902
58903 },
58904 editType: 'ticks'
58905 },
58906 type: {
58907 valType: 'enumerated',
58908 // '-' means we haven't yet run autotype or couldn't find any data
58909 // it gets turned into linear in gd._fullLayout but not copied back
58910 // to gd.data like the others are.
58911 values: ['-', 'linear', 'log', 'date', 'category', 'multicategory'],
58912 dflt: '-',
58913
58914 editType: 'calc',
58915 // we forget when an axis has been autotyped, just writing the auto
58916 // value back to the input - so it doesn't make sense to template this.
58917 // Note: we do NOT prohibit this in `coerce`, so if someone enters a
58918 // type in the template explicitly it will be honored as the default.
58919 _noTemplating: true,
58920
58921 },
58922 autorange: {
58923 valType: 'enumerated',
58924 values: [true, false, 'reversed'],
58925 dflt: true,
58926
58927 editType: 'axrange',
58928 impliedEdits: {'range[0]': undefined, 'range[1]': undefined},
58929
58930 },
58931 rangemode: {
58932 valType: 'enumerated',
58933 values: ['normal', 'tozero', 'nonnegative'],
58934 dflt: 'normal',
58935
58936 editType: 'plot',
58937
58938 },
58939 range: {
58940 valType: 'info_array',
58941
58942 items: [
58943 {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true},
58944 {valType: 'any', editType: 'axrange', impliedEdits: {'^autorange': false}, anim: true}
58945 ],
58946 editType: 'axrange',
58947 impliedEdits: {'autorange': false},
58948 anim: true,
58949
58950 },
58951 fixedrange: {
58952 valType: 'boolean',
58953 dflt: false,
58954
58955 editType: 'calc',
58956
58957 },
58958 // scaleanchor: not used directly, just put here for reference
58959 // values are any opposite-letter axis id
58960 scaleanchor: {
58961 valType: 'enumerated',
58962 values: [
58963 constants.idRegex.x.toString(),
58964 constants.idRegex.y.toString()
58965 ],
58966
58967 editType: 'plot',
58968
58969 },
58970 scaleratio: {
58971 valType: 'number',
58972 min: 0,
58973 dflt: 1,
58974
58975 editType: 'plot',
58976
58977 },
58978 constrain: {
58979 valType: 'enumerated',
58980 values: ['range', 'domain'],
58981 dflt: 'range',
58982
58983 editType: 'plot',
58984
58985 },
58986 // constraintoward: not used directly, just put here for reference
58987 constraintoward: {
58988 valType: 'enumerated',
58989 values: ['left', 'center', 'right', 'top', 'middle', 'bottom'],
58990
58991 editType: 'plot',
58992
58993 },
58994 matches: {
58995 valType: 'enumerated',
58996 values: [
58997 constants.idRegex.x.toString(),
58998 constants.idRegex.y.toString()
58999 ],
59000
59001 editType: 'calc',
59002
59003 },
59004
59005 rangebreaks: templatedArray('rangebreak', {
59006 enabled: {
59007 valType: 'boolean',
59008
59009 dflt: true,
59010 editType: 'calc',
59011
59012 },
59013
59014 bounds: {
59015 valType: 'info_array',
59016
59017 items: [
59018 {valType: 'any', editType: 'calc'},
59019 {valType: 'any', editType: 'calc'}
59020 ],
59021 editType: 'calc',
59022
59023 },
59024
59025 pattern: {
59026 valType: 'enumerated',
59027 values: [DAY_OF_WEEK, HOUR, ''],
59028
59029 editType: 'calc',
59030
59031 },
59032
59033 values: {
59034 valType: 'info_array',
59035 freeLength: true,
59036
59037 editType: 'calc',
59038 items: {
59039 valType: 'any',
59040 editType: 'calc'
59041 },
59042
59043 },
59044 dvalue: {
59045 // TODO could become 'any' to add support for 'months', 'years'
59046 valType: 'number',
59047
59048 editType: 'calc',
59049 min: 0,
59050 dflt: ONEDAY,
59051
59052 },
59053
59054 /*
59055 gap: {
59056 valType: 'number',
59057 min: 0,
59058 dflt: 0, // for *date* axes, maybe something else for *linear*
59059 editType: 'calc',
59060
59061
59062 },
59063 gapmode: {
59064 valType: 'enumerated',
59065 values: ['pixels', 'fraction'],
59066 dflt: 'pixels',
59067 editType: 'calc',
59068
59069
59070 },
59071 */
59072
59073 // To complete https://github.com/plotly/plotly.js/issues/4210
59074 // we additionally need `gap` and make this work on *linear*, and
59075 // possibly all other cartesian axis types. We possibly would also need
59076 // some style attributes controlling the zig-zag on the corresponding
59077 // axis.
59078
59079 editType: 'calc'
59080 }),
59081
59082 // ticks
59083 tickmode: {
59084 valType: 'enumerated',
59085 values: ['auto', 'linear', 'array'],
59086
59087 editType: 'ticks',
59088 impliedEdits: {tick0: undefined, dtick: undefined},
59089
59090 },
59091 nticks: {
59092 valType: 'integer',
59093 min: 0,
59094 dflt: 0,
59095
59096 editType: 'ticks',
59097
59098 },
59099 tick0: {
59100 valType: 'any',
59101
59102 editType: 'ticks',
59103 impliedEdits: {tickmode: 'linear'},
59104
59105 },
59106 dtick: {
59107 valType: 'any',
59108
59109 editType: 'ticks',
59110 impliedEdits: {tickmode: 'linear'},
59111
59112 },
59113 tickvals: {
59114 valType: 'data_array',
59115 editType: 'ticks',
59116
59117 },
59118 ticktext: {
59119 valType: 'data_array',
59120 editType: 'ticks',
59121
59122 },
59123 ticks: {
59124 valType: 'enumerated',
59125 values: ['outside', 'inside', ''],
59126
59127 editType: 'ticks',
59128
59129 },
59130 tickson: {
59131 valType: 'enumerated',
59132 values: ['labels', 'boundaries'],
59133
59134 dflt: 'labels',
59135 editType: 'ticks',
59136
59137 },
59138 mirror: {
59139 valType: 'enumerated',
59140 values: [true, 'ticks', false, 'all', 'allticks'],
59141 dflt: false,
59142
59143 editType: 'ticks+layoutstyle',
59144
59145 },
59146 ticklen: {
59147 valType: 'number',
59148 min: 0,
59149 dflt: 5,
59150
59151 editType: 'ticks',
59152
59153 },
59154 tickwidth: {
59155 valType: 'number',
59156 min: 0,
59157 dflt: 1,
59158
59159 editType: 'ticks',
59160
59161 },
59162 tickcolor: {
59163 valType: 'color',
59164 dflt: colorAttrs.defaultLine,
59165
59166 editType: 'ticks',
59167
59168 },
59169 showticklabels: {
59170 valType: 'boolean',
59171 dflt: true,
59172
59173 editType: 'ticks',
59174
59175 },
59176 automargin: {
59177 valType: 'boolean',
59178 dflt: false,
59179
59180 editType: 'ticks',
59181
59182 },
59183 showspikes: {
59184 valType: 'boolean',
59185 dflt: false,
59186
59187 editType: 'modebar',
59188
59189 },
59190 spikecolor: {
59191 valType: 'color',
59192 dflt: null,
59193
59194 editType: 'none',
59195
59196 },
59197 spikethickness: {
59198 valType: 'number',
59199 dflt: 3,
59200
59201 editType: 'none',
59202
59203 },
59204 spikedash: extendFlat({}, dash, {dflt: 'dash', editType: 'none'}),
59205 spikemode: {
59206 valType: 'flaglist',
59207 flags: ['toaxis', 'across', 'marker'],
59208
59209 dflt: 'toaxis',
59210 editType: 'none',
59211
59212 },
59213 spikesnap: {
59214 valType: 'enumerated',
59215 values: ['data', 'cursor', 'hovered data'],
59216 dflt: 'data',
59217
59218 editType: 'none',
59219
59220 },
59221 tickfont: fontAttrs({
59222 editType: 'ticks',
59223
59224 }),
59225 tickangle: {
59226 valType: 'angle',
59227 dflt: 'auto',
59228
59229 editType: 'ticks',
59230
59231 },
59232 tickprefix: {
59233 valType: 'string',
59234 dflt: '',
59235
59236 editType: 'ticks',
59237
59238 },
59239 showtickprefix: {
59240 valType: 'enumerated',
59241 values: ['all', 'first', 'last', 'none'],
59242 dflt: 'all',
59243
59244 editType: 'ticks',
59245
59246 },
59247 ticksuffix: {
59248 valType: 'string',
59249 dflt: '',
59250
59251 editType: 'ticks',
59252
59253 },
59254 showticksuffix: {
59255 valType: 'enumerated',
59256 values: ['all', 'first', 'last', 'none'],
59257 dflt: 'all',
59258
59259 editType: 'ticks',
59260
59261 },
59262 showexponent: {
59263 valType: 'enumerated',
59264 values: ['all', 'first', 'last', 'none'],
59265 dflt: 'all',
59266
59267 editType: 'ticks',
59268
59269 },
59270 exponentformat: {
59271 valType: 'enumerated',
59272 values: ['none', 'e', 'E', 'power', 'SI', 'B'],
59273 dflt: 'B',
59274
59275 editType: 'ticks',
59276
59277 },
59278 separatethousands: {
59279 valType: 'boolean',
59280 dflt: false,
59281
59282 editType: 'ticks',
59283
59284 },
59285 tickformat: {
59286 valType: 'string',
59287 dflt: '',
59288
59289 editType: 'ticks',
59290
59291 },
59292 tickformatstops: templatedArray('tickformatstop', {
59293 enabled: {
59294 valType: 'boolean',
59295
59296 dflt: true,
59297 editType: 'ticks',
59298
59299 },
59300 dtickrange: {
59301 valType: 'info_array',
59302
59303 items: [
59304 {valType: 'any', editType: 'ticks'},
59305 {valType: 'any', editType: 'ticks'}
59306 ],
59307 editType: 'ticks',
59308
59309 },
59310 value: {
59311 valType: 'string',
59312 dflt: '',
59313
59314 editType: 'ticks',
59315
59316 },
59317 editType: 'ticks'
59318 }),
59319 hoverformat: {
59320 valType: 'string',
59321 dflt: '',
59322
59323 editType: 'none',
59324
59325 },
59326 // lines and grids
59327 showline: {
59328 valType: 'boolean',
59329 dflt: false,
59330
59331 editType: 'ticks+layoutstyle',
59332
59333 },
59334 linecolor: {
59335 valType: 'color',
59336 dflt: colorAttrs.defaultLine,
59337
59338 editType: 'layoutstyle',
59339
59340 },
59341 linewidth: {
59342 valType: 'number',
59343 min: 0,
59344 dflt: 1,
59345
59346 editType: 'ticks+layoutstyle',
59347
59348 },
59349 showgrid: {
59350 valType: 'boolean',
59351
59352 editType: 'ticks',
59353
59354 },
59355 gridcolor: {
59356 valType: 'color',
59357 dflt: colorAttrs.lightLine,
59358
59359 editType: 'ticks',
59360
59361 },
59362 gridwidth: {
59363 valType: 'number',
59364 min: 0,
59365 dflt: 1,
59366
59367 editType: 'ticks',
59368
59369 },
59370 zeroline: {
59371 valType: 'boolean',
59372
59373 editType: 'ticks',
59374
59375 },
59376 zerolinecolor: {
59377 valType: 'color',
59378 dflt: colorAttrs.defaultLine,
59379
59380 editType: 'ticks',
59381
59382 },
59383 zerolinewidth: {
59384 valType: 'number',
59385 dflt: 1,
59386
59387 editType: 'ticks',
59388
59389 },
59390
59391 showdividers: {
59392 valType: 'boolean',
59393 dflt: true,
59394
59395 editType: 'ticks',
59396
59397 },
59398 dividercolor: {
59399 valType: 'color',
59400 dflt: colorAttrs.defaultLine,
59401
59402 editType: 'ticks',
59403
59404 },
59405 dividerwidth: {
59406 valType: 'number',
59407 dflt: 1,
59408
59409 editType: 'ticks',
59410
59411 },
59412 // TODO dividerlen: that would override "to label base" length?
59413
59414 // positioning attributes
59415 // anchor: not used directly, just put here for reference
59416 // values are any opposite-letter axis id
59417 anchor: {
59418 valType: 'enumerated',
59419 values: [
59420 'free',
59421 constants.idRegex.x.toString(),
59422 constants.idRegex.y.toString()
59423 ],
59424
59425 editType: 'plot',
59426
59427 },
59428 // side: not used directly, as values depend on direction
59429 // values are top, bottom for x axes, and left, right for y
59430 side: {
59431 valType: 'enumerated',
59432 values: ['top', 'bottom', 'left', 'right'],
59433
59434 editType: 'plot',
59435
59436 },
59437 // overlaying: not used directly, just put here for reference
59438 // values are false and any other same-letter axis id that's not
59439 // itself overlaying anything
59440 overlaying: {
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 layer: {
59452 valType: 'enumerated',
59453 values: ['above traces', 'below traces'],
59454 dflt: 'above traces',
59455
59456 editType: 'plot',
59457
59458 },
59459 domain: {
59460 valType: 'info_array',
59461
59462 items: [
59463 {valType: 'number', min: 0, max: 1, editType: 'plot'},
59464 {valType: 'number', min: 0, max: 1, editType: 'plot'}
59465 ],
59466 dflt: [0, 1],
59467 editType: 'plot',
59468
59469 },
59470 position: {
59471 valType: 'number',
59472 min: 0,
59473 max: 1,
59474 dflt: 0,
59475
59476 editType: 'plot',
59477
59478 },
59479 categoryorder: {
59480 valType: 'enumerated',
59481 values: [
59482 'trace', 'category ascending', 'category descending', 'array',
59483 'total ascending', 'total descending',
59484 'min ascending', 'min descending',
59485 'max ascending', 'max descending',
59486 'sum ascending', 'sum descending',
59487 'mean ascending', 'mean descending',
59488 'median ascending', 'median descending'
59489 ],
59490 dflt: 'trace',
59491
59492 editType: 'calc',
59493
59494 },
59495 categoryarray: {
59496 valType: 'data_array',
59497
59498 editType: 'calc',
59499
59500 },
59501 uirevision: {
59502 valType: 'any',
59503
59504 editType: 'none',
59505
59506 },
59507 editType: 'calc',
59508
59509 _deprecated: {
59510 autotick: {
59511 valType: 'boolean',
59512
59513 editType: 'ticks',
59514
59515 },
59516 title: {
59517 valType: 'string',
59518
59519 editType: 'ticks',
59520
59521 },
59522 titlefont: fontAttrs({
59523 editType: 'ticks',
59524
59525 })
59526 }
59527};
59528
59529},{"../../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){
59530/**
59531* Copyright 2012-2020, Plotly, Inc.
59532* All rights reserved.
59533*
59534* This source code is licensed under the MIT license found in the
59535* LICENSE file in the root directory of this source tree.
59536*/
59537
59538
59539'use strict';
59540
59541var Lib = _dereq_('../../lib');
59542var Color = _dereq_('../../components/color');
59543var isUnifiedHover = _dereq_('../../components/fx/helpers').isUnifiedHover;
59544var handleHoverModeDefaults = _dereq_('../../components/fx/hovermode_defaults');
59545var Template = _dereq_('../../plot_api/plot_template');
59546var basePlotLayoutAttributes = _dereq_('../layout_attributes');
59547
59548var layoutAttributes = _dereq_('./layout_attributes');
59549var handleTypeDefaults = _dereq_('./type_defaults');
59550var handleAxisDefaults = _dereq_('./axis_defaults');
59551var handleConstraintDefaults = _dereq_('./constraints').handleConstraintDefaults;
59552var handlePositionDefaults = _dereq_('./position_defaults');
59553
59554var axisIds = _dereq_('./axis_ids');
59555var id2name = axisIds.id2name;
59556var name2id = axisIds.name2id;
59557
59558var AX_ID_PATTERN = _dereq_('./constants').AX_ID_PATTERN;
59559
59560var Registry = _dereq_('../../registry');
59561var traceIs = Registry.traceIs;
59562var getComponentMethod = Registry.getComponentMethod;
59563
59564function appendList(cont, k, item) {
59565 if(Array.isArray(cont[k])) cont[k].push(item);
59566 else cont[k] = [item];
59567}
59568
59569module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
59570 var ax2traces = {};
59571 var xaMayHide = {};
59572 var yaMayHide = {};
59573 var xaMustDisplay = {};
59574 var yaMustDisplay = {};
59575 var yaMustNotReverse = {};
59576 var yaMayReverse = {};
59577 var axHasImage = {};
59578 var outerTicks = {};
59579 var noGrids = {};
59580 var i, j;
59581
59582 // look for axes in the data
59583 for(i = 0; i < fullData.length; i++) {
59584 var trace = fullData[i];
59585 if(!traceIs(trace, 'cartesian') && !traceIs(trace, 'gl2d')) continue;
59586
59587 var xaName;
59588 if(trace.xaxis) {
59589 xaName = id2name(trace.xaxis);
59590 appendList(ax2traces, xaName, trace);
59591 } else if(trace.xaxes) {
59592 for(j = 0; j < trace.xaxes.length; j++) {
59593 appendList(ax2traces, id2name(trace.xaxes[j]), trace);
59594 }
59595 }
59596
59597 var yaName;
59598 if(trace.yaxis) {
59599 yaName = id2name(trace.yaxis);
59600 appendList(ax2traces, yaName, trace);
59601 } else if(trace.yaxes) {
59602 for(j = 0; j < trace.yaxes.length; j++) {
59603 appendList(ax2traces, id2name(trace.yaxes[j]), trace);
59604 }
59605 }
59606
59607 // logic for funnels
59608 if(trace.type === 'funnel') {
59609 if(trace.orientation === 'h') {
59610 if(xaName) xaMayHide[xaName] = true;
59611 if(yaName) yaMayReverse[yaName] = true;
59612 } else {
59613 if(yaName) yaMayHide[yaName] = true;
59614 }
59615 } else if(trace.type === 'image') {
59616 if(yaName) axHasImage[yaName] = true;
59617 if(xaName) axHasImage[xaName] = true;
59618 } else {
59619 if(yaName) {
59620 yaMustDisplay[yaName] = true;
59621 yaMustNotReverse[yaName] = true;
59622 }
59623
59624 if(!traceIs(trace, 'carpet') || (trace.type === 'carpet' && !trace._cheater)) {
59625 if(xaName) xaMustDisplay[xaName] = true;
59626 }
59627 }
59628
59629 // Two things trigger axis visibility:
59630 // 1. is not carpet
59631 // 2. carpet that's not cheater
59632
59633 // The above check for definitely-not-cheater is not adequate. This
59634 // second list tracks which axes *could* be a cheater so that the
59635 // full condition triggering hiding is:
59636 // *could* be a cheater and *is not definitely visible*
59637 if(trace.type === 'carpet' && trace._cheater) {
59638 if(xaName) xaMayHide[xaName] = true;
59639 }
59640
59641 // check for default formatting tweaks
59642 if(traceIs(trace, '2dMap')) {
59643 outerTicks[xaName] = true;
59644 outerTicks[yaName] = true;
59645 }
59646
59647 if(traceIs(trace, 'oriented')) {
59648 var positionAxis = trace.orientation === 'h' ? yaName : xaName;
59649 noGrids[positionAxis] = true;
59650 }
59651 }
59652
59653 var subplots = layoutOut._subplots;
59654 var xIds = subplots.xaxis;
59655 var yIds = subplots.yaxis;
59656 var xNames = Lib.simpleMap(xIds, id2name);
59657 var yNames = Lib.simpleMap(yIds, id2name);
59658 var axNames = xNames.concat(yNames);
59659
59660 // plot_bgcolor only makes sense if there's a (2D) plot!
59661 // TODO: bgcolor for each subplot, to inherit from the main one
59662 var plotBgColor = Color.background;
59663 if(xIds.length && yIds.length) {
59664 plotBgColor = Lib.coerce(layoutIn, layoutOut, basePlotLayoutAttributes, 'plot_bgcolor');
59665 }
59666
59667 var bgColor = Color.combine(plotBgColor, layoutOut.paper_bgcolor);
59668
59669 // name of single axis (e.g. 'xaxis', 'yaxis2')
59670 var axName;
59671 // id of single axis (e.g. 'y', 'x5')
59672 var axId;
59673 // 'x' or 'y'
59674 var axLetter;
59675 // input layout axis container
59676 var axLayoutIn;
59677 // full layout axis container
59678 var axLayoutOut;
59679
59680 function newAxLayoutOut() {
59681 var traces = ax2traces[axName] || [];
59682 axLayoutOut._traceIndices = traces.map(function(t) { return t._expandedIndex; });
59683 axLayoutOut._annIndices = [];
59684 axLayoutOut._shapeIndices = [];
59685 axLayoutOut._imgIndices = [];
59686 axLayoutOut._subplotsWith = [];
59687 axLayoutOut._counterAxes = [];
59688 axLayoutOut._name = axLayoutOut._attr = axName;
59689 axLayoutOut._id = axId;
59690 }
59691
59692 function coerce(attr, dflt) {
59693 return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
59694 }
59695
59696 function coerce2(attr, dflt) {
59697 return Lib.coerce2(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
59698 }
59699
59700 function getCounterAxes(axLetter) {
59701 return (axLetter === 'x') ? yIds : xIds;
59702 }
59703
59704 function getOverlayableAxes(axLetter, axName) {
59705 var list = (axLetter === 'x') ? xNames : yNames;
59706 var out = [];
59707
59708 for(var j = 0; j < list.length; j++) {
59709 var axName2 = list[j];
59710
59711 if(axName2 !== axName && !(layoutIn[axName2] || {}).overlaying) {
59712 out.push(name2id(axName2));
59713 }
59714 }
59715
59716 return out;
59717 }
59718
59719 // list of available counter axis names
59720 var counterAxes = {x: getCounterAxes('x'), y: getCounterAxes('y')};
59721 // list of all x AND y axis ids
59722 var allAxisIds = counterAxes.x.concat(counterAxes.y);
59723 // lookup and list of axis ids that axes in axNames have a reference to,
59724 // even though they are missing from allAxisIds
59725 var missingMatchedAxisIdsLookup = {};
59726 var missingMatchedAxisIds = [];
59727
59728 // fill in 'missing' axis lookup when an axis is set to match an axis
59729 // not part of the allAxisIds list, save axis type so that we can propagate
59730 // it to the missing axes
59731 function addMissingMatchedAxis() {
59732 var matchesIn = axLayoutIn.matches;
59733 if(AX_ID_PATTERN.test(matchesIn) && allAxisIds.indexOf(matchesIn) === -1) {
59734 missingMatchedAxisIdsLookup[matchesIn] = axLayoutIn.type;
59735 missingMatchedAxisIds = Object.keys(missingMatchedAxisIdsLookup);
59736 }
59737 }
59738
59739 var hovermode = handleHoverModeDefaults(layoutIn, layoutOut, fullData);
59740 var unifiedHover = isUnifiedHover(hovermode);
59741
59742 // first pass creates the containers, determines types, and handles most of the settings
59743 for(i = 0; i < axNames.length; i++) {
59744 axName = axNames[i];
59745 axId = name2id(axName);
59746 axLetter = axName.charAt(0);
59747
59748 if(!Lib.isPlainObject(layoutIn[axName])) {
59749 layoutIn[axName] = {};
59750 }
59751
59752 axLayoutIn = layoutIn[axName];
59753 axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
59754 newAxLayoutOut();
59755
59756 var visibleDflt =
59757 (axLetter === 'x' && !xaMustDisplay[axName] && xaMayHide[axName]) ||
59758 (axLetter === 'y' && !yaMustDisplay[axName] && yaMayHide[axName]);
59759
59760 var reverseDflt =
59761 (axLetter === 'y' &&
59762 (
59763 (!yaMustNotReverse[axName] && yaMayReverse[axName]) ||
59764 axHasImage[axName]
59765 ));
59766
59767 var defaultOptions = {
59768 letter: axLetter,
59769 font: layoutOut.font,
59770 outerTicks: outerTicks[axName],
59771 showGrid: !noGrids[axName],
59772 data: ax2traces[axName] || [],
59773 bgColor: bgColor,
59774 calendar: layoutOut.calendar,
59775 automargin: true,
59776 visibleDflt: visibleDflt,
59777 reverseDflt: reverseDflt,
59778 splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
59779 };
59780
59781 coerce('uirevision', layoutOut.uirevision);
59782
59783 handleTypeDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions);
59784 handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);
59785
59786 var unifiedSpike = unifiedHover && axLetter === hovermode.charAt(0);
59787 var spikecolor = coerce2('spikecolor', unifiedHover ? axLayoutOut.color : undefined);
59788 var spikethickness = coerce2('spikethickness', unifiedHover ? 1.5 : undefined);
59789 var spikedash = coerce2('spikedash', unifiedHover ? 'dot' : undefined);
59790 var spikemode = coerce2('spikemode', unifiedHover ? 'across' : undefined);
59791 var spikesnap = coerce2('spikesnap', unifiedHover ? 'hovered data' : undefined);
59792 var showSpikes = coerce('showspikes', !!unifiedSpike || !!spikecolor || !!spikethickness || !!spikedash || !!spikemode || !!spikesnap);
59793
59794 if(!showSpikes) {
59795 delete axLayoutOut.spikecolor;
59796 delete axLayoutOut.spikethickness;
59797 delete axLayoutOut.spikedash;
59798 delete axLayoutOut.spikemode;
59799 delete axLayoutOut.spikesnap;
59800 }
59801
59802 handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
59803 letter: axLetter,
59804 counterAxes: counterAxes[axLetter],
59805 overlayableAxes: getOverlayableAxes(axLetter, axName),
59806 grid: layoutOut.grid
59807 });
59808
59809 coerce('title.standoff');
59810
59811 addMissingMatchedAxis();
59812
59813 axLayoutOut._input = axLayoutIn;
59814 }
59815
59816 // coerce the 'missing' axes
59817 i = 0;
59818 while(i < missingMatchedAxisIds.length) {
59819 axId = missingMatchedAxisIds[i++];
59820 axName = id2name(axId);
59821 axLetter = axName.charAt(0);
59822
59823 if(!Lib.isPlainObject(layoutIn[axName])) {
59824 layoutIn[axName] = {};
59825 }
59826
59827 axLayoutIn = layoutIn[axName];
59828 axLayoutOut = Template.newContainer(layoutOut, axName, axLetter + 'axis');
59829 newAxLayoutOut();
59830
59831 var defaultOptions2 = {
59832 letter: axLetter,
59833 font: layoutOut.font,
59834 outerTicks: outerTicks[axName],
59835 showGrid: !noGrids[axName],
59836 data: [],
59837 bgColor: bgColor,
59838 calendar: layoutOut.calendar,
59839 automargin: true,
59840 visibleDflt: false,
59841 reverseDflt: false,
59842 splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
59843 };
59844
59845 coerce('uirevision', layoutOut.uirevision);
59846
59847 axLayoutOut.type = missingMatchedAxisIdsLookup[axId] || 'linear';
59848
59849 handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions2, layoutOut);
59850
59851 handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
59852 letter: axLetter,
59853 counterAxes: counterAxes[axLetter],
59854 overlayableAxes: getOverlayableAxes(axLetter, axName),
59855 grid: layoutOut.grid
59856 });
59857
59858 coerce('fixedrange');
59859
59860 addMissingMatchedAxis();
59861
59862 axLayoutOut._input = axLayoutIn;
59863 }
59864
59865 // quick second pass for range slider and selector defaults
59866 var rangeSliderDefaults = getComponentMethod('rangeslider', 'handleDefaults');
59867 var rangeSelectorDefaults = getComponentMethod('rangeselector', 'handleDefaults');
59868
59869 for(i = 0; i < xNames.length; i++) {
59870 axName = xNames[i];
59871 axLayoutIn = layoutIn[axName];
59872 axLayoutOut = layoutOut[axName];
59873
59874 rangeSliderDefaults(layoutIn, layoutOut, axName);
59875
59876 if(axLayoutOut.type === 'date') {
59877 rangeSelectorDefaults(
59878 axLayoutIn,
59879 axLayoutOut,
59880 layoutOut,
59881 yNames,
59882 axLayoutOut.calendar
59883 );
59884 }
59885
59886 coerce('fixedrange');
59887 }
59888
59889 for(i = 0; i < yNames.length; i++) {
59890 axName = yNames[i];
59891 axLayoutIn = layoutIn[axName];
59892 axLayoutOut = layoutOut[axName];
59893
59894 var anchoredAxis = layoutOut[id2name(axLayoutOut.anchor)];
59895
59896 var fixedRangeDflt = getComponentMethod('rangeslider', 'isVisible')(anchoredAxis);
59897
59898 coerce('fixedrange', fixedRangeDflt);
59899 }
59900
59901 // Finally, handle scale constraints and matching axes.
59902 //
59903 // We need to do this after all axes have coerced both `type`
59904 // (so we link only axes of the same type) and
59905 // `fixedrange` (so we can avoid linking from OR TO a fixed axis).
59906
59907 // sets of axes linked by `scaleanchor` along with the scaleratios compounded
59908 // together, populated in handleConstraintDefaults
59909 var constraintGroups = layoutOut._axisConstraintGroups = [];
59910 // similar to _axisConstraintGroups, but for matching axes
59911 var matchGroups = layoutOut._axisMatchGroups = [];
59912 // make sure to include 'missing' axes here
59913 var allAxisIdsIncludingMissing = allAxisIds.concat(missingMatchedAxisIds);
59914 var axNamesIncludingMissing = axNames.concat(Lib.simpleMap(missingMatchedAxisIds, id2name));
59915
59916 for(i = 0; i < axNamesIncludingMissing.length; i++) {
59917 axName = axNamesIncludingMissing[i];
59918 axLetter = axName.charAt(0);
59919 axLayoutIn = layoutIn[axName];
59920 axLayoutOut = layoutOut[axName];
59921
59922 var scaleanchorDflt;
59923 if(axLetter === 'y' && !axLayoutIn.hasOwnProperty('scaleanchor') && axHasImage[axName]) {
59924 scaleanchorDflt = axLayoutOut.anchor;
59925 } else {
59926 scaleanchorDflt = undefined;
59927 }
59928
59929 var constrainDflt;
59930 if(!axLayoutIn.hasOwnProperty('constrain') && axHasImage[axName]) {
59931 constrainDflt = 'domain';
59932 } else {
59933 constrainDflt = undefined;
59934 }
59935
59936 handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, {
59937 allAxisIds: allAxisIdsIncludingMissing,
59938 layoutOut: layoutOut,
59939 scaleanchorDflt: scaleanchorDflt,
59940 constrainDflt: constrainDflt
59941 });
59942 }
59943
59944 for(i = 0; i < matchGroups.length; i++) {
59945 var group = matchGroups[i];
59946 var rng = null;
59947 var autorange = null;
59948
59949 // find 'matching' range attrs
59950 for(axId in group) {
59951 axLayoutOut = layoutOut[id2name(axId)];
59952 if(!axLayoutOut.matches) {
59953 rng = axLayoutOut.range;
59954 autorange = axLayoutOut.autorange;
59955 }
59956 }
59957 // if `ax.matches` values are reciprocal,
59958 // pick values of first axis in group
59959 if(rng === null || autorange === null) {
59960 for(axId in group) {
59961 axLayoutOut = layoutOut[id2name(axId)];
59962 rng = axLayoutOut.range;
59963 autorange = axLayoutOut.autorange;
59964 break;
59965 }
59966 }
59967 // apply matching range attrs
59968 for(axId in group) {
59969 axLayoutOut = layoutOut[id2name(axId)];
59970 if(axLayoutOut.matches) {
59971 axLayoutOut.range = rng.slice();
59972 axLayoutOut.autorange = autorange;
59973 }
59974 axLayoutOut._matchGroup = group;
59975 }
59976
59977 // remove matching axis from scaleanchor constraint groups (for now)
59978 if(constraintGroups.length) {
59979 for(axId in group) {
59980 for(j = 0; j < constraintGroups.length; j++) {
59981 var group2 = constraintGroups[j];
59982 for(var axId2 in group2) {
59983 if(axId === axId2) {
59984 Lib.warn('Axis ' + axId2 + ' is set with both ' +
59985 'a *scaleanchor* and *matches* constraint; ' +
59986 'ignoring the scale constraint.');
59987
59988 delete group2[axId2];
59989 if(Object.keys(group2).length < 2) {
59990 constraintGroups.splice(j, 1);
59991 }
59992 }
59993 }
59994 }
59995 }
59996 }
59997 }
59998};
59999
60000},{"../../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){
60001/**
60002* Copyright 2012-2020, Plotly, Inc.
60003* All rights reserved.
60004*
60005* This source code is licensed under the MIT license found in the
60006* LICENSE file in the root directory of this source tree.
60007*/
60008
60009'use strict';
60010
60011var colorMix = _dereq_('tinycolor2').mix;
60012var lightFraction = _dereq_('../../components/color/attributes').lightFraction;
60013var Lib = _dereq_('../../lib');
60014
60015/**
60016 * @param {object} opts :
60017 * - dfltColor {string} : default axis color
60018 * - bgColor {string} : combined subplot bg color
60019 * - blend {number, optional} : blend percentage (to compute dflt grid color)
60020 * - showLine {boolean} : show line by default
60021 * - showGrid {boolean} : show grid by default
60022 * - noZeroLine {boolean} : don't coerce zeroline* attributes
60023 * - attributes {object} : attribute object associated with input containers
60024 */
60025module.exports = function handleLineGridDefaults(containerIn, containerOut, coerce, opts) {
60026 opts = opts || {};
60027
60028 var dfltColor = opts.dfltColor;
60029
60030 function coerce2(attr, dflt) {
60031 return Lib.coerce2(containerIn, containerOut, opts.attributes, attr, dflt);
60032 }
60033
60034 var lineColor = coerce2('linecolor', dfltColor);
60035 var lineWidth = coerce2('linewidth');
60036 var showLine = coerce('showline', opts.showLine || !!lineColor || !!lineWidth);
60037
60038 if(!showLine) {
60039 delete containerOut.linecolor;
60040 delete containerOut.linewidth;
60041 }
60042
60043 var gridColorDflt = colorMix(dfltColor, opts.bgColor, opts.blend || lightFraction).toRgbString();
60044 var gridColor = coerce2('gridcolor', gridColorDflt);
60045 var gridWidth = coerce2('gridwidth');
60046 var showGridLines = coerce('showgrid', opts.showGrid || !!gridColor || !!gridWidth);
60047
60048 if(!showGridLines) {
60049 delete containerOut.gridcolor;
60050 delete containerOut.gridwidth;
60051 }
60052
60053 if(!opts.noZeroLine) {
60054 var zeroLineColor = coerce2('zerolinecolor', dfltColor);
60055 var zeroLineWidth = coerce2('zerolinewidth');
60056 var showZeroLine = coerce('zeroline', opts.showGrid || !!zeroLineColor || !!zeroLineWidth);
60057
60058 if(!showZeroLine) {
60059 delete containerOut.zerolinecolor;
60060 delete containerOut.zerolinewidth;
60061 }
60062 }
60063};
60064
60065},{"../../components/color/attributes":51,"../../lib":178,"tinycolor2":35}],239:[function(_dereq_,module,exports){
60066/**
60067* Copyright 2012-2020, Plotly, Inc.
60068* All rights reserved.
60069*
60070* This source code is licensed under the MIT license found in the
60071* LICENSE file in the root directory of this source tree.
60072*/
60073
60074
60075'use strict';
60076
60077var isNumeric = _dereq_('fast-isnumeric');
60078
60079var Lib = _dereq_('../../lib');
60080
60081
60082module.exports = function handlePositionDefaults(containerIn, containerOut, coerce, options) {
60083 var counterAxes = options.counterAxes || [];
60084 var overlayableAxes = options.overlayableAxes || [];
60085 var letter = options.letter;
60086 var grid = options.grid;
60087
60088 var dfltAnchor, dfltDomain, dfltSide, dfltPosition;
60089
60090 if(grid) {
60091 dfltDomain = grid._domains[letter][grid._axisMap[containerOut._id]];
60092 dfltAnchor = grid._anchors[containerOut._id];
60093 if(dfltDomain) {
60094 dfltSide = grid[letter + 'side'].split(' ')[0];
60095 dfltPosition = grid.domain[letter][dfltSide === 'right' || dfltSide === 'top' ? 1 : 0];
60096 }
60097 }
60098
60099 // Even if there's a grid, this axis may not be in it - fall back on non-grid defaults
60100 dfltDomain = dfltDomain || [0, 1];
60101 dfltAnchor = dfltAnchor || (isNumeric(containerIn.position) ? 'free' : (counterAxes[0] || 'free'));
60102 dfltSide = dfltSide || (letter === 'x' ? 'bottom' : 'left');
60103 dfltPosition = dfltPosition || 0;
60104
60105 var anchor = Lib.coerce(containerIn, containerOut, {
60106 anchor: {
60107 valType: 'enumerated',
60108 values: ['free'].concat(counterAxes),
60109 dflt: dfltAnchor
60110 }
60111 }, 'anchor');
60112
60113 if(anchor === 'free') coerce('position', dfltPosition);
60114
60115 Lib.coerce(containerIn, containerOut, {
60116 side: {
60117 valType: 'enumerated',
60118 values: letter === 'x' ? ['bottom', 'top'] : ['left', 'right'],
60119 dflt: dfltSide
60120 }
60121 }, 'side');
60122
60123 var overlaying = false;
60124 if(overlayableAxes.length) {
60125 overlaying = Lib.coerce(containerIn, containerOut, {
60126 overlaying: {
60127 valType: 'enumerated',
60128 values: [false].concat(overlayableAxes),
60129 dflt: false
60130 }
60131 }, 'overlaying');
60132 }
60133
60134 if(!overlaying) {
60135 // TODO: right now I'm copying this domain over to overlaying axes
60136 // in ax.setscale()... but this means we still need (imperfect) logic
60137 // in the axes popover to hide domain for the overlaying axis.
60138 // perhaps I should make a private version _domain that all axes get???
60139 var domain = coerce('domain', dfltDomain);
60140
60141 // according to https://www.npmjs.com/package/canvas-size
60142 // the minimum value of max canvas width across browsers and devices is 4096
60143 // which applied in the calculation below:
60144 if(domain[0] > domain[1] - 1 / 4096) containerOut.domain = dfltDomain;
60145 Lib.noneOrAll(containerIn.domain, containerOut.domain, dfltDomain);
60146 }
60147
60148 coerce('layer');
60149
60150 return containerOut;
60151};
60152
60153},{"../../lib":178,"fast-isnumeric":18}],240:[function(_dereq_,module,exports){
60154/**
60155* Copyright 2012-2020, Plotly, Inc.
60156* All rights reserved.
60157*
60158* This source code is licensed under the MIT license found in the
60159* LICENSE file in the root directory of this source tree.
60160*/
60161
60162
60163'use strict';
60164
60165var FROM_BL = _dereq_('../../constants/alignment').FROM_BL;
60166
60167module.exports = function scaleZoom(ax, factor, centerFraction) {
60168 if(centerFraction === undefined) {
60169 centerFraction = FROM_BL[ax.constraintoward || 'center'];
60170 }
60171
60172 var rangeLinear = [ax.r2l(ax.range[0]), ax.r2l(ax.range[1])];
60173 var center = rangeLinear[0] + (rangeLinear[1] - rangeLinear[0]) * centerFraction;
60174
60175 ax.range = ax._input.range = [
60176 ax.l2r(center + (rangeLinear[0] - center) * factor),
60177 ax.l2r(center + (rangeLinear[1] - center) * factor)
60178 ];
60179};
60180
60181},{"../../constants/alignment":154}],241:[function(_dereq_,module,exports){
60182/**
60183* Copyright 2012-2020, Plotly, Inc.
60184* All rights reserved.
60185*
60186* This source code is licensed under the MIT license found in the
60187* LICENSE file in the root directory of this source tree.
60188*/
60189
60190
60191'use strict';
60192
60193var polybool = _dereq_('polybooljs');
60194
60195var Registry = _dereq_('../../registry');
60196var dashStyle = _dereq_('../../components/drawing').dashStyle;
60197var Color = _dereq_('../../components/color');
60198var Fx = _dereq_('../../components/fx');
60199var makeEventData = _dereq_('../../components/fx/helpers').makeEventData;
60200var dragHelpers = _dereq_('../../components/dragelement/helpers');
60201var freeMode = dragHelpers.freeMode;
60202var rectMode = dragHelpers.rectMode;
60203var drawMode = dragHelpers.drawMode;
60204var openMode = dragHelpers.openMode;
60205var selectMode = dragHelpers.selectMode;
60206
60207var displayOutlines = _dereq_('../../components/shapes/draw_newshape/display_outlines');
60208var handleEllipse = _dereq_('../../components/shapes/draw_newshape/helpers').handleEllipse;
60209var newShapes = _dereq_('../../components/shapes/draw_newshape/newshapes');
60210
60211var Lib = _dereq_('../../lib');
60212var polygon = _dereq_('../../lib/polygon');
60213var throttle = _dereq_('../../lib/throttle');
60214var getFromId = _dereq_('./axis_ids').getFromId;
60215var clearGlCanvases = _dereq_('../../lib/clear_gl_canvases');
60216
60217var redrawReglTraces = _dereq_('../../plot_api/subroutines').redrawReglTraces;
60218
60219var constants = _dereq_('./constants');
60220var MINSELECT = constants.MINSELECT;
60221
60222var filteredPolygon = polygon.filter;
60223var polygonTester = polygon.tester;
60224
60225var clearSelect = _dereq_('./handle_outline').clearSelect;
60226
60227var helpers = _dereq_('./helpers');
60228var p2r = helpers.p2r;
60229var axValue = helpers.axValue;
60230var getTransform = helpers.getTransform;
60231
60232function prepSelect(e, startX, startY, dragOptions, mode) {
60233 var isFreeMode = freeMode(mode);
60234 var isRectMode = rectMode(mode);
60235 var isOpenMode = openMode(mode);
60236 var isDrawMode = drawMode(mode);
60237 var isSelectMode = selectMode(mode);
60238
60239 var isLine = mode === 'drawline';
60240 var isEllipse = mode === 'drawcircle';
60241 var isLineOrEllipse = isLine || isEllipse; // cases with two start & end positions
60242
60243 var gd = dragOptions.gd;
60244 var fullLayout = gd._fullLayout;
60245 var zoomLayer = fullLayout._zoomlayer;
60246 var dragBBox = dragOptions.element.getBoundingClientRect();
60247 var plotinfo = dragOptions.plotinfo;
60248 var transform = getTransform(plotinfo);
60249 var x0 = startX - dragBBox.left;
60250 var y0 = startY - dragBBox.top;
60251 var x1 = x0;
60252 var y1 = y0;
60253 var path0 = 'M' + x0 + ',' + y0;
60254 var pw = dragOptions.xaxes[0]._length;
60255 var ph = dragOptions.yaxes[0]._length;
60256 var allAxes = dragOptions.xaxes.concat(dragOptions.yaxes);
60257 var subtract = e.altKey &&
60258 !(drawMode(mode) && isOpenMode);
60259
60260 var filterPoly, selectionTester, mergedPolygons, currentPolygon;
60261 var i, searchInfo, eventData;
60262
60263 coerceSelectionsCache(e, gd, dragOptions);
60264
60265 if(isFreeMode) {
60266 filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX);
60267 }
60268
60269 var outlines = zoomLayer.selectAll('path.select-outline-' + plotinfo.id).data(isDrawMode ? [0] : [1, 2]);
60270 var drwStyle = fullLayout.newshape;
60271
60272 outlines.enter()
60273 .append('path')
60274 .attr('class', function(d) { return 'select-outline select-outline-' + d + ' select-outline-' + plotinfo.id; })
60275 .style(isDrawMode ? {
60276 opacity: drwStyle.opacity / 2,
60277 fill: isOpenMode ? undefined : drwStyle.fillcolor,
60278 stroke: drwStyle.line.color,
60279 'stroke-dasharray': dashStyle(drwStyle.line.dash, drwStyle.line.width),
60280 'stroke-width': drwStyle.line.width + 'px'
60281 } : {})
60282 .attr('fill-rule', drwStyle.fillrule)
60283 .classed('cursor-move', isDrawMode ? true : false)
60284 .attr('transform', transform)
60285 .attr('d', path0 + 'Z');
60286
60287 var corners = zoomLayer.append('path')
60288 .attr('class', 'zoombox-corners')
60289 .style({
60290 fill: Color.background,
60291 stroke: Color.defaultLine,
60292 'stroke-width': 1
60293 })
60294 .attr('transform', transform)
60295 .attr('d', 'M0,0Z');
60296
60297
60298 var throttleID = fullLayout._uid + constants.SELECTID;
60299 var selection = [];
60300
60301 // find the traces to search for selection points
60302 var searchTraces = determineSearchTraces(gd, dragOptions.xaxes,
60303 dragOptions.yaxes, dragOptions.subplot);
60304
60305 function ascending(a, b) { return a - b; }
60306
60307 // allow subplots to override fillRangeItems routine
60308 var fillRangeItems;
60309
60310 if(plotinfo.fillRangeItems) {
60311 fillRangeItems = plotinfo.fillRangeItems;
60312 } else {
60313 if(isRectMode) {
60314 fillRangeItems = function(eventData, poly) {
60315 var ranges = eventData.range = {};
60316
60317 for(i = 0; i < allAxes.length; i++) {
60318 var ax = allAxes[i];
60319 var axLetter = ax._id.charAt(0);
60320
60321 ranges[ax._id] = [
60322 p2r(ax, poly[axLetter + 'min']),
60323 p2r(ax, poly[axLetter + 'max'])
60324 ].sort(ascending);
60325 }
60326 };
60327 } else { // case of isFreeMode
60328 fillRangeItems = function(eventData, poly, filterPoly) {
60329 var dataPts = eventData.lassoPoints = {};
60330
60331 for(i = 0; i < allAxes.length; i++) {
60332 var ax = allAxes[i];
60333 dataPts[ax._id] = filterPoly.filtered.map(axValue(ax));
60334 }
60335 };
60336 }
60337 }
60338
60339 dragOptions.moveFn = function(dx0, dy0) {
60340 x1 = Math.max(0, Math.min(pw, dx0 + x0));
60341 y1 = Math.max(0, Math.min(ph, dy0 + y0));
60342
60343 var dx = Math.abs(x1 - x0);
60344 var dy = Math.abs(y1 - y0);
60345
60346 if(isRectMode) {
60347 var direction;
60348 var start, end;
60349
60350 if(isSelectMode) {
60351 var q = fullLayout.selectdirection;
60352
60353 if(q === 'any') {
60354 if(dy < Math.min(dx * 0.6, MINSELECT)) {
60355 direction = 'h';
60356 } else if(dx < Math.min(dy * 0.6, MINSELECT)) {
60357 direction = 'v';
60358 } else {
60359 direction = 'd';
60360 }
60361 } else {
60362 direction = q;
60363 }
60364
60365 switch(direction) {
60366 case 'h':
60367 start = isEllipse ? ph / 2 : 0;
60368 end = ph;
60369 break;
60370 case 'v':
60371 start = isEllipse ? pw / 2 : 0;
60372 end = pw;
60373 break;
60374 }
60375 }
60376
60377 if(isDrawMode) {
60378 switch(fullLayout.newshape.drawdirection) {
60379 case 'vertical':
60380 direction = 'h';
60381 start = isEllipse ? ph / 2 : 0;
60382 end = ph;
60383 break;
60384 case 'horizontal':
60385 direction = 'v';
60386 start = isEllipse ? pw / 2 : 0;
60387 end = pw;
60388 break;
60389 case 'ortho':
60390 if(dx < dy) {
60391 direction = 'h';
60392 start = y0;
60393 end = y1;
60394 } else {
60395 direction = 'v';
60396 start = x0;
60397 end = x1;
60398 }
60399 break;
60400 default: // i.e. case of 'diagonal'
60401 direction = 'd';
60402 }
60403 }
60404
60405 if(direction === 'h') {
60406 // horizontal motion
60407 currentPolygon = isLineOrEllipse ?
60408 handleEllipse(isEllipse, [x1, start], [x1, end]) : // using x1 instead of x0 allows adjusting the line while drawing
60409 [[x0, start], [x0, end], [x1, end], [x1, start]]; // make a vertical box
60410
60411 currentPolygon.xmin = isLineOrEllipse ? x1 : Math.min(x0, x1);
60412 currentPolygon.xmax = isLineOrEllipse ? x1 : Math.max(x0, x1);
60413 currentPolygon.ymin = Math.min(start, end);
60414 currentPolygon.ymax = Math.max(start, end);
60415 // extras to guide users in keeping a straight selection
60416 corners.attr('d', 'M' + currentPolygon.xmin + ',' + (y0 - MINSELECT) +
60417 'h-4v' + (2 * MINSELECT) + 'h4Z' +
60418 'M' + (currentPolygon.xmax - 1) + ',' + (y0 - MINSELECT) +
60419 'h4v' + (2 * MINSELECT) + 'h-4Z');
60420 } else if(direction === 'v') {
60421 // vertical motion
60422 currentPolygon = isLineOrEllipse ?
60423 handleEllipse(isEllipse, [start, y1], [end, y1]) : // using y1 instead of y0 allows adjusting the line while drawing
60424 [[start, y0], [start, y1], [end, y1], [end, y0]]; // make a horizontal box
60425
60426 currentPolygon.xmin = Math.min(start, end);
60427 currentPolygon.xmax = Math.max(start, end);
60428 currentPolygon.ymin = isLineOrEllipse ? y1 : Math.min(y0, y1);
60429 currentPolygon.ymax = isLineOrEllipse ? y1 : Math.max(y0, y1);
60430 corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + currentPolygon.ymin +
60431 'v-4h' + (2 * MINSELECT) + 'v4Z' +
60432 'M' + (x0 - MINSELECT) + ',' + (currentPolygon.ymax - 1) +
60433 'v4h' + (2 * MINSELECT) + 'v-4Z');
60434 } else if(direction === 'd') {
60435 // diagonal motion
60436 currentPolygon = isLineOrEllipse ?
60437 handleEllipse(isEllipse, [x0, y0], [x1, y1]) :
60438 [[x0, y0], [x0, y1], [x1, y1], [x1, y0]];
60439
60440 currentPolygon.xmin = Math.min(x0, x1);
60441 currentPolygon.xmax = Math.max(x0, x1);
60442 currentPolygon.ymin = Math.min(y0, y1);
60443 currentPolygon.ymax = Math.max(y0, y1);
60444 corners.attr('d', 'M0,0Z');
60445 }
60446 } else if(isFreeMode) {
60447 filterPoly.addPt([x1, y1]);
60448 currentPolygon = filterPoly.filtered;
60449 }
60450
60451 // create outline & tester
60452 if(dragOptions.selectionDefs && dragOptions.selectionDefs.length) {
60453 mergedPolygons = mergePolygons(dragOptions.mergedPolygons, currentPolygon, subtract);
60454 currentPolygon.subtract = subtract;
60455 selectionTester = multiTester(dragOptions.selectionDefs.concat([currentPolygon]));
60456 } else {
60457 mergedPolygons = [currentPolygon];
60458 selectionTester = polygonTester(currentPolygon);
60459 }
60460
60461 // display polygons on the screen
60462 displayOutlines(convertPoly(mergedPolygons, isOpenMode), outlines, dragOptions);
60463
60464 if(isSelectMode) {
60465 throttle.throttle(
60466 throttleID,
60467 constants.SELECTDELAY,
60468 function() {
60469 selection = [];
60470
60471 var thisSelection;
60472 var traceSelections = [];
60473 var traceSelection;
60474 for(i = 0; i < searchTraces.length; i++) {
60475 searchInfo = searchTraces[i];
60476
60477 traceSelection = searchInfo._module.selectPoints(searchInfo, selectionTester);
60478 traceSelections.push(traceSelection);
60479
60480 thisSelection = fillSelectionItem(traceSelection, searchInfo);
60481
60482 if(selection.length) {
60483 for(var j = 0; j < thisSelection.length; j++) {
60484 selection.push(thisSelection[j]);
60485 }
60486 } else selection = thisSelection;
60487 }
60488
60489 eventData = {points: selection};
60490 updateSelectedState(gd, searchTraces, eventData);
60491 fillRangeItems(eventData, currentPolygon, filterPoly);
60492 dragOptions.gd.emit('plotly_selecting', eventData);
60493 }
60494 );
60495 }
60496 };
60497
60498 dragOptions.clickFn = function(numClicks, evt) {
60499 corners.remove();
60500
60501 if(gd._fullLayout._activeShapeIndex >= 0) {
60502 gd._fullLayout._deactivateShape(gd);
60503 return;
60504 }
60505 if(isDrawMode) return;
60506
60507 var clickmode = fullLayout.clickmode;
60508
60509 throttle.done(throttleID).then(function() {
60510 throttle.clear(throttleID);
60511 if(numClicks === 2) {
60512 // clear selection on doubleclick
60513 outlines.remove();
60514 for(i = 0; i < searchTraces.length; i++) {
60515 searchInfo = searchTraces[i];
60516 searchInfo._module.selectPoints(searchInfo, false);
60517 }
60518
60519 updateSelectedState(gd, searchTraces);
60520
60521 clearSelectionsCache(dragOptions);
60522
60523 gd.emit('plotly_deselect', null);
60524 } else {
60525 if(clickmode.indexOf('select') > -1) {
60526 selectOnClick(evt, gd, dragOptions.xaxes, dragOptions.yaxes,
60527 dragOptions.subplot, dragOptions, outlines);
60528 }
60529
60530 if(clickmode === 'event') {
60531 // TODO: remove in v2 - this was probably never intended to work as it does,
60532 // but in case anyone depends on it we don't want to break it now.
60533 // Note that click-to-select introduced pre v2 also emitts proper
60534 // event data when clickmode is having 'select' in its flag list.
60535 gd.emit('plotly_selected', undefined);
60536 }
60537 }
60538
60539 Fx.click(gd, evt);
60540 }).catch(Lib.error);
60541 };
60542
60543 dragOptions.doneFn = function() {
60544 corners.remove();
60545
60546 throttle.done(throttleID).then(function() {
60547 throttle.clear(throttleID);
60548 dragOptions.gd.emit('plotly_selected', eventData);
60549
60550 if(currentPolygon && dragOptions.selectionDefs) {
60551 // save last polygons
60552 currentPolygon.subtract = subtract;
60553 dragOptions.selectionDefs.push(currentPolygon);
60554
60555 // we have to keep reference to arrays container
60556 dragOptions.mergedPolygons.length = 0;
60557 [].push.apply(dragOptions.mergedPolygons, mergedPolygons);
60558 }
60559
60560 if(dragOptions.doneFnCompleted) {
60561 dragOptions.doneFnCompleted(selection);
60562 }
60563 }).catch(Lib.error);
60564
60565 if(isDrawMode) {
60566 clearSelectionsCache(dragOptions);
60567 }
60568 };
60569}
60570
60571function selectOnClick(evt, gd, xAxes, yAxes, subplot, dragOptions, polygonOutlines) {
60572 var hoverData = gd._hoverdata;
60573 var fullLayout = gd._fullLayout;
60574 var clickmode = fullLayout.clickmode;
60575 var sendEvents = clickmode.indexOf('event') > -1;
60576 var selection = [];
60577 var searchTraces, searchInfo, currentSelectionDef, selectionTester, traceSelection;
60578 var thisTracesSelection, pointOrBinSelected, subtract, eventData, i;
60579
60580 if(isHoverDataSet(hoverData)) {
60581 coerceSelectionsCache(evt, gd, dragOptions);
60582 searchTraces = determineSearchTraces(gd, xAxes, yAxes, subplot);
60583 var clickedPtInfo = extractClickedPtInfo(hoverData, searchTraces);
60584 var isBinnedTrace = clickedPtInfo.pointNumbers.length > 0;
60585
60586
60587 // Note: potentially costly operation isPointOrBinSelected is
60588 // called as late as possible through the use of an assignment
60589 // in an if condition.
60590 if(isBinnedTrace ?
60591 isOnlyThisBinSelected(searchTraces, clickedPtInfo) :
60592 isOnlyOnePointSelected(searchTraces) &&
60593 (pointOrBinSelected = isPointOrBinSelected(clickedPtInfo))) {
60594 if(polygonOutlines) polygonOutlines.remove();
60595 for(i = 0; i < searchTraces.length; i++) {
60596 searchInfo = searchTraces[i];
60597 searchInfo._module.selectPoints(searchInfo, false);
60598 }
60599
60600 updateSelectedState(gd, searchTraces);
60601
60602 clearSelectionsCache(dragOptions);
60603
60604 if(sendEvents) {
60605 gd.emit('plotly_deselect', null);
60606 }
60607 } else {
60608 subtract = evt.shiftKey &&
60609 (pointOrBinSelected !== undefined ?
60610 pointOrBinSelected :
60611 isPointOrBinSelected(clickedPtInfo));
60612 currentSelectionDef = newPointSelectionDef(clickedPtInfo.pointNumber, clickedPtInfo.searchInfo, subtract);
60613
60614 var allSelectionDefs = dragOptions.selectionDefs.concat([currentSelectionDef]);
60615 selectionTester = multiTester(allSelectionDefs);
60616
60617 for(i = 0; i < searchTraces.length; i++) {
60618 traceSelection = searchTraces[i]._module.selectPoints(searchTraces[i], selectionTester);
60619 thisTracesSelection = fillSelectionItem(traceSelection, searchTraces[i]);
60620
60621 if(selection.length) {
60622 for(var j = 0; j < thisTracesSelection.length; j++) {
60623 selection.push(thisTracesSelection[j]);
60624 }
60625 } else selection = thisTracesSelection;
60626 }
60627
60628 eventData = {points: selection};
60629 updateSelectedState(gd, searchTraces, eventData);
60630
60631 if(currentSelectionDef && dragOptions) {
60632 dragOptions.selectionDefs.push(currentSelectionDef);
60633 }
60634
60635 if(polygonOutlines) {
60636 var polygons = dragOptions.mergedPolygons;
60637 var isOpenMode = openMode(dragOptions.dragmode);
60638
60639 // display polygons on the screen
60640 displayOutlines(convertPoly(polygons, isOpenMode), polygonOutlines, dragOptions);
60641 }
60642
60643 if(sendEvents) {
60644 gd.emit('plotly_selected', eventData);
60645 }
60646 }
60647 }
60648}
60649
60650/**
60651 * Constructs a new point selection definition object.
60652 */
60653function newPointSelectionDef(pointNumber, searchInfo, subtract) {
60654 return {
60655 pointNumber: pointNumber,
60656 searchInfo: searchInfo,
60657 subtract: subtract
60658 };
60659}
60660
60661function isPointSelectionDef(o) {
60662 return 'pointNumber' in o && 'searchInfo' in o;
60663}
60664
60665/*
60666 * Constructs a new point number tester.
60667 */
60668function newPointNumTester(pointSelectionDef) {
60669 return {
60670 xmin: 0,
60671 xmax: 0,
60672 ymin: 0,
60673 ymax: 0,
60674 pts: [],
60675 contains: function(pt, omitFirstEdge, pointNumber, searchInfo) {
60676 var idxWantedTrace = pointSelectionDef.searchInfo.cd[0].trace._expandedIndex;
60677 var idxActualTrace = searchInfo.cd[0].trace._expandedIndex;
60678 return idxActualTrace === idxWantedTrace &&
60679 pointNumber === pointSelectionDef.pointNumber;
60680 },
60681 isRect: false,
60682 degenerate: false,
60683 subtract: pointSelectionDef.subtract
60684 };
60685}
60686
60687/**
60688 * Wraps multiple selection testers.
60689 *
60690 * @param {Array} list - An array of selection testers.
60691 *
60692 * @return a selection tester object with a contains function
60693 * that can be called to evaluate a point against all wrapped
60694 * selection testers that were passed in list.
60695 */
60696function multiTester(list) {
60697 var testers = [];
60698 var xmin = isPointSelectionDef(list[0]) ? 0 : list[0][0][0];
60699 var xmax = xmin;
60700 var ymin = isPointSelectionDef(list[0]) ? 0 : list[0][0][1];
60701 var ymax = ymin;
60702
60703 for(var i = 0; i < list.length; i++) {
60704 if(isPointSelectionDef(list[i])) {
60705 testers.push(newPointNumTester(list[i]));
60706 } else {
60707 var tester = polygon.tester(list[i]);
60708 tester.subtract = list[i].subtract;
60709 testers.push(tester);
60710 xmin = Math.min(xmin, tester.xmin);
60711 xmax = Math.max(xmax, tester.xmax);
60712 ymin = Math.min(ymin, tester.ymin);
60713 ymax = Math.max(ymax, tester.ymax);
60714 }
60715 }
60716
60717 /**
60718 * Tests if the given point is within this tester.
60719 *
60720 * @param {Array} pt - [0] is the x coordinate, [1] is the y coordinate of the point.
60721 * @param {*} arg - An optional parameter to pass down to wrapped testers.
60722 * @param {number} pointNumber - The point number of the point within the underlying data array.
60723 * @param {number} searchInfo - An object identifying the trace the point is contained in.
60724 *
60725 * @return {boolean} true if point is considered to be selected, false otherwise.
60726 */
60727 function contains(pt, arg, pointNumber, searchInfo) {
60728 var contained = false;
60729 for(var i = 0; i < testers.length; i++) {
60730 if(testers[i].contains(pt, arg, pointNumber, searchInfo)) {
60731 // if contained by subtract tester - exclude the point
60732 contained = testers[i].subtract === false;
60733 }
60734 }
60735
60736 return contained;
60737 }
60738
60739 return {
60740 xmin: xmin,
60741 xmax: xmax,
60742 ymin: ymin,
60743 ymax: ymax,
60744 pts: [],
60745 contains: contains,
60746 isRect: false,
60747 degenerate: false
60748 };
60749}
60750
60751function coerceSelectionsCache(evt, gd, dragOptions) {
60752 gd._fullLayout._drawing = false;
60753
60754 var fullLayout = gd._fullLayout;
60755 var plotinfo = dragOptions.plotinfo;
60756 var dragmode = dragOptions.dragmode;
60757
60758 var selectingOnSameSubplot = (
60759 fullLayout._lastSelectedSubplot &&
60760 fullLayout._lastSelectedSubplot === plotinfo.id
60761 );
60762
60763 var hasModifierKey = (evt.shiftKey || evt.altKey) &&
60764 !(drawMode(dragmode) && openMode(dragmode));
60765
60766 if(selectingOnSameSubplot && hasModifierKey &&
60767 (plotinfo.selection && plotinfo.selection.selectionDefs) && !dragOptions.selectionDefs) {
60768 // take over selection definitions from prev mode, if any
60769 dragOptions.selectionDefs = plotinfo.selection.selectionDefs;
60770 dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons;
60771 } else if(!hasModifierKey || !plotinfo.selection) {
60772 clearSelectionsCache(dragOptions);
60773 }
60774
60775 // clear selection outline when selecting a different subplot
60776 if(!selectingOnSameSubplot) {
60777 clearSelect(gd);
60778 fullLayout._lastSelectedSubplot = plotinfo.id;
60779 }
60780}
60781
60782function clearSelectionsCache(dragOptions) {
60783 var dragmode = dragOptions.dragmode;
60784 var plotinfo = dragOptions.plotinfo;
60785
60786 var gd = dragOptions.gd;
60787 if(gd._fullLayout._activeShapeIndex >= 0) {
60788 gd._fullLayout._deactivateShape(gd);
60789 }
60790
60791 if(drawMode(dragmode)) {
60792 var fullLayout = gd._fullLayout;
60793 var zoomLayer = fullLayout._zoomlayer;
60794
60795 var outlines = zoomLayer.selectAll('.select-outline-' + plotinfo.id);
60796 if(outlines && gd._fullLayout._drawing) {
60797 // add shape
60798 var shapes = newShapes(outlines, dragOptions);
60799 if(shapes) {
60800 Registry.call('_guiRelayout', gd, {
60801 shapes: shapes
60802 });
60803 }
60804
60805 gd._fullLayout._drawing = false;
60806 }
60807 }
60808
60809 plotinfo.selection = {};
60810 plotinfo.selection.selectionDefs = dragOptions.selectionDefs = [];
60811 plotinfo.selection.mergedPolygons = dragOptions.mergedPolygons = [];
60812}
60813
60814function determineSearchTraces(gd, xAxes, yAxes, subplot) {
60815 var searchTraces = [];
60816 var xAxisIds = xAxes.map(function(ax) { return ax._id; });
60817 var yAxisIds = yAxes.map(function(ax) { return ax._id; });
60818 var cd, trace, i;
60819
60820 for(i = 0; i < gd.calcdata.length; i++) {
60821 cd = gd.calcdata[i];
60822 trace = cd[0].trace;
60823
60824 if(trace.visible !== true || !trace._module || !trace._module.selectPoints) continue;
60825
60826 if(subplot && (trace.subplot === subplot || trace.geo === subplot)) {
60827 searchTraces.push(createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]));
60828 } else if(
60829 trace.type === 'splom' &&
60830 // FIXME: make sure we don't have more than single axis for splom
60831 trace._xaxes[xAxisIds[0]] && trace._yaxes[yAxisIds[0]]
60832 ) {
60833 var info = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]);
60834 info.scene = gd._fullLayout._splomScenes[trace.uid];
60835 searchTraces.push(info);
60836 } else if(
60837 trace.type === 'sankey'
60838 ) {
60839 var sankeyInfo = createSearchInfo(trace._module, cd, xAxes[0], yAxes[0]);
60840 searchTraces.push(sankeyInfo);
60841 } else {
60842 if(xAxisIds.indexOf(trace.xaxis) === -1) continue;
60843 if(yAxisIds.indexOf(trace.yaxis) === -1) continue;
60844
60845 searchTraces.push(createSearchInfo(trace._module, cd,
60846 getFromId(gd, trace.xaxis), getFromId(gd, trace.yaxis)));
60847 }
60848 }
60849
60850 return searchTraces;
60851
60852 function createSearchInfo(module, calcData, xaxis, yaxis) {
60853 return {
60854 _module: module,
60855 cd: calcData,
60856 xaxis: xaxis,
60857 yaxis: yaxis
60858 };
60859 }
60860}
60861
60862function isHoverDataSet(hoverData) {
60863 return hoverData &&
60864 Array.isArray(hoverData) &&
60865 hoverData[0].hoverOnBox !== true;
60866}
60867
60868function extractClickedPtInfo(hoverData, searchTraces) {
60869 var hoverDatum = hoverData[0];
60870 var pointNumber = -1;
60871 var pointNumbers = [];
60872 var searchInfo, i;
60873
60874 for(i = 0; i < searchTraces.length; i++) {
60875 searchInfo = searchTraces[i];
60876 if(hoverDatum.fullData._expandedIndex === searchInfo.cd[0].trace._expandedIndex) {
60877 // Special case for box (and violin)
60878 if(hoverDatum.hoverOnBox === true) {
60879 break;
60880 }
60881
60882 // Hint: in some traces like histogram, one graphical element
60883 // doesn't correspond to one particular data point, but to
60884 // bins of data points. Thus, hoverDatum can have a binNumber
60885 // property instead of pointNumber.
60886 if(hoverDatum.pointNumber !== undefined) {
60887 pointNumber = hoverDatum.pointNumber;
60888 } else if(hoverDatum.binNumber !== undefined) {
60889 pointNumber = hoverDatum.binNumber;
60890 pointNumbers = hoverDatum.pointNumbers;
60891 }
60892
60893 break;
60894 }
60895 }
60896
60897 return {
60898 pointNumber: pointNumber,
60899 pointNumbers: pointNumbers,
60900 searchInfo: searchInfo
60901 };
60902}
60903
60904function isPointOrBinSelected(clickedPtInfo) {
60905 var trace = clickedPtInfo.searchInfo.cd[0].trace;
60906 var ptNum = clickedPtInfo.pointNumber;
60907 var ptNums = clickedPtInfo.pointNumbers;
60908 var ptNumsSet = ptNums.length > 0;
60909
60910 // When pointsNumbers is set (e.g. histogram's binning),
60911 // it is assumed that when the first point of
60912 // a bin is selected, all others are as well
60913 var ptNumToTest = ptNumsSet ? ptNums[0] : ptNum;
60914
60915 // TODO potential performance improvement
60916 // Primarily we need this function to determine if a click adds
60917 // or subtracts from a selection.
60918 // In cases `trace.selectedpoints` is a huge array, indexOf
60919 // might be slow. One remedy would be to introduce a hash somewhere.
60920 return trace.selectedpoints ? trace.selectedpoints.indexOf(ptNumToTest) > -1 : false;
60921}
60922
60923function isOnlyThisBinSelected(searchTraces, clickedPtInfo) {
60924 var tracesWithSelectedPts = [];
60925 var searchInfo, trace, isSameTrace, i;
60926
60927 for(i = 0; i < searchTraces.length; i++) {
60928 searchInfo = searchTraces[i];
60929 if(searchInfo.cd[0].trace.selectedpoints && searchInfo.cd[0].trace.selectedpoints.length > 0) {
60930 tracesWithSelectedPts.push(searchInfo);
60931 }
60932 }
60933
60934 if(tracesWithSelectedPts.length === 1) {
60935 isSameTrace = tracesWithSelectedPts[0] === clickedPtInfo.searchInfo;
60936 if(isSameTrace) {
60937 trace = clickedPtInfo.searchInfo.cd[0].trace;
60938 if(trace.selectedpoints.length === clickedPtInfo.pointNumbers.length) {
60939 for(i = 0; i < clickedPtInfo.pointNumbers.length; i++) {
60940 if(trace.selectedpoints.indexOf(clickedPtInfo.pointNumbers[i]) < 0) {
60941 return false;
60942 }
60943 }
60944 return true;
60945 }
60946 }
60947 }
60948
60949 return false;
60950}
60951
60952function isOnlyOnePointSelected(searchTraces) {
60953 var len = 0;
60954 var searchInfo, trace, i;
60955
60956 for(i = 0; i < searchTraces.length; i++) {
60957 searchInfo = searchTraces[i];
60958 trace = searchInfo.cd[0].trace;
60959 if(trace.selectedpoints) {
60960 if(trace.selectedpoints.length > 1) return false;
60961
60962 len += trace.selectedpoints.length;
60963 if(len > 1) return false;
60964 }
60965 }
60966
60967 return len === 1;
60968}
60969
60970function updateSelectedState(gd, searchTraces, eventData) {
60971 var i, searchInfo, cd, trace;
60972
60973 // before anything else, update preGUI if necessary
60974 for(i = 0; i < searchTraces.length; i++) {
60975 var fullInputTrace = searchTraces[i].cd[0].trace._fullInput;
60976 var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid] || {};
60977 if(tracePreGUI.selectedpoints === undefined) {
60978 tracePreGUI.selectedpoints = fullInputTrace._input.selectedpoints || null;
60979 }
60980 }
60981
60982 if(eventData) {
60983 var pts = eventData.points || [];
60984
60985 for(i = 0; i < searchTraces.length; i++) {
60986 trace = searchTraces[i].cd[0].trace;
60987 trace._input.selectedpoints = trace._fullInput.selectedpoints = [];
60988 if(trace._fullInput !== trace) trace.selectedpoints = [];
60989 }
60990
60991 for(i = 0; i < pts.length; i++) {
60992 var pt = pts[i];
60993 var data = pt.data;
60994 var fullData = pt.fullData;
60995
60996 if(pt.pointIndices) {
60997 [].push.apply(data.selectedpoints, pt.pointIndices);
60998 if(trace._fullInput !== trace) {
60999 [].push.apply(fullData.selectedpoints, pt.pointIndices);
61000 }
61001 } else {
61002 data.selectedpoints.push(pt.pointIndex);
61003 if(trace._fullInput !== trace) {
61004 fullData.selectedpoints.push(pt.pointIndex);
61005 }
61006 }
61007 }
61008 } else {
61009 for(i = 0; i < searchTraces.length; i++) {
61010 trace = searchTraces[i].cd[0].trace;
61011 delete trace.selectedpoints;
61012 delete trace._input.selectedpoints;
61013 if(trace._fullInput !== trace) {
61014 delete trace._fullInput.selectedpoints;
61015 }
61016 }
61017 }
61018
61019 var hasRegl = false;
61020
61021 for(i = 0; i < searchTraces.length; i++) {
61022 searchInfo = searchTraces[i];
61023 cd = searchInfo.cd;
61024 trace = cd[0].trace;
61025
61026 if(Registry.traceIs(trace, 'regl')) {
61027 hasRegl = true;
61028 }
61029
61030 var _module = searchInfo._module;
61031 var fn = _module.styleOnSelect || _module.style;
61032 if(fn) {
61033 fn(gd, cd, cd[0].node3);
61034 if(cd[0].nodeRangePlot3) fn(gd, cd, cd[0].nodeRangePlot3);
61035 }
61036 }
61037
61038 if(hasRegl) {
61039 clearGlCanvases(gd);
61040 redrawReglTraces(gd);
61041 }
61042}
61043
61044function mergePolygons(list, poly, subtract) {
61045 var res;
61046
61047 if(subtract) {
61048 res = polybool.difference({
61049 regions: list,
61050 inverted: false
61051 }, {
61052 regions: [poly],
61053 inverted: false
61054 });
61055
61056 return res.regions;
61057 }
61058
61059 res = polybool.union({
61060 regions: list,
61061 inverted: false
61062 }, {
61063 regions: [poly],
61064 inverted: false
61065 });
61066
61067 return res.regions;
61068}
61069
61070function fillSelectionItem(selection, searchInfo) {
61071 if(Array.isArray(selection)) {
61072 var cd = searchInfo.cd;
61073 var trace = searchInfo.cd[0].trace;
61074
61075 for(var i = 0; i < selection.length; i++) {
61076 selection[i] = makeEventData(selection[i], trace, cd);
61077 }
61078 }
61079
61080 return selection;
61081}
61082
61083function convertPoly(polygonsIn, isOpenMode) { // add M and L command to draft positions
61084 var polygonsOut = [];
61085 for(var i = 0; i < polygonsIn.length; i++) {
61086 polygonsOut[i] = [];
61087 for(var j = 0; j < polygonsIn[i].length; j++) {
61088 polygonsOut[i][j] = [];
61089 polygonsOut[i][j][0] = j ? 'L' : 'M';
61090 for(var k = 0; k < polygonsIn[i][j].length; k++) {
61091 polygonsOut[i][j].push(
61092 polygonsIn[i][j][k]
61093 );
61094 }
61095 }
61096
61097 if(!isOpenMode) {
61098 polygonsOut[i].push([
61099 'Z',
61100 polygonsOut[i][0][1], // initial x
61101 polygonsOut[i][0][2] // initial y
61102 ]);
61103 }
61104 }
61105
61106 return polygonsOut;
61107}
61108
61109module.exports = {
61110 prepSelect: prepSelect,
61111 clearSelect: clearSelect,
61112 clearSelectionsCache: clearSelectionsCache,
61113 selectOnClick: selectOnClick
61114};
61115
61116},{"../../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){
61117/**
61118* Copyright 2012-2020, Plotly, Inc.
61119* All rights reserved.
61120*
61121* This source code is licensed under the MIT license found in the
61122* LICENSE file in the root directory of this source tree.
61123*/
61124
61125'use strict';
61126
61127var d3 = _dereq_('d3');
61128var isNumeric = _dereq_('fast-isnumeric');
61129
61130var Lib = _dereq_('../../lib');
61131var cleanNumber = Lib.cleanNumber;
61132var ms2DateTime = Lib.ms2DateTime;
61133var dateTime2ms = Lib.dateTime2ms;
61134var ensureNumber = Lib.ensureNumber;
61135var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
61136
61137var numConstants = _dereq_('../../constants/numerical');
61138var FP_SAFE = numConstants.FP_SAFE;
61139var BADNUM = numConstants.BADNUM;
61140var LOG_CLIP = numConstants.LOG_CLIP;
61141var ONEDAY = numConstants.ONEDAY;
61142var ONEHOUR = numConstants.ONEHOUR;
61143var ONEMIN = numConstants.ONEMIN;
61144var ONESEC = numConstants.ONESEC;
61145
61146var axisIds = _dereq_('./axis_ids');
61147
61148var constants = _dereq_('./constants');
61149var HOUR_PATTERN = constants.HOUR_PATTERN;
61150var WEEKDAY_PATTERN = constants.WEEKDAY_PATTERN;
61151
61152function fromLog(v) {
61153 return Math.pow(10, v);
61154}
61155
61156function isValidCategory(v) {
61157 return v !== null && v !== undefined;
61158}
61159
61160/**
61161 * Define the conversion functions for an axis data is used in 5 ways:
61162 *
61163 * d: data, in whatever form it's provided
61164 * c: calcdata: turned into numbers, but not linearized
61165 * l: linearized - same as c except for log axes (and other nonlinear
61166 * mappings later?) this is used when we need to know if it's
61167 * *possible* to show some data on this axis, without caring about
61168 * the current range
61169 * p: pixel value - mapped to the screen with current size and zoom
61170 * r: ranges, tick0, and annotation positions match one of the above
61171 * but are handled differently for different types:
61172 * - linear and date: data format (d)
61173 * - category: calcdata format (c), and will stay that way because
61174 * the data format has no continuous mapping
61175 * - log: linearized (l) format
61176 * TODO: in v2.0 we plan to change it to data format. At that point
61177 * shapes will work the same way as ranges, tick0, and annotations
61178 * so they can use this conversion too.
61179 *
61180 * Creates/updates these conversion functions, and a few more utilities
61181 * like cleanRange, and makeCalcdata
61182 *
61183 * also clears the autotick constraints ._minDtick, ._forceTick0
61184 */
61185module.exports = function setConvert(ax, fullLayout) {
61186 fullLayout = fullLayout || {};
61187
61188 var axId = (ax._id || 'x');
61189 var axLetter = axId.charAt(0);
61190
61191 function toLog(v, clip) {
61192 if(v > 0) return Math.log(v) / Math.LN10;
61193
61194 else if(v <= 0 && clip && ax.range && ax.range.length === 2) {
61195 // clip NaN (ie past negative infinity) to LOG_CLIP axis
61196 // length past the negative edge
61197 var r0 = ax.range[0];
61198 var r1 = ax.range[1];
61199 return 0.5 * (r0 + r1 - 2 * LOG_CLIP * Math.abs(r0 - r1));
61200 } else return BADNUM;
61201 }
61202
61203 /*
61204 * wrapped dateTime2ms that:
61205 * - accepts ms numbers for backward compatibility
61206 * - inserts a dummy arg so calendar is the 3rd arg (see notes below).
61207 * - defaults to ax.calendar
61208 */
61209 function dt2ms(v, _, calendar, opts) {
61210 // NOTE: Changed this behavior: previously we took any numeric value
61211 // to be a ms, even if it was a string that could be a bare year.
61212 // Now we convert it as a date if at all possible, and only try
61213 // as (local) ms if that fails.
61214 var ms = dateTime2ms(v, calendar || ax.calendar);
61215 if(ms === BADNUM) {
61216 if(isNumeric(v)) {
61217 v = +v;
61218 if((opts || {}).msUTC) {
61219 // For now it is only used
61220 // to fix bar length in milliseconds.
61221 // It could be applied in other places in v2
61222 return v;
61223 }
61224
61225 // keep track of tenths of ms, that `new Date` will drop
61226 // same logic as in Lib.ms2DateTime
61227 var msecTenths = Math.floor(Lib.mod(v + 0.05, 1) * 10);
61228 var msRounded = Math.round(v - msecTenths / 10);
61229 ms = dateTime2ms(new Date(msRounded)) + msecTenths / 10;
61230 } else return BADNUM;
61231 }
61232 return ms;
61233 }
61234
61235 // wrapped ms2DateTime to insert default ax.calendar
61236 function ms2dt(v, r, calendar) {
61237 return ms2DateTime(v, r, calendar || ax.calendar);
61238 }
61239
61240 function getCategoryName(v) {
61241 return ax._categories[Math.round(v)];
61242 }
61243
61244 /*
61245 * setCategoryIndex: return the index of category v,
61246 * inserting it in the list if it's not already there
61247 *
61248 * this will enter the categories in the order it
61249 * encounters them, ie all the categories from the
61250 * first data set, then all the ones from the second
61251 * that aren't in the first etc.
61252 *
61253 * it is assumed that this function is being invoked in the
61254 * already sorted category order; otherwise there would be
61255 * a disconnect between the array and the index returned
61256 */
61257 function setCategoryIndex(v) {
61258 if(isValidCategory(v)) {
61259 if(ax._categoriesMap === undefined) {
61260 ax._categoriesMap = {};
61261 }
61262
61263 if(ax._categoriesMap[v] !== undefined) {
61264 return ax._categoriesMap[v];
61265 } else {
61266 ax._categories.push(typeof v === 'number' ? String(v) : v);
61267
61268 var curLength = ax._categories.length - 1;
61269 ax._categoriesMap[v] = curLength;
61270
61271 return curLength;
61272 }
61273 }
61274 return BADNUM;
61275 }
61276
61277 function setMultiCategoryIndex(arrayIn, len) {
61278 var arrayOut = new Array(len);
61279
61280 for(var i = 0; i < len; i++) {
61281 var v0 = (arrayIn[0] || [])[i];
61282 var v1 = (arrayIn[1] || [])[i];
61283 arrayOut[i] = getCategoryIndex([v0, v1]);
61284 }
61285
61286 return arrayOut;
61287 }
61288
61289 function getCategoryIndex(v) {
61290 if(ax._categoriesMap) {
61291 return ax._categoriesMap[v];
61292 }
61293 }
61294
61295 function getCategoryPosition(v) {
61296 // d2l/d2c variant that that won't add categories but will also
61297 // allow numbers to be mapped to the linearized axis positions
61298 var index = getCategoryIndex(v);
61299 if(index !== undefined) return index;
61300 if(isNumeric(v)) return +v;
61301 }
61302
61303 // include 2 fractional digits on pixel, for PDF zooming etc
61304 function _l2p(v, m, b) { return d3.round(b + m * v, 2); }
61305
61306 function _p2l(px, m, b) { return (px - b) / m; }
61307
61308 var l2p = function l2p(v) {
61309 if(!isNumeric(v)) return BADNUM;
61310 return _l2p(v, ax._m, ax._b);
61311 };
61312
61313 var p2l = function(px) {
61314 return _p2l(px, ax._m, ax._b);
61315 };
61316
61317 if(ax.rangebreaks) {
61318 var isY = axLetter === 'y';
61319
61320 l2p = function(v) {
61321 if(!isNumeric(v)) return BADNUM;
61322 var len = ax._rangebreaks.length;
61323 if(!len) return _l2p(v, ax._m, ax._b);
61324
61325 var flip = isY;
61326 if(ax.range[0] > ax.range[1]) flip = !flip;
61327 var signAx = flip ? -1 : 1;
61328 var pos = signAx * v;
61329
61330 var q = 0;
61331 for(var i = 0; i < len; i++) {
61332 var min = signAx * ax._rangebreaks[i].min;
61333 var max = signAx * ax._rangebreaks[i].max;
61334
61335 if(pos < min) break;
61336 if(pos > max) q = i + 1;
61337 else {
61338 // when falls into break, pick 'closest' offset
61339 q = pos < (min + max) / 2 ? i : i + 1;
61340 break;
61341 }
61342 }
61343 var b2 = ax._B[q] || 0;
61344 if(!isFinite(b2)) return 0; // avoid NaN translate e.g. in positionLabels if one keep zooming exactly into a break
61345 return _l2p(v, ax._m2, b2);
61346 };
61347
61348 p2l = function(px) {
61349 var len = ax._rangebreaks.length;
61350 if(!len) return _p2l(px, ax._m, ax._b);
61351
61352 var q = 0;
61353 for(var i = 0; i < len; i++) {
61354 if(px < ax._rangebreaks[i].pmin) break;
61355 if(px > ax._rangebreaks[i].pmax) q = i + 1;
61356 }
61357 return _p2l(px, ax._m2, ax._B[q]);
61358 };
61359 }
61360
61361 // conversions among c/l/p are fairly simple - do them together for all axis types
61362 ax.c2l = (ax.type === 'log') ? toLog : ensureNumber;
61363 ax.l2c = (ax.type === 'log') ? fromLog : ensureNumber;
61364
61365 ax.l2p = l2p;
61366 ax.p2l = p2l;
61367
61368 ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p;
61369 ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l;
61370
61371 /*
61372 * now type-specific conversions for **ALL** other combinations
61373 * they're all written out, instead of being combinations of each other, for
61374 * both clarity and speed.
61375 */
61376 if(['linear', '-'].indexOf(ax.type) !== -1) {
61377 // all are data vals, but d and r need cleaning
61378 ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber;
61379 ax.c2d = ax.c2r = ax.l2d = ax.l2r = ensureNumber;
61380
61381 ax.d2p = ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); };
61382 ax.p2d = ax.p2r = p2l;
61383
61384 ax.cleanPos = ensureNumber;
61385 } else if(ax.type === 'log') {
61386 // d and c are data vals, r and l are logged (but d and r need cleaning)
61387 ax.d2r = ax.d2l = function(v, clip) { return toLog(cleanNumber(v), clip); };
61388 ax.r2d = ax.r2c = function(v) { return fromLog(cleanNumber(v)); };
61389
61390 ax.d2c = ax.r2l = cleanNumber;
61391 ax.c2d = ax.l2r = ensureNumber;
61392
61393 ax.c2r = toLog;
61394 ax.l2d = fromLog;
61395
61396 ax.d2p = function(v, clip) { return ax.l2p(ax.d2r(v, clip)); };
61397 ax.p2d = function(px) { return fromLog(p2l(px)); };
61398
61399 ax.r2p = function(v) { return ax.l2p(cleanNumber(v)); };
61400 ax.p2r = p2l;
61401
61402 ax.cleanPos = ensureNumber;
61403 } else if(ax.type === 'date') {
61404 // r and d are date strings, l and c are ms
61405
61406 /*
61407 * Any of these functions with r and d on either side, calendar is the
61408 * **3rd** argument. log has reserved the second argument.
61409 *
61410 * Unless you need the special behavior of the second arg (ms2DateTime
61411 * uses this to limit precision, toLog uses true to clip negatives
61412 * to offscreen low rather than undefined), it's safe to pass 0.
61413 */
61414 ax.d2r = ax.r2d = Lib.identity;
61415
61416 ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms;
61417 ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt;
61418
61419 ax.d2p = ax.r2p = function(v, _, calendar) { return ax.l2p(dt2ms(v, 0, calendar)); };
61420 ax.p2d = ax.p2r = function(px, r, calendar) { return ms2dt(p2l(px), r, calendar); };
61421
61422 ax.cleanPos = function(v) { return Lib.cleanDate(v, BADNUM, ax.calendar); };
61423 } else if(ax.type === 'category') {
61424 // d is categories (string)
61425 // c and l are indices (numbers)
61426 // r is categories or numbers
61427
61428 ax.d2c = ax.d2l = setCategoryIndex;
61429 ax.r2d = ax.c2d = ax.l2d = getCategoryName;
61430
61431 ax.d2r = ax.d2l_noadd = getCategoryPosition;
61432
61433 ax.r2c = function(v) {
61434 var index = getCategoryPosition(v);
61435 return index !== undefined ? index : ax.fraction2r(0.5);
61436 };
61437
61438 ax.l2r = ax.c2r = ensureNumber;
61439 ax.r2l = getCategoryPosition;
61440
61441 ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); };
61442 ax.p2d = function(px) { return getCategoryName(p2l(px)); };
61443 ax.r2p = ax.d2p;
61444 ax.p2r = p2l;
61445
61446 ax.cleanPos = function(v) {
61447 if(typeof v === 'string' && v !== '') return v;
61448 return ensureNumber(v);
61449 };
61450 } else if(ax.type === 'multicategory') {
61451 // N.B. multicategory axes don't define d2c and d2l,
61452 // as 'data-to-calcdata' conversion needs to take into
61453 // account all data array items as in ax.makeCalcdata.
61454
61455 ax.r2d = ax.c2d = ax.l2d = getCategoryName;
61456 ax.d2r = ax.d2l_noadd = getCategoryPosition;
61457
61458 ax.r2c = function(v) {
61459 var index = getCategoryPosition(v);
61460 return index !== undefined ? index : ax.fraction2r(0.5);
61461 };
61462
61463 ax.r2c_just_indices = getCategoryIndex;
61464
61465 ax.l2r = ax.c2r = ensureNumber;
61466 ax.r2l = getCategoryPosition;
61467
61468 ax.d2p = function(v) { return ax.l2p(ax.r2c(v)); };
61469 ax.p2d = function(px) { return getCategoryName(p2l(px)); };
61470 ax.r2p = ax.d2p;
61471 ax.p2r = p2l;
61472
61473 ax.cleanPos = function(v) {
61474 if(Array.isArray(v) || (typeof v === 'string' && v !== '')) return v;
61475 return ensureNumber(v);
61476 };
61477
61478 ax.setupMultiCategory = function(fullData) {
61479 var traceIndices = ax._traceIndices;
61480 var i, j;
61481
61482 var matchGroups = fullLayout._axisMatchGroups;
61483 if(matchGroups && matchGroups.length && ax._categories.length === 0) {
61484 for(i = 0; i < matchGroups.length; i++) {
61485 var group = matchGroups[i];
61486 if(group[axId]) {
61487 for(var axId2 in group) {
61488 if(axId2 !== axId) {
61489 var ax2 = fullLayout[axisIds.id2name(axId2)];
61490 traceIndices = traceIndices.concat(ax2._traceIndices);
61491 }
61492 }
61493 }
61494 }
61495 }
61496
61497 // [ [cnt, {$cat: index}], for 1,2 ]
61498 var seen = [[0, {}], [0, {}]];
61499 // [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ]
61500 var list = [];
61501
61502 for(i = 0; i < traceIndices.length; i++) {
61503 var trace = fullData[traceIndices[i]];
61504
61505 if(axLetter in trace) {
61506 var arrayIn = trace[axLetter];
61507 var len = trace._length || Lib.minRowLength(arrayIn);
61508
61509 if(isArrayOrTypedArray(arrayIn[0]) && isArrayOrTypedArray(arrayIn[1])) {
61510 for(j = 0; j < len; j++) {
61511 var v0 = arrayIn[0][j];
61512 var v1 = arrayIn[1][j];
61513
61514 if(isValidCategory(v0) && isValidCategory(v1)) {
61515 list.push([v0, v1]);
61516
61517 if(!(v0 in seen[0][1])) {
61518 seen[0][1][v0] = seen[0][0]++;
61519 }
61520 if(!(v1 in seen[1][1])) {
61521 seen[1][1][v1] = seen[1][0]++;
61522 }
61523 }
61524 }
61525 }
61526 }
61527 }
61528
61529 list.sort(function(a, b) {
61530 var ind0 = seen[0][1];
61531 var d = ind0[a[0]] - ind0[b[0]];
61532 if(d) return d;
61533
61534 var ind1 = seen[1][1];
61535 return ind1[a[1]] - ind1[b[1]];
61536 });
61537
61538 for(i = 0; i < list.length; i++) {
61539 setCategoryIndex(list[i]);
61540 }
61541 };
61542 }
61543
61544 // find the range value at the specified (linear) fraction of the axis
61545 ax.fraction2r = function(v) {
61546 var rl0 = ax.r2l(ax.range[0]);
61547 var rl1 = ax.r2l(ax.range[1]);
61548 return ax.l2r(rl0 + v * (rl1 - rl0));
61549 };
61550
61551 // find the fraction of the range at the specified range value
61552 ax.r2fraction = function(v) {
61553 var rl0 = ax.r2l(ax.range[0]);
61554 var rl1 = ax.r2l(ax.range[1]);
61555 return (ax.r2l(v) - rl0) / (rl1 - rl0);
61556 };
61557
61558 /*
61559 * cleanRange: make sure range is a couplet of valid & distinct values
61560 * keep numbers away from the limits of floating point numbers,
61561 * and dates away from the ends of our date system (+/- 9999 years)
61562 *
61563 * optional param rangeAttr: operate on a different attribute, like
61564 * ax._r, rather than ax.range
61565 */
61566 ax.cleanRange = function(rangeAttr, opts) {
61567 if(!opts) opts = {};
61568 if(!rangeAttr) rangeAttr = 'range';
61569
61570 var range = Lib.nestedProperty(ax, rangeAttr).get();
61571 var i, dflt;
61572
61573 if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
61574 else if(axLetter === 'y') dflt = constants.DFLTRANGEY;
61575 else dflt = opts.dfltRange || constants.DFLTRANGEX;
61576
61577 // make sure we don't later mutate the defaults
61578 dflt = dflt.slice();
61579
61580 if(ax.rangemode === 'tozero' || ax.rangemode === 'nonnegative') {
61581 dflt[0] = 0;
61582 }
61583
61584 if(!range || range.length !== 2) {
61585 Lib.nestedProperty(ax, rangeAttr).set(dflt);
61586 return;
61587 }
61588
61589 if(ax.type === 'date' && !ax.autorange) {
61590 // check if milliseconds or js date objects are provided for range
61591 // and convert to date strings
61592 range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar);
61593 range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar);
61594 }
61595
61596 for(i = 0; i < 2; i++) {
61597 if(ax.type === 'date') {
61598 if(!Lib.isDateTime(range[i], ax.calendar)) {
61599 ax[rangeAttr] = dflt;
61600 break;
61601 }
61602
61603 if(ax.r2l(range[0]) === ax.r2l(range[1])) {
61604 // split by +/- 1 second
61605 var linCenter = Lib.constrain(ax.r2l(range[0]),
61606 Lib.MIN_MS + 1000, Lib.MAX_MS - 1000);
61607 range[0] = ax.l2r(linCenter - 1000);
61608 range[1] = ax.l2r(linCenter + 1000);
61609 break;
61610 }
61611 } else {
61612 if(!isNumeric(range[i])) {
61613 if(isNumeric(range[1 - i])) {
61614 range[i] = range[1 - i] * (i ? 10 : 0.1);
61615 } else {
61616 ax[rangeAttr] = dflt;
61617 break;
61618 }
61619 }
61620
61621 if(range[i] < -FP_SAFE) range[i] = -FP_SAFE;
61622 else if(range[i] > FP_SAFE) range[i] = FP_SAFE;
61623
61624 if(range[0] === range[1]) {
61625 // somewhat arbitrary: split by 1 or 1ppm, whichever is bigger
61626 var inc = Math.max(1, Math.abs(range[0] * 1e-6));
61627 range[0] -= inc;
61628 range[1] += inc;
61629 }
61630 }
61631 }
61632 };
61633
61634 // set scaling to pixels
61635 ax.setScale = function(usePrivateRange) {
61636 var gs = fullLayout._size;
61637
61638 // make sure we have a domain (pull it in from the axis
61639 // this one is overlaying if necessary)
61640 if(ax.overlaying) {
61641 var ax2 = axisIds.getFromId({ _fullLayout: fullLayout }, ax.overlaying);
61642 ax.domain = ax2.domain;
61643 }
61644
61645 // While transitions are occurring, we get a double-transform
61646 // issue if we transform the drawn layer *and* use the new axis range to
61647 // draw the data. This allows us to construct setConvert using the pre-
61648 // interaction values of the range:
61649 var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range';
61650 var calendar = ax.calendar;
61651 ax.cleanRange(rangeAttr);
61652
61653 var rl0 = ax.r2l(ax[rangeAttr][0], calendar);
61654 var rl1 = ax.r2l(ax[rangeAttr][1], calendar);
61655
61656 var isY = axLetter === 'y';
61657 if(isY) {
61658 ax._offset = gs.t + (1 - ax.domain[1]) * gs.h;
61659 ax._length = gs.h * (ax.domain[1] - ax.domain[0]);
61660 ax._m = ax._length / (rl0 - rl1);
61661 ax._b = -ax._m * rl1;
61662 } else {
61663 ax._offset = gs.l + ax.domain[0] * gs.w;
61664 ax._length = gs.w * (ax.domain[1] - ax.domain[0]);
61665 ax._m = ax._length / (rl1 - rl0);
61666 ax._b = -ax._m * rl0;
61667 }
61668
61669 // set of "N" disjoint rangebreaks inside the range
61670 ax._rangebreaks = [];
61671 // length of these rangebreaks in value space - negative on reversed axes
61672 ax._lBreaks = 0;
61673 // l2p slope (same for all intervals)
61674 ax._m2 = 0;
61675 // set of l2p offsets (one for each of the (N+1) piecewise intervals)
61676 ax._B = [];
61677
61678 if(ax.rangebreaks) {
61679 var i, brk;
61680
61681 ax._rangebreaks = ax.locateBreaks(
61682 Math.min(rl0, rl1),
61683 Math.max(rl0, rl1)
61684 );
61685
61686 if(ax._rangebreaks.length) {
61687 for(i = 0; i < ax._rangebreaks.length; i++) {
61688 brk = ax._rangebreaks[i];
61689 ax._lBreaks += Math.abs(brk.max - brk.min);
61690 }
61691
61692 var flip = isY;
61693 if(rl0 > rl1) flip = !flip;
61694 if(flip) ax._rangebreaks.reverse();
61695 var sign = flip ? -1 : 1;
61696
61697 ax._m2 = sign * ax._length / (Math.abs(rl1 - rl0) - ax._lBreaks);
61698 ax._B.push(-ax._m2 * (isY ? rl1 : rl0));
61699 for(i = 0; i < ax._rangebreaks.length; i++) {
61700 brk = ax._rangebreaks[i];
61701 ax._B.push(
61702 ax._B[ax._B.length - 1] -
61703 sign * ax._m2 * (brk.max - brk.min)
61704 );
61705 }
61706
61707 // fill pixel (i.e. 'p') min/max here,
61708 // to not have to loop through the _rangebreaks twice during `p2l`
61709 for(i = 0; i < ax._rangebreaks.length; i++) {
61710 brk = ax._rangebreaks[i];
61711 brk.pmin = l2p(brk.min);
61712 brk.pmax = l2p(brk.max);
61713 }
61714 }
61715 }
61716
61717 if(!isFinite(ax._m) || !isFinite(ax._b) || ax._length < 0) {
61718 fullLayout._replotting = false;
61719 throw new Error('Something went wrong with axis scaling');
61720 }
61721 };
61722
61723 ax.maskBreaks = function(v) {
61724 var rangebreaksIn = ax.rangebreaks || [];
61725 var bnds, b0, b1, vb, vDate;
61726
61727 for(var i = 0; i < rangebreaksIn.length; i++) {
61728 var brk = rangebreaksIn[i];
61729
61730 if(brk.enabled) {
61731 if(brk.bounds) {
61732 var pattern = brk.pattern;
61733 bnds = Lib.simpleMap(brk.bounds, pattern ?
61734 cleanNumber :
61735 ax.d2c // case of pattern: ''
61736 );
61737 b0 = bnds[0];
61738 b1 = bnds[1];
61739
61740 switch(pattern) {
61741 case WEEKDAY_PATTERN:
61742 vDate = new Date(v);
61743 vb = vDate.getUTCDay();
61744
61745 if(b0 > b1) {
61746 b1 += 7;
61747 if(vb < b0) vb += 7;
61748 }
61749
61750 break;
61751 case HOUR_PATTERN:
61752 vDate = new Date(v);
61753 var hours = vDate.getUTCHours();
61754 var minutes = vDate.getUTCMinutes();
61755 var seconds = vDate.getUTCSeconds();
61756 var milliseconds = vDate.getUTCMilliseconds();
61757
61758 vb = hours + (
61759 minutes / 60 +
61760 seconds / 3600 +
61761 milliseconds / 3600000
61762 );
61763
61764 if(b0 > b1) {
61765 b1 += 24;
61766 if(vb < b0) vb += 24;
61767 }
61768
61769 break;
61770 case '':
61771 // N.B. should work on date axes as well!
61772 // e.g. { bounds: ['2020-01-04', '2020-01-05 23:59'] }
61773 // TODO should work with reversed-range axes
61774 vb = v;
61775 break;
61776 }
61777
61778 if(vb >= b0 && vb < b1) return BADNUM;
61779 } else {
61780 var vals = Lib.simpleMap(brk.values, ax.d2c).sort(Lib.sorterAsc);
61781 for(var j = 0; j < vals.length; j++) {
61782 b0 = vals[j];
61783 b1 = b0 + brk.dvalue;
61784 if(v >= b0 && v < b1) return BADNUM;
61785 }
61786 }
61787 }
61788 }
61789 return v;
61790 };
61791
61792 ax.locateBreaks = function(r0, r1) {
61793 var i, bnds, b0, b1;
61794
61795 var rangebreaksOut = [];
61796 if(!ax.rangebreaks) return rangebreaksOut;
61797
61798 var rangebreaksIn = ax.rangebreaks.slice().sort(function(a, b) {
61799 if(a.pattern === WEEKDAY_PATTERN && b.pattern === HOUR_PATTERN) return -1;
61800 if(b.pattern === WEEKDAY_PATTERN && a.pattern === HOUR_PATTERN) return 1;
61801 return 0;
61802 });
61803
61804 var addBreak = function(min, max) {
61805 min = Lib.constrain(min, r0, r1);
61806 max = Lib.constrain(max, r0, r1);
61807 if(min === max) return;
61808
61809 var isNewBreak = true;
61810 for(var j = 0; j < rangebreaksOut.length; j++) {
61811 var brkj = rangebreaksOut[j];
61812 if(min < brkj.max && max >= brkj.min) {
61813 if(min < brkj.min) {
61814 brkj.min = min;
61815 }
61816 if(max > brkj.max) {
61817 brkj.max = max;
61818 }
61819 isNewBreak = false;
61820 }
61821 }
61822 if(isNewBreak) {
61823 rangebreaksOut.push({min: min, max: max});
61824 }
61825 };
61826
61827 for(i = 0; i < rangebreaksIn.length; i++) {
61828 var brk = rangebreaksIn[i];
61829
61830 if(brk.enabled) {
61831 if(brk.bounds) {
61832 var t0 = r0;
61833 var t1 = r1;
61834 if(brk.pattern) {
61835 // to remove decimal (most often found in auto ranges)
61836 t0 = Math.floor(t0);
61837 }
61838
61839 bnds = Lib.simpleMap(brk.bounds, brk.pattern ? cleanNumber : ax.r2l);
61840 b0 = bnds[0];
61841 b1 = bnds[1];
61842
61843 // r0 value as date
61844 var t0Date = new Date(t0);
61845 // r0 value for break pattern
61846 var bndDelta;
61847 // step in ms between rangebreaks
61848 var step;
61849
61850 switch(brk.pattern) {
61851 case WEEKDAY_PATTERN:
61852 step = 7 * ONEDAY;
61853
61854 bndDelta = (
61855 (b1 < b0 ? 7 : 0) +
61856 (b1 - b0)
61857 ) * ONEDAY;
61858
61859 t0 += b0 * ONEDAY - (
61860 t0Date.getUTCDay() * ONEDAY +
61861 t0Date.getUTCHours() * ONEHOUR +
61862 t0Date.getUTCMinutes() * ONEMIN +
61863 t0Date.getUTCSeconds() * ONESEC +
61864 t0Date.getUTCMilliseconds()
61865 );
61866 break;
61867 case HOUR_PATTERN:
61868 step = ONEDAY;
61869
61870 bndDelta = (
61871 (b1 < b0 ? 24 : 0) +
61872 (b1 - b0)
61873 ) * ONEHOUR;
61874
61875 t0 += b0 * ONEHOUR - (
61876 t0Date.getUTCHours() * ONEHOUR +
61877 t0Date.getUTCMinutes() * ONEMIN +
61878 t0Date.getUTCSeconds() * ONESEC +
61879 t0Date.getUTCMilliseconds()
61880 );
61881 break;
61882 default:
61883 t0 = Math.min(bnds[0], bnds[1]);
61884 t1 = Math.max(bnds[0], bnds[1]);
61885 step = t1 - t0;
61886 bndDelta = step;
61887 }
61888
61889 for(var t = t0; t < t1; t += step) {
61890 addBreak(t, t + bndDelta);
61891 }
61892 } else {
61893 var vals = Lib.simpleMap(brk.values, ax.d2c);
61894 for(var j = 0; j < vals.length; j++) {
61895 b0 = vals[j];
61896 b1 = b0 + brk.dvalue;
61897 addBreak(b0, b1);
61898 }
61899 }
61900 }
61901 }
61902
61903 rangebreaksOut.sort(function(a, b) { return a.min - b.min; });
61904
61905 return rangebreaksOut;
61906 };
61907
61908 // makeCalcdata: takes an x or y array and converts it
61909 // to a position on the axis object "ax"
61910 // inputs:
61911 // trace - a data object from gd.data
61912 // axLetter - a string, either 'x' or 'y', for which item
61913 // to convert (TODO: is this now always the same as
61914 // the first letter of ax._id?)
61915 // in case the expected data isn't there, make a list of
61916 // integers based on the opposite data
61917 ax.makeCalcdata = function(trace, axLetter, opts) {
61918 var arrayIn, arrayOut, i, len;
61919
61920 var axType = ax.type;
61921 var cal = axType === 'date' && trace[axLetter + 'calendar'];
61922
61923 if(axLetter in trace) {
61924 arrayIn = trace[axLetter];
61925 len = trace._length || Lib.minRowLength(arrayIn);
61926
61927 if(Lib.isTypedArray(arrayIn) && (axType === 'linear' || axType === 'log')) {
61928 if(len === arrayIn.length) {
61929 return arrayIn;
61930 } else if(arrayIn.subarray) {
61931 return arrayIn.subarray(0, len);
61932 }
61933 }
61934
61935 if(axType === 'multicategory') {
61936 return setMultiCategoryIndex(arrayIn, len);
61937 }
61938
61939 arrayOut = new Array(len);
61940 for(i = 0; i < len; i++) {
61941 arrayOut[i] = ax.d2c(arrayIn[i], 0, cal, opts);
61942 }
61943 } else {
61944 var v0 = ((axLetter + '0') in trace) ? ax.d2c(trace[axLetter + '0'], 0, cal) : 0;
61945 var dv = (trace['d' + axLetter]) ? Number(trace['d' + axLetter]) : 1;
61946
61947 // the opposing data, for size if we have x and dx etc
61948 arrayIn = trace[{x: 'y', y: 'x'}[axLetter]];
61949 len = trace._length || arrayIn.length;
61950 arrayOut = new Array(len);
61951
61952 for(i = 0; i < len; i++) {
61953 arrayOut[i] = v0 + i * dv;
61954 }
61955 }
61956
61957 // mask (i.e. set to BADNUM) coords that fall inside rangebreaks
61958 if(ax.rangebreaks) {
61959 for(i = 0; i < len; i++) {
61960 arrayOut[i] = ax.maskBreaks(arrayOut[i]);
61961 }
61962 }
61963
61964 return arrayOut;
61965 };
61966
61967 ax.isValidRange = function(range) {
61968 return (
61969 Array.isArray(range) &&
61970 range.length === 2 &&
61971 isNumeric(ax.r2l(range[0])) &&
61972 isNumeric(ax.r2l(range[1]))
61973 );
61974 };
61975
61976 ax.isPtWithinRange = function(d, calendar) {
61977 var coord = ax.c2l(d[axLetter], null, calendar);
61978 var r0 = ax.r2l(ax.range[0]);
61979 var r1 = ax.r2l(ax.range[1]);
61980
61981 if(r0 < r1) {
61982 return r0 <= coord && coord <= r1;
61983 } else {
61984 // Reversed axis case.
61985 return r1 <= coord && coord <= r0;
61986 }
61987 };
61988
61989 // should skip if not category nor multicategory
61990 ax.clearCalc = function() {
61991 var emptyCategories = function() {
61992 ax._categories = [];
61993 ax._categoriesMap = {};
61994 };
61995
61996 var matchGroups = fullLayout._axisMatchGroups;
61997
61998 if(matchGroups && matchGroups.length) {
61999 var found = false;
62000
62001 for(var i = 0; i < matchGroups.length; i++) {
62002 var group = matchGroups[i];
62003
62004 if(group[axId]) {
62005 found = true;
62006 var categories = null;
62007 var categoriesMap = null;
62008
62009 for(var axId2 in group) {
62010 var ax2 = fullLayout[axisIds.id2name(axId2)];
62011 if(ax2._categories) {
62012 categories = ax2._categories;
62013 categoriesMap = ax2._categoriesMap;
62014 break;
62015 }
62016 }
62017
62018 if(categories && categoriesMap) {
62019 ax._categories = categories;
62020 ax._categoriesMap = categoriesMap;
62021 } else {
62022 emptyCategories();
62023 }
62024 break;
62025 }
62026 }
62027 if(!found) emptyCategories();
62028 } else {
62029 emptyCategories();
62030 }
62031
62032 if(ax._initialCategories) {
62033 for(var j = 0; j < ax._initialCategories.length; j++) {
62034 setCategoryIndex(ax._initialCategories[j]);
62035 }
62036 }
62037 };
62038
62039 // sort the axis (and all the matching ones) by _initialCategories
62040 // returns the indices of the traces affected by the reordering
62041 ax.sortByInitialCategories = function() {
62042 var affectedTraces = [];
62043 var emptyCategories = function() {
62044 ax._categories = [];
62045 ax._categoriesMap = {};
62046 };
62047
62048 emptyCategories();
62049
62050 if(ax._initialCategories) {
62051 for(var j = 0; j < ax._initialCategories.length; j++) {
62052 setCategoryIndex(ax._initialCategories[j]);
62053 }
62054 }
62055
62056 affectedTraces = affectedTraces.concat(ax._traceIndices);
62057
62058 // Propagate to matching axes
62059 var group = ax._matchGroup;
62060 for(var axId2 in group) {
62061 if(axId === axId2) continue;
62062 var ax2 = fullLayout[axisIds.id2name(axId2)];
62063 ax2._categories = ax._categories;
62064 ax2._categoriesMap = ax._categoriesMap;
62065 affectedTraces = affectedTraces.concat(ax2._traceIndices);
62066 }
62067 return affectedTraces;
62068 };
62069
62070 // Propagate localization into the axis so that
62071 // methods in Axes can use it w/o having to pass fullLayout
62072 // Default (non-d3) number formatting uses separators directly
62073 // dates and d3-formatted numbers use the d3 locale
62074 // Fall back on default format for dummy axes that don't care about formatting
62075 var locale = fullLayout._d3locale;
62076 if(ax.type === 'date') {
62077 ax._dateFormat = locale ? locale.timeFormat.utc : d3.time.format.utc;
62078 ax._extraFormat = fullLayout._extraFormat;
62079 }
62080 // occasionally we need _numFormat to pass through
62081 // even though it won't be needed by this axis
62082 ax._separators = fullLayout.separators;
62083 ax._numFormat = locale ? locale.numberFormat : d3.format;
62084
62085 // and for bar charts and box plots: reset forced minimum tick spacing
62086 delete ax._minDtick;
62087 delete ax._forceTick0;
62088};
62089
62090},{"../../constants/numerical":158,"../../lib":178,"./axis_ids":225,"./constants":228,"d3":16,"fast-isnumeric":18}],243:[function(_dereq_,module,exports){
62091/**
62092* Copyright 2012-2020, Plotly, Inc.
62093* All rights reserved.
62094*
62095* This source code is licensed under the MIT license found in the
62096* LICENSE file in the root directory of this source tree.
62097*/
62098
62099
62100'use strict';
62101
62102var Lib = _dereq_('../../lib');
62103var layoutAttributes = _dereq_('./layout_attributes');
62104var handleArrayContainerDefaults = _dereq_('../array_container_defaults');
62105
62106module.exports = function handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options, config) {
62107 if(!config || config.pass === 1) {
62108 handlePrefixSuffix(containerIn, containerOut, coerce, axType, options);
62109 }
62110
62111 if(!config || config.pass === 2) {
62112 handleOtherDefaults(containerIn, containerOut, coerce, axType, options);
62113 }
62114};
62115
62116function handlePrefixSuffix(containerIn, containerOut, coerce, axType, options) {
62117 var showAttrDflt = getShowAttrDflt(containerIn);
62118
62119 var tickPrefix = coerce('tickprefix');
62120 if(tickPrefix) coerce('showtickprefix', showAttrDflt);
62121
62122 var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt);
62123 if(tickSuffix) coerce('showticksuffix', showAttrDflt);
62124}
62125
62126function handleOtherDefaults(containerIn, containerOut, coerce, axType, options) {
62127 var showAttrDflt = getShowAttrDflt(containerIn);
62128
62129 var tickPrefix = coerce('tickprefix');
62130 if(tickPrefix) coerce('showtickprefix', showAttrDflt);
62131
62132 var tickSuffix = coerce('ticksuffix', options.tickSuffixDflt);
62133 if(tickSuffix) coerce('showticksuffix', showAttrDflt);
62134
62135 var showTickLabels = coerce('showticklabels');
62136 if(showTickLabels) {
62137 var font = options.font || {};
62138 var contColor = containerOut.color;
62139 // as with titlefont.color, inherit axis.color only if one was
62140 // explicitly provided
62141 var dfltFontColor = (contColor && contColor !== layoutAttributes.color.dflt) ?
62142 contColor : font.color;
62143 Lib.coerceFont(coerce, 'tickfont', {
62144 family: font.family,
62145 size: font.size,
62146 color: dfltFontColor
62147 });
62148 coerce('tickangle');
62149
62150 if(axType !== 'category') {
62151 var tickFormat = coerce('tickformat');
62152
62153 handleArrayContainerDefaults(containerIn, containerOut, {
62154 name: 'tickformatstops',
62155 inclusionAttr: 'enabled',
62156 handleItemDefaults: tickformatstopDefaults
62157 });
62158 if(!containerOut.tickformatstops.length) {
62159 delete containerOut.tickformatstops;
62160 }
62161
62162 if(!tickFormat && axType !== 'date') {
62163 coerce('showexponent', showAttrDflt);
62164 coerce('exponentformat');
62165 coerce('separatethousands');
62166 }
62167 }
62168 }
62169}
62170
62171/*
62172 * Attributes 'showexponent', 'showtickprefix' and 'showticksuffix'
62173 * share values.
62174 *
62175 * If only 1 attribute is set,
62176 * the remaining attributes inherit that value.
62177 *
62178 * If 2 attributes are set to the same value,
62179 * the remaining attribute inherits that value.
62180 *
62181 * If 2 attributes are set to different values,
62182 * the remaining is set to its dflt value.
62183 *
62184 */
62185function getShowAttrDflt(containerIn) {
62186 var showAttrsAll = ['showexponent', 'showtickprefix', 'showticksuffix'];
62187 var showAttrs = showAttrsAll.filter(function(a) {
62188 return containerIn[a] !== undefined;
62189 });
62190 var sameVal = function(a) {
62191 return containerIn[a] === containerIn[showAttrs[0]];
62192 };
62193
62194 if(showAttrs.every(sameVal) || showAttrs.length === 1) {
62195 return containerIn[showAttrs[0]];
62196 }
62197}
62198
62199function tickformatstopDefaults(valueIn, valueOut) {
62200 function coerce(attr, dflt) {
62201 return Lib.coerce(valueIn, valueOut, layoutAttributes.tickformatstops, attr, dflt);
62202 }
62203
62204 var enabled = coerce('enabled');
62205 if(enabled) {
62206 coerce('dtickrange');
62207 coerce('value');
62208 }
62209}
62210
62211},{"../../lib":178,"../array_container_defaults":218,"./layout_attributes":236}],244:[function(_dereq_,module,exports){
62212/**
62213* Copyright 2012-2020, Plotly, Inc.
62214* All rights reserved.
62215*
62216* This source code is licensed under the MIT license found in the
62217* LICENSE file in the root directory of this source tree.
62218*/
62219
62220
62221'use strict';
62222
62223var Lib = _dereq_('../../lib');
62224
62225var layoutAttributes = _dereq_('./layout_attributes');
62226
62227
62228/**
62229 * options: inherits outerTicks from axes.handleAxisDefaults
62230 */
62231module.exports = function handleTickDefaults(containerIn, containerOut, coerce, options) {
62232 var tickLen = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'ticklen');
62233 var tickWidth = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickwidth');
62234 var tickColor = Lib.coerce2(containerIn, containerOut, layoutAttributes, 'tickcolor', containerOut.color);
62235 var showTicks = coerce('ticks', (options.outerTicks || tickLen || tickWidth || tickColor) ? 'outside' : '');
62236
62237 if(!showTicks) {
62238 delete containerOut.ticklen;
62239 delete containerOut.tickwidth;
62240 delete containerOut.tickcolor;
62241 }
62242};
62243
62244},{"../../lib":178,"./layout_attributes":236}],245:[function(_dereq_,module,exports){
62245/**
62246* Copyright 2012-2020, Plotly, Inc.
62247* All rights reserved.
62248*
62249* This source code is licensed under the MIT license found in the
62250* LICENSE file in the root directory of this source tree.
62251*/
62252
62253'use strict';
62254
62255var cleanTicks = _dereq_('./clean_ticks');
62256var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
62257
62258module.exports = function handleTickValueDefaults(containerIn, containerOut, coerce, axType) {
62259 function readInput(attr) {
62260 var v = containerIn[attr];
62261 return (
62262 v !== undefined
62263 ) ? v : (containerOut._template || {})[attr];
62264 }
62265
62266 var _tick0 = readInput('tick0');
62267 var _dtick = readInput('dtick');
62268 var _tickvals = readInput('tickvals');
62269
62270 var tickmodeDefault = isArrayOrTypedArray(_tickvals) ? 'array' :
62271 _dtick ? 'linear' :
62272 'auto';
62273 var tickmode = coerce('tickmode', tickmodeDefault);
62274
62275 if(tickmode === 'auto') coerce('nticks');
62276 else if(tickmode === 'linear') {
62277 // dtick is usually a positive number, but there are some
62278 // special strings available for log or date axes
62279 // tick0 also has special logic
62280 var dtick = containerOut.dtick = cleanTicks.dtick(
62281 _dtick, axType);
62282 containerOut.tick0 = cleanTicks.tick0(
62283 _tick0, axType, containerOut.calendar, dtick);
62284 } else if(axType !== 'multicategory') {
62285 var tickvals = coerce('tickvals');
62286 if(tickvals === undefined) containerOut.tickmode = 'auto';
62287 else coerce('ticktext');
62288 }
62289};
62290
62291},{"../../lib":178,"./clean_ticks":227}],246:[function(_dereq_,module,exports){
62292/**
62293* Copyright 2012-2020, Plotly, Inc.
62294* All rights reserved.
62295*
62296* This source code is licensed under the MIT license found in the
62297* LICENSE file in the root directory of this source tree.
62298*/
62299
62300'use strict';
62301
62302var d3 = _dereq_('d3');
62303
62304var Registry = _dereq_('../../registry');
62305var Lib = _dereq_('../../lib');
62306var Drawing = _dereq_('../../components/drawing');
62307var Axes = _dereq_('./axes');
62308
62309/**
62310 * transitionAxes
62311 *
62312 * transition axes from one set of ranges to another, using a svg
62313 * transformations, similar to during panning.
62314 *
62315 * @param {DOM element | object} gd
62316 * @param {array} edits : array of 'edits', each item with
62317 * - plotinfo {object} subplot object
62318 * - xr0 {array} initial x-range
62319 * - xr1 {array} end x-range
62320 * - yr0 {array} initial y-range
62321 * - yr1 {array} end y-range
62322 * @param {object} transitionOpts
62323 * @param {function} makeOnCompleteCallback
62324 */
62325module.exports = function transitionAxes(gd, edits, transitionOpts, makeOnCompleteCallback) {
62326 var fullLayout = gd._fullLayout;
62327
62328 // special case for redraw:false Plotly.animate that relies on this
62329 // to update axis-referenced layout components
62330 if(edits.length === 0) {
62331 Axes.redrawComponents(gd);
62332 return;
62333 }
62334
62335 function unsetSubplotTransform(subplot) {
62336 var xa = subplot.xaxis;
62337 var ya = subplot.yaxis;
62338
62339 fullLayout._defs.select('#' + subplot.clipId + '> rect')
62340 .call(Drawing.setTranslate, 0, 0)
62341 .call(Drawing.setScale, 1, 1);
62342
62343 subplot.plot
62344 .call(Drawing.setTranslate, xa._offset, ya._offset)
62345 .call(Drawing.setScale, 1, 1);
62346
62347 var traceGroups = subplot.plot.selectAll('.scatterlayer .trace');
62348
62349 // This is specifically directed at scatter traces, applying an inverse
62350 // scale to individual points to counteract the scale of the trace
62351 // as a whole:
62352 traceGroups.selectAll('.point')
62353 .call(Drawing.setPointGroupScale, 1, 1);
62354 traceGroups.selectAll('.textpoint')
62355 .call(Drawing.setTextPointsScale, 1, 1);
62356 traceGroups
62357 .call(Drawing.hideOutsideRangePoints, subplot);
62358 }
62359
62360 function updateSubplot(edit, progress) {
62361 var plotinfo = edit.plotinfo;
62362 var xa = plotinfo.xaxis;
62363 var ya = plotinfo.yaxis;
62364 var xlen = xa._length;
62365 var ylen = ya._length;
62366 var editX = !!edit.xr1;
62367 var editY = !!edit.yr1;
62368 var viewBox = [];
62369
62370 if(editX) {
62371 var xr0 = Lib.simpleMap(edit.xr0, xa.r2l);
62372 var xr1 = Lib.simpleMap(edit.xr1, xa.r2l);
62373 var dx0 = xr0[1] - xr0[0];
62374 var dx1 = xr1[1] - xr1[0];
62375 viewBox[0] = (xr0[0] * (1 - progress) + progress * xr1[0] - xr0[0]) / (xr0[1] - xr0[0]) * xlen;
62376 viewBox[2] = xlen * ((1 - progress) + progress * dx1 / dx0);
62377 xa.range[0] = xa.l2r(xr0[0] * (1 - progress) + progress * xr1[0]);
62378 xa.range[1] = xa.l2r(xr0[1] * (1 - progress) + progress * xr1[1]);
62379 } else {
62380 viewBox[0] = 0;
62381 viewBox[2] = xlen;
62382 }
62383
62384 if(editY) {
62385 var yr0 = Lib.simpleMap(edit.yr0, ya.r2l);
62386 var yr1 = Lib.simpleMap(edit.yr1, ya.r2l);
62387 var dy0 = yr0[1] - yr0[0];
62388 var dy1 = yr1[1] - yr1[0];
62389 viewBox[1] = (yr0[1] * (1 - progress) + progress * yr1[1] - yr0[1]) / (yr0[0] - yr0[1]) * ylen;
62390 viewBox[3] = ylen * ((1 - progress) + progress * dy1 / dy0);
62391 ya.range[0] = xa.l2r(yr0[0] * (1 - progress) + progress * yr1[0]);
62392 ya.range[1] = ya.l2r(yr0[1] * (1 - progress) + progress * yr1[1]);
62393 } else {
62394 viewBox[1] = 0;
62395 viewBox[3] = ylen;
62396 }
62397
62398 Axes.drawOne(gd, xa, {skipTitle: true});
62399 Axes.drawOne(gd, ya, {skipTitle: true});
62400 Axes.redrawComponents(gd, [xa._id, ya._id]);
62401
62402 var xScaleFactor = editX ? xlen / viewBox[2] : 1;
62403 var yScaleFactor = editY ? ylen / viewBox[3] : 1;
62404 var clipDx = editX ? viewBox[0] : 0;
62405 var clipDy = editY ? viewBox[1] : 0;
62406 var fracDx = editX ? (viewBox[0] / viewBox[2] * xlen) : 0;
62407 var fracDy = editY ? (viewBox[1] / viewBox[3] * ylen) : 0;
62408 var plotDx = xa._offset - fracDx;
62409 var plotDy = ya._offset - fracDy;
62410
62411 plotinfo.clipRect
62412 .call(Drawing.setTranslate, clipDx, clipDy)
62413 .call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor);
62414
62415 plotinfo.plot
62416 .call(Drawing.setTranslate, plotDx, plotDy)
62417 .call(Drawing.setScale, xScaleFactor, yScaleFactor);
62418
62419 // apply an inverse scale to individual points to counteract
62420 // the scale of the trace group.
62421 Drawing.setPointGroupScale(plotinfo.zoomScalePts, 1 / xScaleFactor, 1 / yScaleFactor);
62422 Drawing.setTextPointsScale(plotinfo.zoomScaleTxt, 1 / xScaleFactor, 1 / yScaleFactor);
62423 }
62424
62425 var onComplete;
62426 if(makeOnCompleteCallback) {
62427 // This module makes the choice whether or not it notifies Plotly.transition
62428 // about completion:
62429 onComplete = makeOnCompleteCallback();
62430 }
62431
62432 function transitionComplete() {
62433 var aobj = {};
62434
62435 for(var i = 0; i < edits.length; i++) {
62436 var edit = edits[i];
62437 var xa = edit.plotinfo.xaxis;
62438 var ya = edit.plotinfo.yaxis;
62439 if(edit.xr1) aobj[xa._name + '.range'] = edit.xr1.slice();
62440 if(edit.yr1) aobj[ya._name + '.range'] = edit.yr1.slice();
62441 }
62442
62443 // Signal that this transition has completed:
62444 onComplete && onComplete();
62445
62446 return Registry.call('relayout', gd, aobj).then(function() {
62447 for(var i = 0; i < edits.length; i++) {
62448 unsetSubplotTransform(edits[i].plotinfo);
62449 }
62450 });
62451 }
62452
62453 function transitionInterrupt() {
62454 var aobj = {};
62455
62456 for(var i = 0; i < edits.length; i++) {
62457 var edit = edits[i];
62458 var xa = edit.plotinfo.xaxis;
62459 var ya = edit.plotinfo.yaxis;
62460 if(edit.xr0) aobj[xa._name + '.range'] = edit.xr0.slice();
62461 if(edit.yr0) aobj[ya._name + '.range'] = edit.yr0.slice();
62462 }
62463
62464 return Registry.call('relayout', gd, aobj).then(function() {
62465 for(var i = 0; i < edits.length; i++) {
62466 unsetSubplotTransform(edits[i].plotinfo);
62467 }
62468 });
62469 }
62470
62471 var t1, t2, raf;
62472 var easeFn = d3.ease(transitionOpts.easing);
62473
62474 gd._transitionData._interruptCallbacks.push(function() {
62475 window.cancelAnimationFrame(raf);
62476 raf = null;
62477 return transitionInterrupt();
62478 });
62479
62480 function doFrame() {
62481 t2 = Date.now();
62482
62483 var tInterp = Math.min(1, (t2 - t1) / transitionOpts.duration);
62484 var progress = easeFn(tInterp);
62485
62486 for(var i = 0; i < edits.length; i++) {
62487 updateSubplot(edits[i], progress);
62488 }
62489
62490 if(t2 - t1 > transitionOpts.duration) {
62491 transitionComplete();
62492 raf = window.cancelAnimationFrame(doFrame);
62493 } else {
62494 raf = window.requestAnimationFrame(doFrame);
62495 }
62496 }
62497
62498 t1 = Date.now();
62499 raf = window.requestAnimationFrame(doFrame);
62500
62501 return Promise.resolve();
62502};
62503
62504},{"../../components/drawing":74,"../../lib":178,"../../registry":269,"./axes":222,"d3":16}],247:[function(_dereq_,module,exports){
62505/**
62506* Copyright 2012-2020, Plotly, Inc.
62507* All rights reserved.
62508*
62509* This source code is licensed under the MIT license found in the
62510* LICENSE file in the root directory of this source tree.
62511*/
62512
62513'use strict';
62514
62515var traceIs = _dereq_('../../registry').traceIs;
62516var autoType = _dereq_('./axis_autotype');
62517
62518/*
62519 * data: the plot data to use in choosing auto type
62520 * name: axis object name (ie 'xaxis') if one should be stored
62521 */
62522module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) {
62523 var axType = coerce('type', (options.splomStash || {}).type);
62524
62525 if(axType === '-') {
62526 setAutoType(containerOut, options.data);
62527
62528 if(containerOut.type === '-') {
62529 containerOut.type = 'linear';
62530 } else {
62531 // copy autoType back to input axis
62532 // note that if this object didn't exist
62533 // in the input layout, we have to put it in
62534 // this happens in the main supplyDefaults function
62535 containerIn.type = containerOut.type;
62536 }
62537 }
62538};
62539
62540function setAutoType(ax, data) {
62541 // new logic: let people specify any type they want,
62542 // only autotype if type is '-'
62543 if(ax.type !== '-') return;
62544
62545 var id = ax._id;
62546 var axLetter = id.charAt(0);
62547 var i;
62548
62549 // support 3d
62550 if(id.indexOf('scene') !== -1) id = axLetter;
62551
62552 var d0 = getFirstNonEmptyTrace(data, id, axLetter);
62553 if(!d0) return;
62554
62555 // first check for histograms, as the count direction
62556 // should always default to a linear axis
62557 if(d0.type === 'histogram' &&
62558 axLetter === {v: 'y', h: 'x'}[d0.orientation || 'v']
62559 ) {
62560 ax.type = 'linear';
62561 return;
62562 }
62563
62564 var calAttr = axLetter + 'calendar';
62565 var calendar = d0[calAttr];
62566 var opts = {noMultiCategory: !traceIs(d0, 'cartesian') || traceIs(d0, 'noMultiCategory')};
62567
62568 // To not confuse 2D x/y used for per-box sample points for multicategory coordinates
62569 if(d0.type === 'box' && d0._hasPreCompStats &&
62570 axLetter === {h: 'x', v: 'y'}[d0.orientation || 'v']
62571 ) {
62572 opts.noMultiCategory = true;
62573 }
62574
62575 // check all boxes on this x axis to see
62576 // if they're dates, numbers, or categories
62577 if(isBoxWithoutPositionCoords(d0, axLetter)) {
62578 var posLetter = getBoxPosLetter(d0);
62579 var boxPositions = [];
62580
62581 for(i = 0; i < data.length; i++) {
62582 var trace = data[i];
62583 if(!traceIs(trace, 'box-violin') || (trace[axLetter + 'axis'] || axLetter) !== id) continue;
62584
62585 if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]);
62586 else if(trace.name !== undefined) boxPositions.push(trace.name);
62587 else boxPositions.push('text');
62588
62589 if(trace[calAttr] !== calendar) calendar = undefined;
62590 }
62591
62592 ax.type = autoType(boxPositions, calendar, opts);
62593 } else if(d0.type === 'splom') {
62594 var dimensions = d0.dimensions;
62595 var dim = dimensions[d0._axesDim[id]];
62596 if(dim.visible) ax.type = autoType(dim.values, calendar, opts);
62597 } else {
62598 ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar, opts);
62599 }
62600}
62601
62602function getFirstNonEmptyTrace(data, id, axLetter) {
62603 for(var i = 0; i < data.length; i++) {
62604 var trace = data[i];
62605
62606 if(trace.type === 'splom' &&
62607 trace._length > 0 &&
62608 (trace['_' + axLetter + 'axes'] || {})[id]
62609 ) {
62610 return trace;
62611 }
62612
62613 if((trace[axLetter + 'axis'] || axLetter) === id) {
62614 if(isBoxWithoutPositionCoords(trace, axLetter)) {
62615 return trace;
62616 } else if((trace[axLetter] || []).length || trace[axLetter + '0']) {
62617 return trace;
62618 }
62619 }
62620 }
62621}
62622
62623function getBoxPosLetter(trace) {
62624 return {v: 'x', h: 'y'}[trace.orientation || 'v'];
62625}
62626
62627function isBoxWithoutPositionCoords(trace, axLetter) {
62628 var posLetter = getBoxPosLetter(trace);
62629 var isBox = traceIs(trace, 'box-violin');
62630 var isCandlestick = traceIs(trace._fullInput || {}, 'candlestick');
62631
62632 return (
62633 isBox &&
62634 !isCandlestick &&
62635 axLetter === posLetter &&
62636 trace[posLetter] === undefined &&
62637 trace[posLetter + '0'] === undefined
62638 );
62639}
62640
62641},{"../../registry":269,"./axis_autotype":223}],248:[function(_dereq_,module,exports){
62642/**
62643* Copyright 2012-2020, Plotly, Inc.
62644* All rights reserved.
62645*
62646* This source code is licensed under the MIT license found in the
62647* LICENSE file in the root directory of this source tree.
62648*/
62649
62650'use strict';
62651
62652var Registry = _dereq_('../registry');
62653var Lib = _dereq_('../lib');
62654
62655/*
62656 * Create or update an observer. This function is designed to be
62657 * idempotent so that it can be called over and over as the component
62658 * updates, and will attach and detach listeners as needed.
62659 *
62660 * @param {optional object} container
62661 * An object on which the observer is stored. This is the mechanism
62662 * by which it is idempotent. If it already exists, another won't be
62663 * added. Each time it's called, the value lookup table is updated.
62664 * @param {array} commandList
62665 * An array of commands, following either `buttons` of `updatemenus`
62666 * or `steps` of `sliders`.
62667 * @param {function} onchange
62668 * A listener called when the value is changed. Receives data object
62669 * with information about the new state.
62670 */
62671exports.manageCommandObserver = function(gd, container, commandList, onchange) {
62672 var ret = {};
62673 var enabled = true;
62674
62675 if(container && container._commandObserver) {
62676 ret = container._commandObserver;
62677 }
62678
62679 if(!ret.cache) {
62680 ret.cache = {};
62681 }
62682
62683 // Either create or just recompute this:
62684 ret.lookupTable = {};
62685
62686 var binding = exports.hasSimpleAPICommandBindings(gd, commandList, ret.lookupTable);
62687
62688 if(container && container._commandObserver) {
62689 if(!binding) {
62690 // If container exists and there are no longer any bindings,
62691 // remove existing:
62692 if(container._commandObserver.remove) {
62693 container._commandObserver.remove();
62694 container._commandObserver = null;
62695 return ret;
62696 }
62697 } else {
62698 // If container exists and there *are* bindings, then the lookup
62699 // table should have been updated and check is already attached,
62700 // so there's nothing to be done:
62701 return ret;
62702 }
62703 }
62704
62705 // Determine whether there's anything to do for this binding:
62706
62707 if(binding) {
62708 // Build the cache:
62709 bindingValueHasChanged(gd, binding, ret.cache);
62710
62711 ret.check = function check() {
62712 if(!enabled) return;
62713
62714 var update = bindingValueHasChanged(gd, binding, ret.cache);
62715
62716 if(update.changed && onchange) {
62717 // Disable checks for the duration of this command in order to avoid
62718 // infinite loops:
62719 if(ret.lookupTable[update.value] !== undefined) {
62720 ret.disable();
62721 Promise.resolve(onchange({
62722 value: update.value,
62723 type: binding.type,
62724 prop: binding.prop,
62725 traces: binding.traces,
62726 index: ret.lookupTable[update.value]
62727 })).then(ret.enable, ret.enable);
62728 }
62729 }
62730
62731 return update.changed;
62732 };
62733
62734 var checkEvents = [
62735 'plotly_relayout',
62736 'plotly_redraw',
62737 'plotly_restyle',
62738 'plotly_update',
62739 'plotly_animatingframe',
62740 'plotly_afterplot'
62741 ];
62742
62743 for(var i = 0; i < checkEvents.length; i++) {
62744 gd._internalOn(checkEvents[i], ret.check);
62745 }
62746
62747 ret.remove = function() {
62748 for(var i = 0; i < checkEvents.length; i++) {
62749 gd._removeInternalListener(checkEvents[i], ret.check);
62750 }
62751 };
62752 } else {
62753 // TODO: It'd be really neat to actually give a *reason* for this, but at least a warning
62754 // is a start
62755 Lib.log('Unable to automatically bind plot updates to API command');
62756
62757 ret.lookupTable = {};
62758 ret.remove = function() {};
62759 }
62760
62761 ret.disable = function disable() {
62762 enabled = false;
62763 };
62764
62765 ret.enable = function enable() {
62766 enabled = true;
62767 };
62768
62769 if(container) {
62770 container._commandObserver = ret;
62771 }
62772
62773 return ret;
62774};
62775
62776/*
62777 * This function checks to see if an array of objects containing
62778 * method and args properties is compatible with automatic two-way
62779 * binding. The criteria right now are that
62780 *
62781 * 1. multiple traces may be affected
62782 * 2. only one property may be affected
62783 * 3. the same property must be affected by all commands
62784 */
62785exports.hasSimpleAPICommandBindings = function(gd, commandList, bindingsByValue) {
62786 var i;
62787 var n = commandList.length;
62788
62789 var refBinding;
62790
62791 for(i = 0; i < n; i++) {
62792 var binding;
62793 var command = commandList[i];
62794 var method = command.method;
62795 var args = command.args;
62796
62797 if(!Array.isArray(args)) args = [];
62798
62799 // If any command has no method, refuse to bind:
62800 if(!method) {
62801 return false;
62802 }
62803 var bindings = exports.computeAPICommandBindings(gd, method, args);
62804
62805 // Right now, handle one and *only* one property being set:
62806 if(bindings.length !== 1) {
62807 return false;
62808 }
62809
62810 if(!refBinding) {
62811 refBinding = bindings[0];
62812 if(Array.isArray(refBinding.traces)) {
62813 refBinding.traces.sort();
62814 }
62815 } else {
62816 binding = bindings[0];
62817 if(binding.type !== refBinding.type) {
62818 return false;
62819 }
62820 if(binding.prop !== refBinding.prop) {
62821 return false;
62822 }
62823 if(Array.isArray(refBinding.traces)) {
62824 if(Array.isArray(binding.traces)) {
62825 binding.traces.sort();
62826 for(var j = 0; j < refBinding.traces.length; j++) {
62827 if(refBinding.traces[j] !== binding.traces[j]) {
62828 return false;
62829 }
62830 }
62831 } else {
62832 return false;
62833 }
62834 } else {
62835 if(binding.prop !== refBinding.prop) {
62836 return false;
62837 }
62838 }
62839 }
62840
62841 binding = bindings[0];
62842 var value = binding.value;
62843 if(Array.isArray(value)) {
62844 if(value.length === 1) {
62845 value = value[0];
62846 } else {
62847 return false;
62848 }
62849 }
62850 if(bindingsByValue) {
62851 bindingsByValue[value] = i;
62852 }
62853 }
62854
62855 return refBinding;
62856};
62857
62858function bindingValueHasChanged(gd, binding, cache) {
62859 var container, value, obj;
62860 var changed = false;
62861
62862 if(binding.type === 'data') {
62863 // If it's data, we need to get a trace. Based on the limited scope
62864 // of what we cover, we can just take the first trace from the list,
62865 // or otherwise just the first trace:
62866 container = gd._fullData[binding.traces !== null ? binding.traces[0] : 0];
62867 } else if(binding.type === 'layout') {
62868 container = gd._fullLayout;
62869 } else {
62870 return false;
62871 }
62872
62873 value = Lib.nestedProperty(container, binding.prop).get();
62874
62875 obj = cache[binding.type] = cache[binding.type] || {};
62876
62877 if(obj.hasOwnProperty(binding.prop)) {
62878 if(obj[binding.prop] !== value) {
62879 changed = true;
62880 }
62881 }
62882
62883 obj[binding.prop] = value;
62884
62885 return {
62886 changed: changed,
62887 value: value
62888 };
62889}
62890
62891/*
62892 * Execute an API command. There's really not much to this; it just provides
62893 * a common hook so that implementations don't need to be synchronized across
62894 * multiple components with the ability to invoke API commands.
62895 *
62896 * @param {string} method
62897 * The name of the plotly command to execute. Must be one of 'animate',
62898 * 'restyle', 'relayout', 'update'.
62899 * @param {array} args
62900 * A list of arguments passed to the API command
62901 */
62902exports.executeAPICommand = function(gd, method, args) {
62903 if(method === 'skip') return Promise.resolve();
62904
62905 var _method = Registry.apiMethodRegistry[method];
62906 var allArgs = [gd];
62907 if(!Array.isArray(args)) args = [];
62908
62909 for(var i = 0; i < args.length; i++) {
62910 allArgs.push(args[i]);
62911 }
62912
62913 return _method.apply(null, allArgs).catch(function(err) {
62914 Lib.warn('API call to Plotly.' + method + ' rejected.', err);
62915 return Promise.reject(err);
62916 });
62917};
62918
62919exports.computeAPICommandBindings = function(gd, method, args) {
62920 var bindings;
62921
62922 if(!Array.isArray(args)) args = [];
62923
62924 switch(method) {
62925 case 'restyle':
62926 bindings = computeDataBindings(gd, args);
62927 break;
62928 case 'relayout':
62929 bindings = computeLayoutBindings(gd, args);
62930 break;
62931 case 'update':
62932 bindings = computeDataBindings(gd, [args[0], args[2]])
62933 .concat(computeLayoutBindings(gd, [args[1]]));
62934 break;
62935 case 'animate':
62936 bindings = computeAnimateBindings(gd, args);
62937 break;
62938 default:
62939 // This is the case where intelligent logic about what affects
62940 // this command is not implemented. It causes no ill effects.
62941 // For example, addFrames simply won't bind to a control component.
62942 bindings = [];
62943 }
62944 return bindings;
62945};
62946
62947function computeAnimateBindings(gd, args) {
62948 // We'll assume that the only relevant modification an animation
62949 // makes that's meaningfully tracked is the frame:
62950 if(Array.isArray(args[0]) && args[0].length === 1 && ['string', 'number'].indexOf(typeof args[0][0]) !== -1) {
62951 return [{type: 'layout', prop: '_currentFrame', value: args[0][0].toString()}];
62952 } else {
62953 return [];
62954 }
62955}
62956
62957function computeLayoutBindings(gd, args) {
62958 var bindings = [];
62959
62960 var astr = args[0];
62961 var aobj = {};
62962 if(typeof astr === 'string') {
62963 aobj[astr] = args[1];
62964 } else if(Lib.isPlainObject(astr)) {
62965 aobj = astr;
62966 } else {
62967 return bindings;
62968 }
62969
62970 crawl(aobj, function(path, attrName, attr) {
62971 bindings.push({type: 'layout', prop: path, value: attr});
62972 }, '', 0);
62973
62974 return bindings;
62975}
62976
62977function computeDataBindings(gd, args) {
62978 var traces, astr, val, aobj;
62979 var bindings = [];
62980
62981 // Logic copied from Plotly.restyle:
62982 astr = args[0];
62983 val = args[1];
62984 traces = args[2];
62985 aobj = {};
62986 if(typeof astr === 'string') {
62987 aobj[astr] = val;
62988 } else if(Lib.isPlainObject(astr)) {
62989 // the 3-arg form
62990 aobj = astr;
62991
62992 if(traces === undefined) {
62993 traces = val;
62994 }
62995 } else {
62996 return bindings;
62997 }
62998
62999 if(traces === undefined) {
63000 // Explicitly assign this to null instead of undefined:
63001 traces = null;
63002 }
63003
63004 crawl(aobj, function(path, attrName, _attr) {
63005 var thisTraces;
63006 var attr;
63007
63008 if(Array.isArray(_attr)) {
63009 attr = _attr.slice();
63010
63011 var nAttr = Math.min(attr.length, gd.data.length);
63012 if(traces) {
63013 nAttr = Math.min(nAttr, traces.length);
63014 }
63015 thisTraces = [];
63016 for(var j = 0; j < nAttr; j++) {
63017 thisTraces[j] = traces ? traces[j] : j;
63018 }
63019 } else {
63020 attr = _attr;
63021 thisTraces = traces ? traces.slice() : null;
63022 }
63023
63024 // Convert [7] to just 7 when traces is null:
63025 if(thisTraces === null) {
63026 if(Array.isArray(attr)) {
63027 attr = attr[0];
63028 }
63029 } else if(Array.isArray(thisTraces)) {
63030 if(!Array.isArray(attr)) {
63031 var tmp = attr;
63032 attr = [];
63033 for(var i = 0; i < thisTraces.length; i++) {
63034 attr[i] = tmp;
63035 }
63036 }
63037 attr.length = Math.min(thisTraces.length, attr.length);
63038 }
63039
63040 bindings.push({
63041 type: 'data',
63042 prop: path,
63043 traces: thisTraces,
63044 value: attr
63045 });
63046 }, '', 0);
63047
63048 return bindings;
63049}
63050
63051function crawl(attrs, callback, path, depth) {
63052 Object.keys(attrs).forEach(function(attrName) {
63053 var attr = attrs[attrName];
63054
63055 if(attrName[0] === '_') return;
63056
63057 var thisPath = path + (depth > 0 ? '.' : '') + attrName;
63058
63059 if(Lib.isPlainObject(attr)) {
63060 crawl(attr, callback, thisPath, depth + 1);
63061 } else {
63062 // Only execute the callback on leaf nodes:
63063 callback(thisPath, attrName, attr);
63064 }
63065 });
63066}
63067
63068},{"../lib":178,"../registry":269}],249:[function(_dereq_,module,exports){
63069/**
63070* Copyright 2012-2020, Plotly, Inc.
63071* All rights reserved.
63072*
63073* This source code is licensed under the MIT license found in the
63074* LICENSE file in the root directory of this source tree.
63075*/
63076
63077'use strict';
63078
63079var extendFlat = _dereq_('../lib/extend').extendFlat;
63080
63081/**
63082 * Make a xy domain attribute group
63083 *
63084 * @param {object} opts
63085 * @param {string}
63086 * opts.name: name to be inserted in the default description
63087 * @param {boolean}
63088 * opts.trace: set to true for trace containers
63089 * @param {string}
63090 * opts.editType: editType for all pieces
63091 * @param {boolean}
63092 * opts.noGridCell: set to true to omit `row` and `column`
63093 *
63094 * @param {object} extra
63095 * @param {string}
63096 * extra.description: extra description. N.B we use
63097 * a separate extra container to make it compatible with
63098 * the compress_attributes transform.
63099 *
63100 * @return {object} attributes object containing {x,y} as specified
63101 */
63102exports.attributes = function(opts, extra) {
63103 opts = opts || {};
63104 extra = extra || {};
63105
63106 var base = {
63107 valType: 'info_array',
63108
63109 editType: opts.editType,
63110 items: [
63111 {valType: 'number', min: 0, max: 1, editType: opts.editType},
63112 {valType: 'number', min: 0, max: 1, editType: opts.editType}
63113 ],
63114 dflt: [0, 1]
63115 };
63116
63117 var namePart = opts.name ? opts.name + ' ' : '';
63118 var contPart = opts.trace ? 'trace ' : 'subplot ';
63119 var descPart = extra.description ? ' ' + extra.description : '';
63120
63121 var out = {
63122 x: extendFlat({}, base, {
63123
63124 }),
63125 y: extendFlat({}, base, {
63126
63127 }),
63128 editType: opts.editType
63129 };
63130
63131 if(!opts.noGridCell) {
63132 out.row = {
63133 valType: 'integer',
63134 min: 0,
63135 dflt: 0,
63136
63137 editType: opts.editType,
63138
63139 };
63140 out.column = {
63141 valType: 'integer',
63142 min: 0,
63143 dflt: 0,
63144
63145 editType: opts.editType,
63146
63147 };
63148 }
63149
63150 return out;
63151};
63152
63153exports.defaults = function(containerOut, layout, coerce, dfltDomains) {
63154 var dfltX = (dfltDomains && dfltDomains.x) || [0, 1];
63155 var dfltY = (dfltDomains && dfltDomains.y) || [0, 1];
63156
63157 var grid = layout.grid;
63158 if(grid) {
63159 var column = coerce('domain.column');
63160 if(column !== undefined) {
63161 if(column < grid.columns) dfltX = grid._domains.x[column];
63162 else delete containerOut.domain.column;
63163 }
63164
63165 var row = coerce('domain.row');
63166 if(row !== undefined) {
63167 if(row < grid.rows) dfltY = grid._domains.y[row];
63168 else delete containerOut.domain.row;
63169 }
63170 }
63171
63172 var x = coerce('domain.x', dfltX);
63173 var y = coerce('domain.y', dfltY);
63174
63175 // don't accept bad input data
63176 if(!(x[0] < x[1])) containerOut.domain.x = dfltX.slice();
63177 if(!(y[0] < y[1])) containerOut.domain.y = dfltY.slice();
63178};
63179
63180},{"../lib/extend":173}],250:[function(_dereq_,module,exports){
63181/**
63182* Copyright 2012-2020, Plotly, Inc.
63183* All rights reserved.
63184*
63185* This source code is licensed under the MIT license found in the
63186* LICENSE file in the root directory of this source tree.
63187*/
63188
63189'use strict';
63190
63191/*
63192 * make a font attribute group
63193 *
63194 * @param {object} opts
63195 * @param {string}
63196 * opts.description: where & how this font is used
63197 * @param {optional bool} arrayOk:
63198 * should each part (family, size, color) be arrayOk? default false.
63199 * @param {string} editType:
63200 * the editType for all pieces of this font
63201 * @param {optional string} colorEditType:
63202 * a separate editType just for color
63203 *
63204 * @return {object} attributes object containing {family, size, color} as specified
63205 */
63206module.exports = function(opts) {
63207 var editType = opts.editType;
63208 var colorEditType = opts.colorEditType;
63209 if(colorEditType === undefined) colorEditType = editType;
63210 var attrs = {
63211 family: {
63212 valType: 'string',
63213
63214 noBlank: true,
63215 strict: true,
63216 editType: editType,
63217
63218 },
63219 size: {
63220 valType: 'number',
63221
63222 min: 1,
63223 editType: editType
63224 },
63225 color: {
63226 valType: 'color',
63227
63228 editType: colorEditType
63229 },
63230 editType: editType,
63231 // blank strings so compress_attributes can remove
63232 // TODO - that's uber hacky... better solution?
63233
63234 };
63235
63236 if(opts.arrayOk) {
63237 attrs.family.arrayOk = true;
63238 attrs.size.arrayOk = true;
63239 attrs.color.arrayOk = true;
63240 }
63241
63242 return attrs;
63243};
63244
63245},{}],251:[function(_dereq_,module,exports){
63246/**
63247* Copyright 2012-2020, Plotly, Inc.
63248* All rights reserved.
63249*
63250* This source code is licensed under the MIT license found in the
63251* LICENSE file in the root directory of this source tree.
63252*/
63253
63254'use strict';
63255
63256module.exports = {
63257 _isLinkedToArray: 'frames_entry',
63258
63259 group: {
63260 valType: 'string',
63261
63262
63263 },
63264 name: {
63265 valType: 'string',
63266
63267
63268 },
63269 traces: {
63270 valType: 'any',
63271
63272
63273 },
63274 baseframe: {
63275 valType: 'string',
63276
63277
63278 },
63279 data: {
63280 valType: 'any',
63281
63282
63283 },
63284 layout: {
63285 valType: 'any',
63286
63287
63288 }
63289};
63290
63291},{}],252:[function(_dereq_,module,exports){
63292/**
63293* Copyright 2012-2020, Plotly, Inc.
63294* All rights reserved.
63295*
63296* This source code is licensed under the MIT license found in the
63297* LICENSE file in the root directory of this source tree.
63298*/
63299
63300'use strict';
63301
63302var Registry = _dereq_('../registry');
63303var SUBPLOT_PATTERN = _dereq_('./cartesian/constants').SUBPLOT_PATTERN;
63304
63305/**
63306 * Get calcdata trace(s) associated with a given subplot
63307 *
63308 * @param {array} calcData: as in gd.calcdata
63309 * @param {string} type: subplot type
63310 * @param {string} subplotId: subplot id to look for
63311 *
63312 * @return {array} array of calcdata traces
63313 */
63314exports.getSubplotCalcData = function(calcData, type, subplotId) {
63315 var basePlotModule = Registry.subplotsRegistry[type];
63316 if(!basePlotModule) return [];
63317
63318 var attr = basePlotModule.attr;
63319 var subplotCalcData = [];
63320
63321 for(var i = 0; i < calcData.length; i++) {
63322 var calcTrace = calcData[i];
63323 var trace = calcTrace[0].trace;
63324
63325 if(trace[attr] === subplotId) subplotCalcData.push(calcTrace);
63326 }
63327
63328 return subplotCalcData;
63329};
63330/**
63331 * Get calcdata trace(s) that can be plotted with a given module
63332 * NOTE: this isn't necessarily just exactly matching trace type,
63333 * if multiple trace types use the same plotting routine, they will be
63334 * collected here.
63335 * In order to not plot the same thing multiple times, we return two arrays,
63336 * the calcdata we *will* plot with this module, and the ones we *won't*
63337 *
63338 * @param {array} calcdata: as in gd.calcdata
63339 * @param {object|string|fn} arg1:
63340 * the plotting module, or its name, or its plot method
63341 *
63342 * @return {array[array]} [foundCalcdata, remainingCalcdata]
63343 */
63344exports.getModuleCalcData = function(calcdata, arg1) {
63345 var moduleCalcData = [];
63346 var remainingCalcData = [];
63347
63348 var plotMethod;
63349 if(typeof arg1 === 'string') {
63350 plotMethod = Registry.getModule(arg1).plot;
63351 } else if(typeof arg1 === 'function') {
63352 plotMethod = arg1;
63353 } else {
63354 plotMethod = arg1.plot;
63355 }
63356 if(!plotMethod) {
63357 return [moduleCalcData, calcdata];
63358 }
63359
63360 for(var i = 0; i < calcdata.length; i++) {
63361 var cd = calcdata[i];
63362 var trace = cd[0].trace;
63363 // N.B.
63364 // - 'legendonly' traces do not make it past here
63365 // - skip over 'visible' traces that got trimmed completely during calc transforms
63366 if(trace.visible !== true || trace._length === 0) continue;
63367
63368 // group calcdata trace not by 'module' (as the name of this function
63369 // would suggest), but by 'module plot method' so that if some traces
63370 // share the same module plot method (e.g. bar and histogram), we
63371 // only call it one!
63372 if(trace._module.plot === plotMethod) {
63373 moduleCalcData.push(cd);
63374 } else {
63375 remainingCalcData.push(cd);
63376 }
63377 }
63378
63379 return [moduleCalcData, remainingCalcData];
63380};
63381
63382/**
63383 * Get the data trace(s) associated with a given subplot.
63384 *
63385 * @param {array} data plotly full data array.
63386 * @param {string} type subplot type to look for.
63387 * @param {string} subplotId subplot id to look for.
63388 *
63389 * @return {array} list of trace objects.
63390 *
63391 */
63392exports.getSubplotData = function getSubplotData(data, type, subplotId) {
63393 if(!Registry.subplotsRegistry[type]) return [];
63394
63395 var attr = Registry.subplotsRegistry[type].attr;
63396 var subplotData = [];
63397 var trace, subplotX, subplotY;
63398
63399 if(type === 'gl2d') {
63400 var spmatch = subplotId.match(SUBPLOT_PATTERN);
63401 subplotX = 'x' + spmatch[1];
63402 subplotY = 'y' + spmatch[2];
63403 }
63404
63405 for(var i = 0; i < data.length; i++) {
63406 trace = data[i];
63407
63408 if(type === 'gl2d' && Registry.traceIs(trace, 'gl2d')) {
63409 if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) {
63410 subplotData.push(trace);
63411 }
63412 } else {
63413 if(trace[attr] === subplotId) subplotData.push(trace);
63414 }
63415 }
63416
63417 return subplotData;
63418};
63419
63420},{"../registry":269,"./cartesian/constants":228}],253:[function(_dereq_,module,exports){
63421/**
63422* Copyright 2012-2020, Plotly, Inc.
63423* All rights reserved.
63424*
63425* This source code is licensed under the MIT license found in the
63426* LICENSE file in the root directory of this source tree.
63427*/
63428
63429
63430'use strict';
63431
63432function xformMatrix(m, v) {
63433 var out = [0, 0, 0, 0];
63434 var i, j;
63435
63436 for(i = 0; i < 4; ++i) {
63437 for(j = 0; j < 4; ++j) {
63438 out[j] += m[4 * i + j] * v[i];
63439 }
63440 }
63441
63442 return out;
63443}
63444
63445function project(camera, v) {
63446 var p = xformMatrix(camera.projection,
63447 xformMatrix(camera.view,
63448 xformMatrix(camera.model, [v[0], v[1], v[2], 1])));
63449 return p;
63450}
63451
63452module.exports = project;
63453
63454},{}],254:[function(_dereq_,module,exports){
63455/**
63456* Copyright 2012-2020, Plotly, Inc.
63457* All rights reserved.
63458*
63459* This source code is licensed under the MIT license found in the
63460* LICENSE file in the root directory of this source tree.
63461*/
63462
63463'use strict';
63464
63465var fontAttrs = _dereq_('./font_attributes');
63466var animationAttrs = _dereq_('./animation_attributes');
63467var colorAttrs = _dereq_('../components/color/attributes');
63468var drawNewShapeAttrs = _dereq_('../components/shapes/draw_newshape/attributes');
63469var padAttrs = _dereq_('./pad_attributes');
63470var extendFlat = _dereq_('../lib/extend').extendFlat;
63471
63472var globalFont = fontAttrs({
63473 editType: 'calc',
63474
63475});
63476globalFont.family.dflt = '"Open Sans", verdana, arial, sans-serif';
63477globalFont.size.dflt = 12;
63478globalFont.color.dflt = colorAttrs.defaultLine;
63479
63480module.exports = {
63481 font: globalFont,
63482 title: {
63483 text: {
63484 valType: 'string',
63485
63486 editType: 'layoutstyle',
63487
63488 },
63489 font: fontAttrs({
63490 editType: 'layoutstyle',
63491
63492 }),
63493 xref: {
63494 valType: 'enumerated',
63495 dflt: 'container',
63496 values: ['container', 'paper'],
63497
63498 editType: 'layoutstyle',
63499
63500 },
63501 yref: {
63502 valType: 'enumerated',
63503 dflt: 'container',
63504 values: ['container', 'paper'],
63505
63506 editType: 'layoutstyle',
63507
63508 },
63509 x: {
63510 valType: 'number',
63511 min: 0,
63512 max: 1,
63513 dflt: 0.5,
63514
63515 editType: 'layoutstyle',
63516
63517 },
63518 y: {
63519 valType: 'number',
63520 min: 0,
63521 max: 1,
63522 dflt: 'auto',
63523
63524 editType: 'layoutstyle',
63525
63526 },
63527 xanchor: {
63528 valType: 'enumerated',
63529 dflt: 'auto',
63530 values: ['auto', 'left', 'center', 'right'],
63531
63532 editType: 'layoutstyle',
63533
63534 },
63535 yanchor: {
63536 valType: 'enumerated',
63537 dflt: 'auto',
63538 values: ['auto', 'top', 'middle', 'bottom'],
63539
63540 editType: 'layoutstyle',
63541
63542 },
63543 pad: extendFlat(padAttrs({editType: 'layoutstyle'}), {
63544
63545 }),
63546 editType: 'layoutstyle'
63547 },
63548 uniformtext: {
63549 mode: {
63550 valType: 'enumerated',
63551 values: [false, 'hide', 'show'],
63552 dflt: false,
63553
63554 editType: 'plot',
63555
63556 },
63557 minsize: {
63558 valType: 'number',
63559 min: 0,
63560 dflt: 0,
63561
63562 editType: 'plot',
63563
63564 },
63565 editType: 'plot'
63566 },
63567 autosize: {
63568 valType: 'boolean',
63569
63570 dflt: false,
63571 // autosize, width, and height get special editType treatment in _relayout
63572 // so we can handle noop resizes more efficiently
63573 editType: 'none',
63574
63575 },
63576 width: {
63577 valType: 'number',
63578
63579 min: 10,
63580 dflt: 700,
63581 editType: 'plot',
63582
63583 },
63584 height: {
63585 valType: 'number',
63586
63587 min: 10,
63588 dflt: 450,
63589 editType: 'plot',
63590
63591 },
63592 margin: {
63593 l: {
63594 valType: 'number',
63595
63596 min: 0,
63597 dflt: 80,
63598 editType: 'plot',
63599
63600 },
63601 r: {
63602 valType: 'number',
63603
63604 min: 0,
63605 dflt: 80,
63606 editType: 'plot',
63607
63608 },
63609 t: {
63610 valType: 'number',
63611
63612 min: 0,
63613 dflt: 100,
63614 editType: 'plot',
63615
63616 },
63617 b: {
63618 valType: 'number',
63619
63620 min: 0,
63621 dflt: 80,
63622 editType: 'plot',
63623
63624 },
63625 pad: {
63626 valType: 'number',
63627
63628 min: 0,
63629 dflt: 0,
63630 editType: 'plot',
63631
63632 },
63633 autoexpand: {
63634 valType: 'boolean',
63635
63636 dflt: true,
63637 editType: 'plot',
63638
63639 },
63640 editType: 'plot'
63641 },
63642 paper_bgcolor: {
63643 valType: 'color',
63644
63645 dflt: colorAttrs.background,
63646 editType: 'plot',
63647
63648 },
63649 plot_bgcolor: {
63650 // defined here, but set in cartesian.supplyLayoutDefaults
63651 // because it needs to know if there are (2D) axes or not
63652 valType: 'color',
63653
63654 dflt: colorAttrs.background,
63655 editType: 'layoutstyle',
63656
63657 },
63658 separators: {
63659 valType: 'string',
63660
63661 editType: 'plot',
63662
63663 },
63664 hidesources: {
63665 valType: 'boolean',
63666
63667 dflt: false,
63668 editType: 'plot',
63669
63670 },
63671 showlegend: {
63672 // handled in legend.supplyLayoutDefaults
63673 // but included here because it's not in the legend object
63674 valType: 'boolean',
63675
63676 editType: 'legend',
63677
63678 },
63679 colorway: {
63680 valType: 'colorlist',
63681 dflt: colorAttrs.defaults,
63682
63683 editType: 'calc',
63684
63685 },
63686 datarevision: {
63687 valType: 'any',
63688
63689 editType: 'calc',
63690
63691 },
63692 uirevision: {
63693 valType: 'any',
63694
63695 editType: 'none',
63696
63697 },
63698 editrevision: {
63699 valType: 'any',
63700
63701 editType: 'none',
63702
63703 },
63704 selectionrevision: {
63705 valType: 'any',
63706
63707 editType: 'none',
63708
63709 },
63710 template: {
63711 valType: 'any',
63712
63713 editType: 'calc',
63714
63715 },
63716 modebar: {
63717 orientation: {
63718 valType: 'enumerated',
63719 values: ['v', 'h'],
63720 dflt: 'h',
63721
63722 editType: 'modebar',
63723
63724 },
63725 bgcolor: {
63726 valType: 'color',
63727
63728 editType: 'modebar',
63729
63730 },
63731 color: {
63732 valType: 'color',
63733
63734 editType: 'modebar',
63735
63736 },
63737 activecolor: {
63738 valType: 'color',
63739
63740 editType: 'modebar',
63741
63742 },
63743 uirevision: {
63744 valType: 'any',
63745
63746 editType: 'none',
63747
63748 },
63749 editType: 'modebar'
63750 },
63751
63752 newshape: drawNewShapeAttrs.newshape,
63753 activeshape: drawNewShapeAttrs.activeshape,
63754
63755 meta: {
63756 valType: 'any',
63757 arrayOk: true,
63758
63759 editType: 'plot',
63760
63761 },
63762
63763 transition: extendFlat({}, animationAttrs.transition, {
63764
63765 editType: 'none'
63766 }),
63767 _deprecated: {
63768 title: {
63769 valType: 'string',
63770
63771 editType: 'layoutstyle',
63772
63773 },
63774 titlefont: fontAttrs({
63775 editType: 'layoutstyle',
63776
63777 })
63778 }
63779};
63780
63781},{"../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){
63782/**
63783* Copyright 2012-2020, Plotly, Inc.
63784* All rights reserved.
63785*
63786* This source code is licensed under the MIT license found in the
63787* LICENSE file in the root directory of this source tree.
63788*/
63789
63790'use strict';
63791
63792/**
63793 * Creates a set of padding attributes.
63794 *
63795 * @param {object} opts
63796 * @param {string} editType:
63797 * the editType for all pieces of this padding definition
63798 *
63799 * @return {object} attributes object containing {t, r, b, l} as specified
63800 */
63801module.exports = function(opts) {
63802 var editType = opts.editType;
63803 return {
63804 t: {
63805 valType: 'number',
63806 dflt: 0,
63807
63808 editType: editType,
63809
63810 },
63811 r: {
63812 valType: 'number',
63813 dflt: 0,
63814
63815 editType: editType,
63816
63817 },
63818 b: {
63819 valType: 'number',
63820 dflt: 0,
63821
63822 editType: editType,
63823
63824 },
63825 l: {
63826 valType: 'number',
63827 dflt: 0,
63828
63829 editType: editType,
63830
63831 },
63832 editType: editType
63833 };
63834};
63835
63836},{}],256:[function(_dereq_,module,exports){
63837/**
63838* Copyright 2012-2020, Plotly, Inc.
63839* All rights reserved.
63840*
63841* This source code is licensed under the MIT license found in the
63842* LICENSE file in the root directory of this source tree.
63843*/
63844
63845'use strict';
63846
63847var d3 = _dereq_('d3');
63848var isNumeric = _dereq_('fast-isnumeric');
63849
63850var Registry = _dereq_('../registry');
63851var PlotSchema = _dereq_('../plot_api/plot_schema');
63852var Template = _dereq_('../plot_api/plot_template');
63853var Lib = _dereq_('../lib');
63854var Color = _dereq_('../components/color');
63855var BADNUM = _dereq_('../constants/numerical').BADNUM;
63856
63857var axisIDs = _dereq_('./cartesian/axis_ids');
63858var clearSelect = _dereq_('./cartesian/handle_outline').clearSelect;
63859
63860var animationAttrs = _dereq_('./animation_attributes');
63861var frameAttrs = _dereq_('./frame_attributes');
63862
63863var getModuleCalcData = _dereq_('../plots/get_data').getModuleCalcData;
63864
63865var relinkPrivateKeys = Lib.relinkPrivateKeys;
63866var _ = Lib._;
63867
63868var plots = module.exports = {};
63869
63870// Expose registry methods on Plots for backward-compatibility
63871Lib.extendFlat(plots, Registry);
63872
63873plots.attributes = _dereq_('./attributes');
63874plots.attributes.type.values = plots.allTypes;
63875plots.fontAttrs = _dereq_('./font_attributes');
63876plots.layoutAttributes = _dereq_('./layout_attributes');
63877
63878// TODO make this a plot attribute?
63879plots.fontWeight = 'normal';
63880
63881var transformsRegistry = plots.transformsRegistry;
63882
63883var commandModule = _dereq_('./command');
63884plots.executeAPICommand = commandModule.executeAPICommand;
63885plots.computeAPICommandBindings = commandModule.computeAPICommandBindings;
63886plots.manageCommandObserver = commandModule.manageCommandObserver;
63887plots.hasSimpleAPICommandBindings = commandModule.hasSimpleAPICommandBindings;
63888
63889// in some cases the browser doesn't seem to know how big
63890// the text is at first, so it needs to draw it,
63891// then wait a little, then draw it again
63892plots.redrawText = function(gd) {
63893 gd = Lib.getGraphDiv(gd);
63894
63895 var fullLayout = gd._fullLayout || {};
63896 var hasPolar = fullLayout._has && fullLayout._has('polar');
63897 var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r;
63898
63899 // do not work if polar is present
63900 if(hasLegacyPolar) return;
63901
63902 return new Promise(function(resolve) {
63903 setTimeout(function() {
63904 Registry.getComponentMethod('annotations', 'draw')(gd);
63905 Registry.getComponentMethod('legend', 'draw')(gd);
63906 Registry.getComponentMethod('colorbar', 'draw')(gd);
63907 resolve(plots.previousPromises(gd));
63908 }, 300);
63909 });
63910};
63911
63912// resize plot about the container size
63913plots.resize = function(gd) {
63914 gd = Lib.getGraphDiv(gd);
63915
63916 var resolveLastResize;
63917 var p = new Promise(function(resolve, reject) {
63918 if(!gd || Lib.isHidden(gd)) {
63919 reject(new Error('Resize must be passed a displayed plot div element.'));
63920 }
63921
63922 if(gd._redrawTimer) clearTimeout(gd._redrawTimer);
63923 if(gd._resolveResize) resolveLastResize = gd._resolveResize;
63924 gd._resolveResize = resolve;
63925
63926 gd._redrawTimer = setTimeout(function() {
63927 // return if there is nothing to resize or is hidden
63928 if(!gd.layout || (gd.layout.width && gd.layout.height) || Lib.isHidden(gd)) {
63929 resolve(gd);
63930 return;
63931 }
63932
63933 delete gd.layout.width;
63934 delete gd.layout.height;
63935
63936 // autosizing doesn't count as a change that needs saving
63937 var oldchanged = gd.changed;
63938
63939 // nor should it be included in the undo queue
63940 gd.autoplay = true;
63941
63942 Registry.call('relayout', gd, {autosize: true}).then(function() {
63943 gd.changed = oldchanged;
63944 // Only resolve if a new call hasn't been made!
63945 if(gd._resolveResize === resolve) {
63946 delete gd._resolveResize;
63947 resolve(gd);
63948 }
63949 });
63950 }, 100);
63951 });
63952
63953 if(resolveLastResize) resolveLastResize(p);
63954 return p;
63955};
63956
63957
63958// for use in Lib.syncOrAsync, check if there are any
63959// pending promises in this plot and wait for them
63960plots.previousPromises = function(gd) {
63961 if((gd._promises || []).length) {
63962 return Promise.all(gd._promises)
63963 .then(function() { gd._promises = []; });
63964 }
63965};
63966
63967/**
63968 * Adds the 'Edit chart' link.
63969 * Note that now Plotly.plot() calls this so it can regenerate whenever it replots
63970 *
63971 * Add source links to your graph inside the 'showSources' config argument.
63972 */
63973plots.addLinks = function(gd) {
63974 // Do not do anything if showLink and showSources are not set to true in config
63975 if(!gd._context.showLink && !gd._context.showSources) return;
63976
63977 var fullLayout = gd._fullLayout;
63978
63979 var linkContainer = Lib.ensureSingle(fullLayout._paper, 'text', 'js-plot-link-container', function(s) {
63980 s.style({
63981 'font-family': '"Open Sans", Arial, sans-serif',
63982 'font-size': '12px',
63983 'fill': Color.defaultLine,
63984 'pointer-events': 'all'
63985 })
63986 .each(function() {
63987 var links = d3.select(this);
63988 links.append('tspan').classed('js-link-to-tool', true);
63989 links.append('tspan').classed('js-link-spacer', true);
63990 links.append('tspan').classed('js-sourcelinks', true);
63991 });
63992 });
63993
63994 // The text node inside svg
63995 var text = linkContainer.node();
63996 var attrs = {y: fullLayout._paper.attr('height') - 9};
63997
63998 // If text's width is bigger than the layout
63999 // Check that text is a child node or document.body
64000 // because otherwise IE/Edge might throw an exception
64001 // when calling getComputedTextLength().
64002 // Apparently offsetParent is null for invisibles.
64003 if(document.body.contains(text) && text.getComputedTextLength() >= (fullLayout.width - 20)) {
64004 // Align the text at the left
64005 attrs['text-anchor'] = 'start';
64006 attrs.x = 5;
64007 } else {
64008 // Align the text at the right
64009 attrs['text-anchor'] = 'end';
64010 attrs.x = fullLayout._paper.attr('width') - 7;
64011 }
64012
64013 linkContainer.attr(attrs);
64014
64015 var toolspan = linkContainer.select('.js-link-to-tool');
64016 var spacespan = linkContainer.select('.js-link-spacer');
64017 var sourcespan = linkContainer.select('.js-sourcelinks');
64018
64019 if(gd._context.showSources) gd._context.showSources(gd);
64020
64021 // 'view in plotly' link for embedded plots
64022 if(gd._context.showLink) positionPlayWithData(gd, toolspan);
64023
64024 // separator if we have both sources and tool link
64025 spacespan.text((toolspan.text() && sourcespan.text()) ? ' - ' : '');
64026};
64027
64028// note that now this function is only adding the brand in
64029// iframes and 3rd-party apps
64030function positionPlayWithData(gd, container) {
64031 container.text('');
64032 var link = container.append('a')
64033 .attr({
64034 'xlink:xlink:href': '#',
64035 'class': 'link--impt link--embedview',
64036 'font-weight': 'bold'
64037 })
64038 .text(gd._context.linkText + ' ' + String.fromCharCode(187));
64039
64040 if(gd._context.sendData) {
64041 link.on('click', function() {
64042 plots.sendDataToCloud(gd);
64043 });
64044 } else {
64045 var path = window.location.pathname.split('/');
64046 var query = window.location.search;
64047 link.attr({
64048 'xlink:xlink:show': 'new',
64049 'xlink:xlink:href': '/' + path[2].split('.')[0] + '/' + path[1] + query
64050 });
64051 }
64052}
64053
64054plots.sendDataToCloud = function(gd) {
64055 var baseUrl = (window.PLOTLYENV || {}).BASE_URL || gd._context.plotlyServerURL;
64056 if(!baseUrl) return;
64057
64058 gd.emit('plotly_beforeexport');
64059
64060 var hiddenformDiv = d3.select(gd)
64061 .append('div')
64062 .attr('id', 'hiddenform')
64063 .style('display', 'none');
64064
64065 var hiddenform = hiddenformDiv
64066 .append('form')
64067 .attr({
64068 action: baseUrl + '/external',
64069 method: 'post',
64070 target: '_blank'
64071 });
64072
64073 var hiddenformInput = hiddenform
64074 .append('input')
64075 .attr({
64076 type: 'text',
64077 name: 'data'
64078 });
64079
64080 hiddenformInput.node().value = plots.graphJson(gd, false, 'keepdata');
64081 hiddenform.node().submit();
64082 hiddenformDiv.remove();
64083
64084 gd.emit('plotly_afterexport');
64085 return false;
64086};
64087
64088var d3FormatKeys = [
64089 'days', 'shortDays', 'months', 'shortMonths', 'periods',
64090 'dateTime', 'date', 'time',
64091 'decimal', 'thousands', 'grouping', 'currency'
64092];
64093
64094var extraFormatKeys = [
64095 'year', 'month', 'dayMonth', 'dayMonthYear'
64096];
64097
64098/*
64099 * Fill in default values
64100 * @param {DOM element} gd
64101 * @param {object} opts
64102 * @param {boolean} opts.skipUpdateCalc: normally if the existing gd.calcdata looks
64103 * compatible with the new gd._fullData we finish by linking the new _fullData traces
64104 * to the old gd.calcdata, so it's correctly set if we're not going to recalc. But also,
64105 * if there are calcTransforms on the trace, we first remap data arrays from the old full
64106 * trace into the new one. Use skipUpdateCalc to defer this (needed by Plotly.react)
64107 *
64108 * gd.data, gd.layout:
64109 * are precisely what the user specified (except as modified by cleanData/cleanLayout),
64110 * these fields shouldn't be modified (except for filling in some auto values)
64111 * nor used directly after the supply defaults step.
64112 *
64113 * gd._fullData, gd._fullLayout:
64114 * are complete descriptions of how to draw the plot,
64115 * use these fields in all required computations.
64116 *
64117 * gd._fullLayout._modules
64118 * is a list of all the trace modules required to draw the plot.
64119 *
64120 * gd._fullLayout._visibleModules
64121 * subset of _modules, a list of modules corresponding to visible:true traces.
64122 *
64123 * gd._fullLayout._basePlotModules
64124 * is a list of all the plot modules required to draw the plot.
64125 *
64126 * gd._fullLayout._transformModules
64127 * is a list of all the transform modules invoked.
64128 *
64129 */
64130plots.supplyDefaults = function(gd, opts) {
64131 var skipUpdateCalc = opts && opts.skipUpdateCalc;
64132 var oldFullLayout = gd._fullLayout || {};
64133
64134 if(oldFullLayout._skipDefaults) {
64135 delete oldFullLayout._skipDefaults;
64136 return;
64137 }
64138
64139 var newFullLayout = gd._fullLayout = {};
64140 var newLayout = gd.layout || {};
64141
64142 var oldFullData = gd._fullData || [];
64143 var newFullData = gd._fullData = [];
64144 var newData = gd.data || [];
64145
64146 var oldCalcdata = gd.calcdata || [];
64147
64148 var context = gd._context || {};
64149
64150 var i;
64151
64152 // Create all the storage space for frames, but only if doesn't already exist
64153 if(!gd._transitionData) plots.createTransitionData(gd);
64154
64155 // So we only need to do this once (and since we have gd here)
64156 // get the translated placeholder titles.
64157 // These ones get used as default values so need to be known at supplyDefaults
64158 // others keep their blank defaults but render the placeholder as desired later
64159 // TODO: make these work the same way, only inserting the placeholder text at draw time?
64160 // The challenge is that this has slightly different behavior right now in editable mode:
64161 // using the placeholder as default makes this text permanently (but lightly) visible,
64162 // but explicit '' for these titles gives you a placeholder that's hidden until you mouse
64163 // over it - so you're not distracted by it if you really don't want a title, but if you do
64164 // and you're new to plotly you may not be able to find it.
64165 // When editable=false the two behave the same, no title is drawn.
64166 newFullLayout._dfltTitle = {
64167 plot: _(gd, 'Click to enter Plot title'),
64168 x: _(gd, 'Click to enter X axis title'),
64169 y: _(gd, 'Click to enter Y axis title'),
64170 colorbar: _(gd, 'Click to enter Colorscale title'),
64171 annotation: _(gd, 'new text')
64172 };
64173 newFullLayout._traceWord = _(gd, 'trace');
64174
64175 var formatObj = getFormatObj(gd, d3FormatKeys);
64176
64177 // stash the token from context so mapbox subplots can use it as default
64178 newFullLayout._mapboxAccessToken = context.mapboxAccessToken;
64179
64180 // first fill in what we can of layout without looking at data
64181 // because fullData needs a few things from layout
64182 if(oldFullLayout._initialAutoSizeIsDone) {
64183 // coerce the updated layout while preserving width and height
64184 var oldWidth = oldFullLayout.width;
64185 var oldHeight = oldFullLayout.height;
64186
64187 plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj);
64188
64189 if(!newLayout.width) newFullLayout.width = oldWidth;
64190 if(!newLayout.height) newFullLayout.height = oldHeight;
64191 plots.sanitizeMargins(newFullLayout);
64192 } else {
64193 // coerce the updated layout and autosize if needed
64194 plots.supplyLayoutGlobalDefaults(newLayout, newFullLayout, formatObj);
64195
64196 var missingWidthOrHeight = (!newLayout.width || !newLayout.height);
64197 var autosize = newFullLayout.autosize;
64198 var autosizable = context.autosizable;
64199 var initialAutoSize = missingWidthOrHeight && (autosize || autosizable);
64200
64201 if(initialAutoSize) plots.plotAutoSize(gd, newLayout, newFullLayout);
64202 else if(missingWidthOrHeight) plots.sanitizeMargins(newFullLayout);
64203
64204 // for backwards-compatibility with Plotly v1.x.x
64205 if(!autosize && missingWidthOrHeight) {
64206 newLayout.width = newFullLayout.width;
64207 newLayout.height = newFullLayout.height;
64208 }
64209 }
64210
64211 newFullLayout._d3locale = getFormatter(formatObj, newFullLayout.separators);
64212 newFullLayout._extraFormat = getFormatObj(gd, extraFormatKeys);
64213
64214 newFullLayout._initialAutoSizeIsDone = true;
64215
64216 // keep track of how many traces are inputted
64217 newFullLayout._dataLength = newData.length;
64218
64219 // clear the lists of trace and baseplot modules, and subplots
64220 newFullLayout._modules = [];
64221 newFullLayout._visibleModules = [];
64222 newFullLayout._basePlotModules = [];
64223 var subplots = newFullLayout._subplots = emptySubplotLists();
64224
64225 // initialize axis and subplot hash objects for splom-generated grids
64226 var splomAxes = newFullLayout._splomAxes = {x: {}, y: {}};
64227 var splomSubplots = newFullLayout._splomSubplots = {};
64228 // initialize splom grid defaults
64229 newFullLayout._splomGridDflt = {};
64230
64231 // for stacked area traces to share config across traces
64232 newFullLayout._scatterStackOpts = {};
64233 // for the first scatter trace on each subplot (so it knows tonext->tozero)
64234 newFullLayout._firstScatter = {};
64235 // for grouped bar/box/violin trace to share config across traces
64236 newFullLayout._alignmentOpts = {};
64237 // track color axes referenced in the data
64238 newFullLayout._colorAxes = {};
64239
64240 // for traces to request a default rangeslider on their x axes
64241 // eg set `_requestRangeslider.x2 = true` for xaxis2
64242 newFullLayout._requestRangeslider = {};
64243
64244 // pull uids from old data to use as new defaults
64245 newFullLayout._traceUids = getTraceUids(oldFullData, newData);
64246
64247 // then do the data
64248 newFullLayout._globalTransforms = (gd._context || {}).globalTransforms;
64249 plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout);
64250
64251 // redo grid size defaults with info about splom x/y axes,
64252 // and fill in generated cartesian axes and subplots
64253 var splomXa = Object.keys(splomAxes.x);
64254 var splomYa = Object.keys(splomAxes.y);
64255 if(splomXa.length > 1 && splomYa.length > 1) {
64256 Registry.getComponentMethod('grid', 'sizeDefaults')(newLayout, newFullLayout);
64257
64258 for(i = 0; i < splomXa.length; i++) {
64259 Lib.pushUnique(subplots.xaxis, splomXa[i]);
64260 }
64261 for(i = 0; i < splomYa.length; i++) {
64262 Lib.pushUnique(subplots.yaxis, splomYa[i]);
64263 }
64264 for(var k in splomSubplots) {
64265 Lib.pushUnique(subplots.cartesian, k);
64266 }
64267 }
64268
64269 // attach helper method to check whether a plot type is present on graph
64270 newFullLayout._has = plots._hasPlotType.bind(newFullLayout);
64271
64272 if(oldFullData.length === newFullData.length) {
64273 for(i = 0; i < newFullData.length; i++) {
64274 relinkPrivateKeys(newFullData[i], oldFullData[i]);
64275 }
64276 }
64277
64278 // finally, fill in the pieces of layout that may need to look at data
64279 plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData, gd._transitionData);
64280
64281 // Special cases that introduce interactions between traces.
64282 // This is after relinkPrivateKeys so we can use those in crossTraceDefaults
64283 // and after layout module defaults, so we can use eg barmode
64284 var _modules = newFullLayout._visibleModules;
64285 var crossTraceDefaultsFuncs = [];
64286 for(i = 0; i < _modules.length; i++) {
64287 var funci = _modules[i].crossTraceDefaults;
64288 // some trace types share crossTraceDefaults (ie histogram2d, histogram2dcontour)
64289 if(funci) Lib.pushUnique(crossTraceDefaultsFuncs, funci);
64290 }
64291 for(i = 0; i < crossTraceDefaultsFuncs.length; i++) {
64292 crossTraceDefaultsFuncs[i](newFullData, newFullLayout);
64293 }
64294
64295 // turn on flag to optimize large splom-only graphs
64296 // mostly by omitting SVG layers during Cartesian.drawFramework
64297 newFullLayout._hasOnlyLargeSploms = (
64298 newFullLayout._basePlotModules.length === 1 &&
64299 newFullLayout._basePlotModules[0].name === 'splom' &&
64300 splomXa.length > 15 &&
64301 splomYa.length > 15 &&
64302 newFullLayout.shapes.length === 0 &&
64303 newFullLayout.images.length === 0
64304 );
64305
64306 // TODO remove in v2.0.0
64307 // add has-plot-type refs to fullLayout for backward compatibility
64308 newFullLayout._hasCartesian = newFullLayout._has('cartesian');
64309 newFullLayout._hasGeo = newFullLayout._has('geo');
64310 newFullLayout._hasGL3D = newFullLayout._has('gl3d');
64311 newFullLayout._hasGL2D = newFullLayout._has('gl2d');
64312 newFullLayout._hasTernary = newFullLayout._has('ternary');
64313 newFullLayout._hasPie = newFullLayout._has('pie');
64314
64315 // relink / initialize subplot axis objects
64316 plots.linkSubplots(newFullData, newFullLayout, oldFullData, oldFullLayout);
64317
64318 // clean subplots and other artifacts from previous plot calls
64319 plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout);
64320
64321 var hadGL2D = !!(oldFullLayout._has && oldFullLayout._has('gl2d'));
64322 var hasGL2D = !!(newFullLayout._has && newFullLayout._has('gl2d'));
64323 var hadCartesian = !!(oldFullLayout._has && oldFullLayout._has('cartesian'));
64324 var hasCartesian = !!(newFullLayout._has && newFullLayout._has('cartesian'));
64325 var hadBgLayer = hadCartesian || hadGL2D;
64326 var hasBgLayer = hasCartesian || hasGL2D;
64327 if(hadBgLayer && !hasBgLayer) {
64328 // remove bgLayer
64329 oldFullLayout._bgLayer.remove();
64330 } else if(hasBgLayer && !hadBgLayer) {
64331 // create bgLayer
64332 newFullLayout._shouldCreateBgLayer = true;
64333 }
64334
64335 // clear selection outline until we implement persistent selection,
64336 // don't clear them though when drag handlers (e.g. listening to
64337 // `plotly_selecting`) update the graph.
64338 // we should try to come up with a better solution when implementing
64339 // https://github.com/plotly/plotly.js/issues/1851
64340 if(oldFullLayout._zoomlayer && !gd._dragging) {
64341 clearSelect({ // mock old gd
64342 _fullLayout: oldFullLayout
64343 });
64344 }
64345
64346
64347 // fill in meta helpers
64348 fillMetaTextHelpers(newFullData, newFullLayout);
64349
64350 // relink functions and _ attributes to promote consistency between plots
64351 relinkPrivateKeys(newFullLayout, oldFullLayout);
64352
64353 // colorscale crossTraceDefaults needs newFullLayout with relinked keys
64354 Registry.getComponentMethod('colorscale', 'crossTraceDefaults')(newFullData, newFullLayout);
64355
64356 // For persisting GUI-driven changes in layout
64357 // _preGUI and _tracePreGUI were already copied over in relinkPrivateKeys
64358 if(!newFullLayout._preGUI) newFullLayout._preGUI = {};
64359 // track trace GUI changes by uid rather than by trace index
64360 if(!newFullLayout._tracePreGUI) newFullLayout._tracePreGUI = {};
64361 var tracePreGUI = newFullLayout._tracePreGUI;
64362 var uids = {};
64363 var uid;
64364 for(uid in tracePreGUI) uids[uid] = 'old';
64365 for(i = 0; i < newFullData.length; i++) {
64366 uid = newFullData[i]._fullInput.uid;
64367 if(!uids[uid]) tracePreGUI[uid] = {};
64368 uids[uid] = 'new';
64369 }
64370 for(uid in uids) {
64371 if(uids[uid] === 'old') delete tracePreGUI[uid];
64372 }
64373
64374 // set up containers for margin calculations
64375 initMargins(newFullLayout);
64376
64377 // collect and do some initial calculations for rangesliders
64378 Registry.getComponentMethod('rangeslider', 'makeData')(newFullLayout);
64379
64380 // update object references in calcdata
64381 if(!skipUpdateCalc && oldCalcdata.length === newFullData.length) {
64382 plots.supplyDefaultsUpdateCalc(oldCalcdata, newFullData);
64383 }
64384};
64385
64386plots.supplyDefaultsUpdateCalc = function(oldCalcdata, newFullData) {
64387 for(var i = 0; i < newFullData.length; i++) {
64388 var newTrace = newFullData[i];
64389 var cd0 = (oldCalcdata[i] || [])[0];
64390 if(cd0 && cd0.trace) {
64391 var oldTrace = cd0.trace;
64392 if(oldTrace._hasCalcTransform) {
64393 var arrayAttrs = oldTrace._arrayAttrs;
64394 var j, astr, oldArrayVal;
64395
64396 for(j = 0; j < arrayAttrs.length; j++) {
64397 astr = arrayAttrs[j];
64398 oldArrayVal = Lib.nestedProperty(oldTrace, astr).get().slice();
64399 Lib.nestedProperty(newTrace, astr).set(oldArrayVal);
64400 }
64401 }
64402 cd0.trace = newTrace;
64403 }
64404 }
64405};
64406
64407/**
64408 * Create a list of uid strings satisfying (in this order of importance):
64409 * 1. all unique, all strings
64410 * 2. matches input uids if provided
64411 * 3. matches previous data uids
64412 */
64413function getTraceUids(oldFullData, newData) {
64414 var len = newData.length;
64415 var oldFullInput = [];
64416 var i, prevFullInput;
64417 for(i = 0; i < oldFullData.length; i++) {
64418 var thisFullInput = oldFullData[i]._fullInput;
64419 if(thisFullInput !== prevFullInput) oldFullInput.push(thisFullInput);
64420 prevFullInput = thisFullInput;
64421 }
64422 var oldLen = oldFullInput.length;
64423 var out = new Array(len);
64424 var seenUids = {};
64425
64426 function setUid(uid, i) {
64427 out[i] = uid;
64428 seenUids[uid] = 1;
64429 }
64430
64431 function tryUid(uid, i) {
64432 if(uid && typeof uid === 'string' && !seenUids[uid]) {
64433 setUid(uid, i);
64434 return true;
64435 }
64436 }
64437
64438 for(i = 0; i < len; i++) {
64439 var newUid = newData[i].uid;
64440 if(typeof newUid === 'number') newUid = String(newUid);
64441
64442 if(tryUid(newUid, i)) continue;
64443 if(i < oldLen && tryUid(oldFullInput[i].uid, i)) continue;
64444 setUid(Lib.randstr(seenUids), i);
64445 }
64446
64447 return out;
64448}
64449
64450/**
64451 * Make a container for collecting subplots we need to display.
64452 *
64453 * Finds all subplot types we need to enumerate once and caches it,
64454 * but makes a new output object each time.
64455 * Single-trace subplots (which have no `id`) such as pie, table, etc
64456 * do not need to be collected because we just draw all visible traces.
64457 */
64458function emptySubplotLists() {
64459 var collectableSubplotTypes = Registry.collectableSubplotTypes;
64460 var out = {};
64461 var i, j;
64462
64463 if(!collectableSubplotTypes) {
64464 collectableSubplotTypes = [];
64465
64466 var subplotsRegistry = Registry.subplotsRegistry;
64467
64468 for(var subplotType in subplotsRegistry) {
64469 var subplotModule = subplotsRegistry[subplotType];
64470 var subplotAttr = subplotModule.attr;
64471
64472 if(subplotAttr) {
64473 collectableSubplotTypes.push(subplotType);
64474
64475 // special case, currently just for cartesian:
64476 // we need to enumerate axes, not just subplots
64477 if(Array.isArray(subplotAttr)) {
64478 for(j = 0; j < subplotAttr.length; j++) {
64479 Lib.pushUnique(collectableSubplotTypes, subplotAttr[j]);
64480 }
64481 }
64482 }
64483 }
64484 }
64485
64486 for(i = 0; i < collectableSubplotTypes.length; i++) {
64487 out[collectableSubplotTypes[i]] = [];
64488 }
64489 return out;
64490}
64491
64492/**
64493 * getFormatObj: use _context to get the format object from locale.
64494 * Used to get d3.locale argument object and extraFormat argument object
64495 *
64496 * Regarding d3.locale argument :
64497 * decimal and thousands can be overridden later by layout.separators
64498 * grouping and currency are not presently used by our automatic number
64499 * formatting system but can be used by custom formats.
64500 *
64501 * @returns {object} d3.locale format object
64502 */
64503function getFormatObj(gd, formatKeys) {
64504 var locale = gd._context.locale;
64505 if(!locale) locale === 'en-US';
64506
64507 var formatDone = false;
64508 var formatObj = {};
64509
64510 function includeFormat(newFormat) {
64511 var formatFinished = true;
64512 for(var i = 0; i < formatKeys.length; i++) {
64513 var formatKey = formatKeys[i];
64514 if(!formatObj[formatKey]) {
64515 if(newFormat[formatKey]) {
64516 formatObj[formatKey] = newFormat[formatKey];
64517 } else formatFinished = false;
64518 }
64519 }
64520 if(formatFinished) formatDone = true;
64521 }
64522
64523 // same as localize, look for format parts in each format spec in the chain
64524 for(var i = 0; i < 2; i++) {
64525 var locales = gd._context.locales;
64526 for(var j = 0; j < 2; j++) {
64527 var formatj = (locales[locale] || {}).format;
64528 if(formatj) {
64529 includeFormat(formatj);
64530 if(formatDone) break;
64531 }
64532 locales = Registry.localeRegistry;
64533 }
64534
64535 var baseLocale = locale.split('-')[0];
64536 if(formatDone || baseLocale === locale) break;
64537 locale = baseLocale;
64538 }
64539
64540 // lastly pick out defaults from english (non-US, as DMY is so much more common)
64541 if(!formatDone) includeFormat(Registry.localeRegistry.en.format);
64542
64543 return formatObj;
64544}
64545
64546/**
64547 * getFormatter: combine the final separators with the locale formatting object
64548 * we pulled earlier to generate number and time formatters
64549 * TODO: remove separators in v2, only use locale, so we don't need this step?
64550 *
64551 * @param {object} formatObj: d3.locale format object
64552 * @param {string} separators: length-2 string to override decimal and thousands
64553 * separators in number formatting
64554 *
64555 * @returns {object} {numberFormat, timeFormat} d3 formatter factory functions
64556 * for numbers and time
64557 */
64558function getFormatter(formatObj, separators) {
64559 formatObj.decimal = separators.charAt(0);
64560 formatObj.thousands = separators.charAt(1);
64561
64562 return d3.locale(formatObj);
64563}
64564
64565function fillMetaTextHelpers(newFullData, newFullLayout) {
64566 var _meta;
64567 var meta4data = [];
64568
64569 if(newFullLayout.meta) {
64570 _meta = newFullLayout._meta = {
64571 meta: newFullLayout.meta,
64572 layout: {meta: newFullLayout.meta}
64573 };
64574 }
64575
64576 for(var i = 0; i < newFullData.length; i++) {
64577 var trace = newFullData[i];
64578
64579 if(trace.meta) {
64580 meta4data[trace.index] = trace._meta = {meta: trace.meta};
64581 } else if(newFullLayout.meta) {
64582 trace._meta = {meta: newFullLayout.meta};
64583 }
64584 if(newFullLayout.meta) {
64585 trace._meta.layout = {meta: newFullLayout.meta};
64586 }
64587 }
64588
64589 if(meta4data.length) {
64590 if(!_meta) {
64591 _meta = newFullLayout._meta = {};
64592 }
64593 _meta.data = meta4data;
64594 }
64595}
64596
64597// Create storage for all of the data related to frames and transitions:
64598plots.createTransitionData = function(gd) {
64599 // Set up the default keyframe if it doesn't exist:
64600 if(!gd._transitionData) {
64601 gd._transitionData = {};
64602 }
64603
64604 if(!gd._transitionData._frames) {
64605 gd._transitionData._frames = [];
64606 }
64607
64608 if(!gd._transitionData._frameHash) {
64609 gd._transitionData._frameHash = {};
64610 }
64611
64612 if(!gd._transitionData._counter) {
64613 gd._transitionData._counter = 0;
64614 }
64615
64616 if(!gd._transitionData._interruptCallbacks) {
64617 gd._transitionData._interruptCallbacks = [];
64618 }
64619};
64620
64621// helper function to be bound to fullLayout to check
64622// whether a certain plot type is present on plot
64623// or trace has a category
64624plots._hasPlotType = function(category) {
64625 var i;
64626
64627 // check base plot modules
64628 var basePlotModules = this._basePlotModules || [];
64629 for(i = 0; i < basePlotModules.length; i++) {
64630 if(basePlotModules[i].name === category) return true;
64631 }
64632
64633 // check trace modules (including non-visible:true)
64634 var modules = this._modules || [];
64635 for(i = 0; i < modules.length; i++) {
64636 var name = modules[i].name;
64637 if(name === category) return true;
64638 // N.B. this is modules[i] along with 'categories' as a hash object
64639 var _module = Registry.modules[name];
64640 if(_module && _module.categories[category]) return true;
64641 }
64642
64643 return false;
64644};
64645
64646plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
64647 var i, j;
64648
64649 var basePlotModules = oldFullLayout._basePlotModules || [];
64650 for(i = 0; i < basePlotModules.length; i++) {
64651 var _module = basePlotModules[i];
64652
64653 if(_module.clean) {
64654 _module.clean(newFullData, newFullLayout, oldFullData, oldFullLayout);
64655 }
64656 }
64657
64658 var hadGl = oldFullLayout._has && oldFullLayout._has('gl');
64659 var hasGl = newFullLayout._has && newFullLayout._has('gl');
64660
64661 if(hadGl && !hasGl) {
64662 if(oldFullLayout._glcontainer !== undefined) {
64663 oldFullLayout._glcontainer.selectAll('.gl-canvas').remove();
64664 oldFullLayout._glcontainer.selectAll('.no-webgl').remove();
64665 oldFullLayout._glcanvas = null;
64666 }
64667 }
64668
64669 var hasInfoLayer = !!oldFullLayout._infolayer;
64670
64671 oldLoop:
64672 for(i = 0; i < oldFullData.length; i++) {
64673 var oldTrace = oldFullData[i];
64674 var oldUid = oldTrace.uid;
64675
64676 for(j = 0; j < newFullData.length; j++) {
64677 var newTrace = newFullData[j];
64678
64679 if(oldUid === newTrace.uid) continue oldLoop;
64680 }
64681
64682 // clean old colorbars
64683 if(hasInfoLayer) {
64684 oldFullLayout._infolayer.select('.cb' + oldUid).remove();
64685 }
64686 }
64687};
64688
64689plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
64690 var i, j;
64691
64692 var oldSubplots = oldFullLayout._plots || {};
64693 var newSubplots = newFullLayout._plots = {};
64694 var newSubplotList = newFullLayout._subplots;
64695
64696 var mockGd = {
64697 _fullData: newFullData,
64698 _fullLayout: newFullLayout
64699 };
64700
64701 var ids = newSubplotList.cartesian.concat(newSubplotList.gl2d || []);
64702
64703 for(i = 0; i < ids.length; i++) {
64704 var id = ids[i];
64705 var oldSubplot = oldSubplots[id];
64706 var xaxis = axisIDs.getFromId(mockGd, id, 'x');
64707 var yaxis = axisIDs.getFromId(mockGd, id, 'y');
64708 var plotinfo;
64709
64710 // link or create subplot object
64711 if(oldSubplot) {
64712 plotinfo = newSubplots[id] = oldSubplot;
64713 } else {
64714 plotinfo = newSubplots[id] = {};
64715 plotinfo.id = id;
64716 }
64717
64718 // add these axis ids to each others' subplot lists
64719 xaxis._counterAxes.push(yaxis._id);
64720 yaxis._counterAxes.push(xaxis._id);
64721 xaxis._subplotsWith.push(id);
64722 yaxis._subplotsWith.push(id);
64723
64724 // update x and y axis layout object refs
64725 plotinfo.xaxis = xaxis;
64726 plotinfo.yaxis = yaxis;
64727
64728 // By default, we clip at the subplot level,
64729 // but if one trace on a given subplot has *cliponaxis* set to false,
64730 // we need to clip at the trace module layer level;
64731 // find this out here, once of for all.
64732 plotinfo._hasClipOnAxisFalse = false;
64733
64734 for(j = 0; j < newFullData.length; j++) {
64735 var trace = newFullData[j];
64736
64737 if(
64738 trace.xaxis === plotinfo.xaxis._id &&
64739 trace.yaxis === plotinfo.yaxis._id &&
64740 trace.cliponaxis === false
64741 ) {
64742 plotinfo._hasClipOnAxisFalse = true;
64743 break;
64744 }
64745 }
64746 }
64747
64748 // while we're at it, link overlaying axes to their main axes and
64749 // anchored axes to the axes they're anchored to
64750 var axList = axisIDs.list(mockGd, null, true);
64751 var ax;
64752 for(i = 0; i < axList.length; i++) {
64753 ax = axList[i];
64754 var mainAx = null;
64755
64756 if(ax.overlaying) {
64757 mainAx = axisIDs.getFromId(mockGd, ax.overlaying);
64758
64759 // you cannot overlay an axis that's already overlaying another
64760 if(mainAx && mainAx.overlaying) {
64761 ax.overlaying = false;
64762 mainAx = null;
64763 }
64764 }
64765 ax._mainAxis = mainAx || ax;
64766
64767 /*
64768 * For now force overlays to overlay completely... so they
64769 * can drag together correctly and share backgrounds.
64770 * Later perhaps we make separate axis domain and
64771 * tick/line domain or something, so they can still share
64772 * the (possibly larger) dragger and background but don't
64773 * have to both be drawn over that whole domain
64774 */
64775 if(mainAx) ax.domain = mainAx.domain.slice();
64776
64777 ax._anchorAxis = ax.anchor === 'free' ?
64778 null :
64779 axisIDs.getFromId(mockGd, ax.anchor);
64780 }
64781
64782 // finally, we can find the main subplot for each axis
64783 // (on which the ticks & labels are drawn)
64784 for(i = 0; i < axList.length; i++) {
64785 ax = axList[i];
64786 ax._counterAxes.sort(axisIDs.idSort);
64787 ax._subplotsWith.sort(Lib.subplotSort);
64788 ax._mainSubplot = findMainSubplot(ax, newFullLayout);
64789
64790 // find "full" domain span of counter axes,
64791 // this loop can be costly, so only compute it when required
64792 if(ax._counterAxes.length && (
64793 (ax.spikemode && ax.spikemode.indexOf('across') !== -1) ||
64794 (ax.automargin && ax.mirror && ax.anchor !== 'free') ||
64795 Registry.getComponentMethod('rangeslider', 'isVisible')(ax)
64796 )) {
64797 var min = 1;
64798 var max = 0;
64799 for(j = 0; j < ax._counterAxes.length; j++) {
64800 var ax2 = axisIDs.getFromId(mockGd, ax._counterAxes[j]);
64801 min = Math.min(min, ax2.domain[0]);
64802 max = Math.max(max, ax2.domain[1]);
64803 }
64804 if(min < max) {
64805 ax._counterDomainMin = min;
64806 ax._counterDomainMax = max;
64807 }
64808 }
64809 }
64810};
64811
64812function findMainSubplot(ax, fullLayout) {
64813 var mockGd = {_fullLayout: fullLayout};
64814
64815 var isX = ax._id.charAt(0) === 'x';
64816 var anchorAx = ax._mainAxis._anchorAxis;
64817 var mainSubplotID = '';
64818 var nextBestMainSubplotID = '';
64819 var anchorID = '';
64820
64821 // First try the main ID with the anchor
64822 if(anchorAx) {
64823 anchorID = anchorAx._mainAxis._id;
64824 mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id);
64825 }
64826
64827 // Then look for a subplot with the counteraxis overlaying the anchor
64828 // If that fails just use the first subplot including this axis
64829 if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) {
64830 mainSubplotID = '';
64831
64832 var counterIDs = ax._counterAxes;
64833 for(var j = 0; j < counterIDs.length; j++) {
64834 var counterPart = counterIDs[j];
64835 var id = isX ? (ax._id + counterPart) : (counterPart + ax._id);
64836 if(!nextBestMainSubplotID) nextBestMainSubplotID = id;
64837 var counterAx = axisIDs.getFromId(mockGd, counterPart);
64838 if(anchorID && counterAx.overlaying === anchorID) {
64839 mainSubplotID = id;
64840 break;
64841 }
64842 }
64843 }
64844
64845 return mainSubplotID || nextBestMainSubplotID;
64846}
64847
64848// This function clears any trace attributes with valType: color and
64849// no set dflt filed in the plot schema. This is needed because groupby (which
64850// is the only transform for which this currently applies) supplies parent
64851// trace defaults, then expanded trace defaults. The result is that `null`
64852// colors are default-supplied and inherited as a color instead of a null.
64853// The result is that expanded trace default colors have no effect, with
64854// the final result that groups are indistinguishable. This function clears
64855// those colors so that individual groupby groups get unique colors.
64856plots.clearExpandedTraceDefaultColors = function(trace) {
64857 var colorAttrs, path, i;
64858
64859 // This uses weird closure state in order to satisfy the linter rule
64860 // that we can't create functions in a loop.
64861 function locateColorAttrs(attr, attrName, attrs, level) {
64862 path[level] = attrName;
64863 path.length = level + 1;
64864 if(attr.valType === 'color' && attr.dflt === undefined) {
64865 colorAttrs.push(path.join('.'));
64866 }
64867 }
64868
64869 path = [];
64870
64871 // Get the cached colorAttrs:
64872 colorAttrs = trace._module._colorAttrs;
64873
64874 // Or else compute and cache the colorAttrs on the module:
64875 if(!colorAttrs) {
64876 trace._module._colorAttrs = colorAttrs = [];
64877 PlotSchema.crawl(
64878 trace._module.attributes,
64879 locateColorAttrs
64880 );
64881 }
64882
64883 for(i = 0; i < colorAttrs.length; i++) {
64884 var origprop = Lib.nestedProperty(trace, '_input.' + colorAttrs[i]);
64885
64886 if(!origprop.get()) {
64887 Lib.nestedProperty(trace, colorAttrs[i]).set(null);
64888 }
64889 }
64890};
64891
64892
64893plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
64894 var modules = fullLayout._modules;
64895 var visibleModules = fullLayout._visibleModules;
64896 var basePlotModules = fullLayout._basePlotModules;
64897 var cnt = 0;
64898 var colorCnt = 0;
64899
64900 var i, fullTrace, trace;
64901
64902 fullLayout._transformModules = [];
64903
64904 function pushModule(fullTrace) {
64905 dataOut.push(fullTrace);
64906
64907 var _module = fullTrace._module;
64908 if(!_module) return;
64909
64910 Lib.pushUnique(modules, _module);
64911 if(fullTrace.visible === true) Lib.pushUnique(visibleModules, _module);
64912 Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule);
64913 cnt++;
64914
64915 // TODO: do we really want color not to increment for explicitly invisible traces?
64916 // This logic is weird, but matches previous behavior: traces that you explicitly
64917 // set to visible:false do not increment the color, but traces WE determine to be
64918 // empty or invalid (and thus set to visible:false) DO increment color.
64919 // I kind of think we should just let all traces increment color, visible or not.
64920 // see mock: axes-autotype-empty vs. a test of restyling visible: false that
64921 // I can't find right now...
64922 if(fullTrace._input.visible !== false) colorCnt++;
64923 }
64924
64925 var carpetIndex = {};
64926 var carpetDependents = [];
64927 var dataTemplate = (layout.template || {}).data || {};
64928 var templater = Template.traceTemplater(dataTemplate);
64929
64930 for(i = 0; i < dataIn.length; i++) {
64931 trace = dataIn[i];
64932
64933 // reuse uid we may have pulled out of oldFullData
64934 // Note: templater supplies trace type
64935 fullTrace = templater.newTrace(trace);
64936 fullTrace.uid = fullLayout._traceUids[i];
64937 plots.supplyTraceDefaults(trace, fullTrace, colorCnt, fullLayout, i);
64938
64939 fullTrace.index = i;
64940 fullTrace._input = trace;
64941 fullTrace._expandedIndex = cnt;
64942
64943 if(fullTrace.transforms && fullTrace.transforms.length) {
64944 var sdInvisible = trace.visible !== false && fullTrace.visible === false;
64945
64946 var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout);
64947
64948 for(var j = 0; j < expandedTraces.length; j++) {
64949 var expandedTrace = expandedTraces[j];
64950
64951 // No further templating during transforms.
64952 var fullExpandedTrace = {
64953 _template: fullTrace._template,
64954 type: fullTrace.type,
64955 // set uid using parent uid and expanded index
64956 // to promote consistency between update calls
64957 uid: fullTrace.uid + j
64958 };
64959
64960 // If the first supplyDefaults created `visible: false`,
64961 // clear it before running supplyDefaults a second time,
64962 // because sometimes there are items we still want to coerce
64963 // inside trace modules before determining that the trace is
64964 // again `visible: false`, for example partial visibilities
64965 // in `splom` traces.
64966 if(sdInvisible && expandedTrace.visible === false) {
64967 delete expandedTrace.visible;
64968 }
64969
64970 plots.supplyTraceDefaults(expandedTrace, fullExpandedTrace, cnt, fullLayout, i);
64971
64972 // relink private (i.e. underscore) keys expanded trace to full expanded trace so
64973 // that transform supply-default methods can set _ keys for future use.
64974 relinkPrivateKeys(fullExpandedTrace, expandedTrace);
64975
64976 // add info about parent data trace
64977 fullExpandedTrace.index = i;
64978 fullExpandedTrace._input = trace;
64979 fullExpandedTrace._fullInput = fullTrace;
64980
64981 // add info about the expanded data
64982 fullExpandedTrace._expandedIndex = cnt;
64983 fullExpandedTrace._expandedInput = expandedTrace;
64984
64985 pushModule(fullExpandedTrace);
64986 }
64987 } else {
64988 // add identify refs for consistency with transformed traces
64989 fullTrace._fullInput = fullTrace;
64990 fullTrace._expandedInput = fullTrace;
64991
64992 pushModule(fullTrace);
64993 }
64994
64995 if(Registry.traceIs(fullTrace, 'carpetAxis')) {
64996 carpetIndex[fullTrace.carpet] = fullTrace;
64997 }
64998
64999 if(Registry.traceIs(fullTrace, 'carpetDependent')) {
65000 carpetDependents.push(i);
65001 }
65002 }
65003
65004 for(i = 0; i < carpetDependents.length; i++) {
65005 fullTrace = dataOut[carpetDependents[i]];
65006
65007 if(!fullTrace.visible) continue;
65008
65009 var carpetAxis = carpetIndex[fullTrace.carpet];
65010 fullTrace._carpet = carpetAxis;
65011
65012 if(!carpetAxis || !carpetAxis.visible) {
65013 fullTrace.visible = false;
65014 continue;
65015 }
65016
65017 fullTrace.xaxis = carpetAxis.xaxis;
65018 fullTrace.yaxis = carpetAxis.yaxis;
65019 }
65020};
65021
65022plots.supplyAnimationDefaults = function(opts) {
65023 opts = opts || {};
65024 var i;
65025 var optsOut = {};
65026
65027 function coerce(attr, dflt) {
65028 return Lib.coerce(opts || {}, optsOut, animationAttrs, attr, dflt);
65029 }
65030
65031 coerce('mode');
65032 coerce('direction');
65033 coerce('fromcurrent');
65034
65035 if(Array.isArray(opts.frame)) {
65036 optsOut.frame = [];
65037 for(i = 0; i < opts.frame.length; i++) {
65038 optsOut.frame[i] = plots.supplyAnimationFrameDefaults(opts.frame[i] || {});
65039 }
65040 } else {
65041 optsOut.frame = plots.supplyAnimationFrameDefaults(opts.frame || {});
65042 }
65043
65044 if(Array.isArray(opts.transition)) {
65045 optsOut.transition = [];
65046 for(i = 0; i < opts.transition.length; i++) {
65047 optsOut.transition[i] = plots.supplyAnimationTransitionDefaults(opts.transition[i] || {});
65048 }
65049 } else {
65050 optsOut.transition = plots.supplyAnimationTransitionDefaults(opts.transition || {});
65051 }
65052
65053 return optsOut;
65054};
65055
65056plots.supplyAnimationFrameDefaults = function(opts) {
65057 var optsOut = {};
65058
65059 function coerce(attr, dflt) {
65060 return Lib.coerce(opts || {}, optsOut, animationAttrs.frame, attr, dflt);
65061 }
65062
65063 coerce('duration');
65064 coerce('redraw');
65065
65066 return optsOut;
65067};
65068
65069plots.supplyAnimationTransitionDefaults = function(opts) {
65070 var optsOut = {};
65071
65072 function coerce(attr, dflt) {
65073 return Lib.coerce(opts || {}, optsOut, animationAttrs.transition, attr, dflt);
65074 }
65075
65076 coerce('duration');
65077 coerce('easing');
65078
65079 return optsOut;
65080};
65081
65082plots.supplyFrameDefaults = function(frameIn) {
65083 var frameOut = {};
65084
65085 function coerce(attr, dflt) {
65086 return Lib.coerce(frameIn, frameOut, frameAttrs, attr, dflt);
65087 }
65088
65089 coerce('group');
65090 coerce('name');
65091 coerce('traces');
65092 coerce('baseframe');
65093 coerce('data');
65094 coerce('layout');
65095
65096 return frameOut;
65097};
65098
65099plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, traceInIndex) {
65100 var colorway = layout.colorway || Color.defaults;
65101 var defaultColor = colorway[colorIndex % colorway.length];
65102
65103 var i;
65104
65105 function coerce(attr, dflt) {
65106 return Lib.coerce(traceIn, traceOut, plots.attributes, attr, dflt);
65107 }
65108
65109 var visible = coerce('visible');
65110
65111 coerce('type');
65112 coerce('name', layout._traceWord + ' ' + traceInIndex);
65113
65114 coerce('uirevision', layout.uirevision);
65115
65116 // we want even invisible traces to make their would-be subplots visible
65117 // so coerce the subplot id(s) now no matter what
65118 var _module = plots.getModule(traceOut);
65119
65120 traceOut._module = _module;
65121 if(_module) {
65122 var basePlotModule = _module.basePlotModule;
65123 var subplotAttr = basePlotModule.attr;
65124 var subplotAttrs = basePlotModule.attributes;
65125 if(subplotAttr && subplotAttrs) {
65126 var subplots = layout._subplots;
65127 var subplotId = '';
65128
65129 if(
65130 visible ||
65131 basePlotModule.name !== 'gl2d' // for now just drop empty gl2d subplots
65132 // TODO - currently if we draw an empty gl2d subplot, it draws
65133 // nothing then gets stuck and you can't get it back without newPlot
65134 // sort this out in the regl refactor?
65135 ) {
65136 if(Array.isArray(subplotAttr)) {
65137 for(i = 0; i < subplotAttr.length; i++) {
65138 var attri = subplotAttr[i];
65139 var vali = Lib.coerce(traceIn, traceOut, subplotAttrs, attri);
65140
65141 if(subplots[attri]) Lib.pushUnique(subplots[attri], vali);
65142 subplotId += vali;
65143 }
65144 } else {
65145 subplotId = Lib.coerce(traceIn, traceOut, subplotAttrs, subplotAttr);
65146 }
65147
65148 if(subplots[basePlotModule.name]) {
65149 Lib.pushUnique(subplots[basePlotModule.name], subplotId);
65150 }
65151 }
65152 }
65153 }
65154
65155 if(visible) {
65156 coerce('customdata');
65157 coerce('ids');
65158 coerce('meta');
65159
65160 if(Registry.traceIs(traceOut, 'showLegend')) {
65161 Lib.coerce(traceIn, traceOut,
65162 _module.attributes.showlegend ? _module.attributes : plots.attributes,
65163 'showlegend'
65164 );
65165
65166 coerce('legendgroup');
65167
65168 traceOut._dfltShowLegend = true;
65169 } else {
65170 traceOut._dfltShowLegend = false;
65171 }
65172
65173 if(_module) {
65174 _module.supplyDefaults(traceIn, traceOut, defaultColor, layout);
65175 }
65176
65177 if(!Registry.traceIs(traceOut, 'noOpacity')) {
65178 coerce('opacity');
65179 }
65180
65181 if(Registry.traceIs(traceOut, 'notLegendIsolatable')) {
65182 // This clears out the legendonly state for traces like carpet that
65183 // cannot be isolated in the legend
65184 traceOut.visible = !!traceOut.visible;
65185 }
65186
65187 if(!Registry.traceIs(traceOut, 'noHover')) {
65188 if(!traceOut.hovertemplate) Lib.coerceHoverinfo(traceIn, traceOut, layout);
65189
65190 // parcats support hover, but not hoverlabel stylings (yet)
65191 if(traceOut.type !== 'parcats') {
65192 Registry.getComponentMethod('fx', 'supplyDefaults')(traceIn, traceOut, defaultColor, layout);
65193 }
65194 }
65195
65196 if(_module && _module.selectPoints) {
65197 coerce('selectedpoints');
65198 }
65199
65200 plots.supplyTransformDefaults(traceIn, traceOut, layout);
65201 }
65202
65203 return traceOut;
65204};
65205
65206/**
65207 * hasMakesDataTransform: does this trace have a transform that makes its own
65208 * data, either by grabbing it from somewhere else or by creating it from input
65209 * parameters? If so, we should still keep going with supplyDefaults
65210 * even if the trace is invisible, which may just be because it has no data yet.
65211 */
65212function hasMakesDataTransform(trace) {
65213 var transforms = trace.transforms;
65214 if(Array.isArray(transforms) && transforms.length) {
65215 for(var i = 0; i < transforms.length; i++) {
65216 var ti = transforms[i];
65217 var _module = ti._module || transformsRegistry[ti.type];
65218 if(_module && _module.makesData) return true;
65219 }
65220 }
65221 return false;
65222}
65223
65224plots.hasMakesDataTransform = hasMakesDataTransform;
65225
65226plots.supplyTransformDefaults = function(traceIn, traceOut, layout) {
65227 // For now we only allow transforms on 1D traces, ie those that specify a _length.
65228 // If we were to implement 2D transforms, we'd need to have each transform
65229 // describe its own applicability and disable itself when it doesn't apply.
65230 // Also allow transforms that make their own data, but not in globalTransforms
65231 if(!(traceOut._length || hasMakesDataTransform(traceIn))) return;
65232
65233 var globalTransforms = layout._globalTransforms || [];
65234 var transformModules = layout._transformModules || [];
65235
65236 if(!Array.isArray(traceIn.transforms) && globalTransforms.length === 0) return;
65237
65238 var containerIn = traceIn.transforms || [];
65239 var transformList = globalTransforms.concat(containerIn);
65240 var containerOut = traceOut.transforms = [];
65241
65242 for(var i = 0; i < transformList.length; i++) {
65243 var transformIn = transformList[i];
65244 var type = transformIn.type;
65245 var _module = transformsRegistry[type];
65246 var transformOut;
65247
65248 /*
65249 * Supply defaults may run twice. First pass runs all supply defaults steps
65250 * and adds the _module to any output transforms.
65251 * If transforms exist another pass is run so that any generated traces also
65252 * go through supply defaults. This has the effect of rerunning
65253 * supplyTransformDefaults. If the transform does not have a `transform`
65254 * function it could not have generated any new traces and the second stage
65255 * is unnecessary. We detect this case with the following variables.
65256 */
65257 var isFirstStage = !(transformIn._module && transformIn._module === _module);
65258 var doLaterStages = _module && typeof _module.transform === 'function';
65259
65260 if(!_module) Lib.warn('Unrecognized transform type ' + type + '.');
65261
65262 if(_module && _module.supplyDefaults && (isFirstStage || doLaterStages)) {
65263 transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn);
65264 transformOut.type = type;
65265 transformOut._module = _module;
65266
65267 Lib.pushUnique(transformModules, _module);
65268 } else {
65269 transformOut = Lib.extendFlat({}, transformIn);
65270 }
65271
65272 containerOut.push(transformOut);
65273 }
65274};
65275
65276function applyTransforms(fullTrace, fullData, layout, fullLayout) {
65277 var container = fullTrace.transforms;
65278 var dataOut = [fullTrace];
65279
65280 for(var i = 0; i < container.length; i++) {
65281 var transform = container[i];
65282 var _module = transformsRegistry[transform.type];
65283
65284 if(_module && _module.transform) {
65285 dataOut = _module.transform(dataOut, {
65286 transform: transform,
65287 fullTrace: fullTrace,
65288 fullData: fullData,
65289 layout: layout,
65290 fullLayout: fullLayout,
65291 transformIndex: i
65292 });
65293 }
65294 }
65295
65296 return dataOut;
65297}
65298
65299plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
65300 function coerce(attr, dflt) {
65301 return Lib.coerce(layoutIn, layoutOut, plots.layoutAttributes, attr, dflt);
65302 }
65303
65304 var template = layoutIn.template;
65305 if(Lib.isPlainObject(template)) {
65306 layoutOut.template = template;
65307 layoutOut._template = template.layout;
65308 layoutOut._dataTemplate = template.data;
65309 }
65310
65311 var globalFont = Lib.coerceFont(coerce, 'font');
65312
65313 coerce('title.text', layoutOut._dfltTitle.plot);
65314
65315 Lib.coerceFont(coerce, 'title.font', {
65316 family: globalFont.family,
65317 size: Math.round(globalFont.size * 1.4),
65318 color: globalFont.color
65319 });
65320
65321 coerce('title.xref');
65322 coerce('title.yref');
65323 coerce('title.x');
65324 coerce('title.y');
65325 coerce('title.xanchor');
65326 coerce('title.yanchor');
65327 coerce('title.pad.t');
65328 coerce('title.pad.r');
65329 coerce('title.pad.b');
65330 coerce('title.pad.l');
65331
65332 var uniformtextMode = coerce('uniformtext.mode');
65333 if(uniformtextMode) {
65334 coerce('uniformtext.minsize');
65335 }
65336
65337 // Make sure that autosize is defaulted to *true*
65338 // on layouts with no set width and height for backward compatibly,
65339 // in particular https://plotly.com/javascript/responsive-fluid-layout/
65340 //
65341 // Before https://github.com/plotly/plotly.js/pull/635 ,
65342 // layouts with no set width and height were set temporary set to 'initial'
65343 // to pass through the autosize routine
65344 //
65345 // This behavior is subject to change in v2.
65346 coerce('autosize', !(layoutIn.width && layoutIn.height));
65347
65348 coerce('width');
65349 coerce('height');
65350 coerce('margin.l');
65351 coerce('margin.r');
65352 coerce('margin.t');
65353 coerce('margin.b');
65354 coerce('margin.pad');
65355 coerce('margin.autoexpand');
65356
65357 if(layoutIn.width && layoutIn.height) plots.sanitizeMargins(layoutOut);
65358
65359 Registry.getComponentMethod('grid', 'sizeDefaults')(layoutIn, layoutOut);
65360
65361 coerce('paper_bgcolor');
65362
65363 coerce('separators', formatObj.decimal + formatObj.thousands);
65364 coerce('hidesources');
65365
65366 coerce('colorway');
65367
65368 coerce('datarevision');
65369 var uirevision = coerce('uirevision');
65370 coerce('editrevision', uirevision);
65371 coerce('selectionrevision', uirevision);
65372
65373 coerce('modebar.orientation');
65374 coerce('modebar.bgcolor', Color.addOpacity(layoutOut.paper_bgcolor, 0.5));
65375 var modebarDefaultColor = Color.contrast(Color.rgb(layoutOut.modebar.bgcolor));
65376 coerce('modebar.color', Color.addOpacity(modebarDefaultColor, 0.3));
65377 coerce('modebar.activecolor', Color.addOpacity(modebarDefaultColor, 0.7));
65378 coerce('modebar.uirevision', uirevision);
65379
65380 Registry.getComponentMethod(
65381 'shapes',
65382 'supplyDrawNewShapeDefaults'
65383 )(layoutIn, layoutOut, coerce);
65384
65385 coerce('meta');
65386
65387 // do not include defaults in fullLayout when users do not set transition
65388 if(Lib.isPlainObject(layoutIn.transition)) {
65389 coerce('transition.duration');
65390 coerce('transition.easing');
65391 coerce('transition.ordering');
65392 }
65393
65394 Registry.getComponentMethod(
65395 'calendars',
65396 'handleDefaults'
65397 )(layoutIn, layoutOut, 'calendar');
65398
65399 Registry.getComponentMethod(
65400 'fx',
65401 'supplyLayoutGlobalDefaults'
65402 )(layoutIn, layoutOut, coerce);
65403};
65404
65405function getComputedSize(attr) {
65406 return (
65407 (typeof attr === 'string') &&
65408 (attr.substr(attr.length - 2) === 'px') &&
65409 parseFloat(attr)
65410 );
65411}
65412
65413
65414plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
65415 var context = gd._context || {};
65416 var frameMargins = context.frameMargins;
65417 var newWidth;
65418 var newHeight;
65419
65420 var isPlotDiv = Lib.isPlotDiv(gd);
65421
65422 if(isPlotDiv) gd.emit('plotly_autosize');
65423
65424 // embedded in an iframe - just take the full iframe size
65425 // if we get to this point, with no aspect ratio restrictions
65426 if(context.fillFrame) {
65427 newWidth = window.innerWidth;
65428 newHeight = window.innerHeight;
65429
65430 // somehow we get a few extra px height sometimes...
65431 // just hide it
65432 document.body.style.overflow = 'hidden';
65433 } else {
65434 // plotly.js - let the developers do what they want, either
65435 // provide height and width for the container div,
65436 // specify size in layout, or take the defaults,
65437 // but don't enforce any ratio restrictions
65438 var computedStyle = isPlotDiv ? window.getComputedStyle(gd) : {};
65439
65440 newWidth = getComputedSize(computedStyle.width) || getComputedSize(computedStyle.maxWidth) || fullLayout.width;
65441 newHeight = getComputedSize(computedStyle.height) || getComputedSize(computedStyle.maxHeight) || fullLayout.height;
65442
65443 if(isNumeric(frameMargins) && frameMargins > 0) {
65444 var factor = 1 - 2 * frameMargins;
65445 newWidth = Math.round(factor * newWidth);
65446 newHeight = Math.round(factor * newHeight);
65447 }
65448 }
65449
65450 var minWidth = plots.layoutAttributes.width.min;
65451 var minHeight = plots.layoutAttributes.height.min;
65452 if(newWidth < minWidth) newWidth = minWidth;
65453 if(newHeight < minHeight) newHeight = minHeight;
65454
65455 var widthHasChanged = !layout.width &&
65456 (Math.abs(fullLayout.width - newWidth) > 1);
65457 var heightHasChanged = !layout.height &&
65458 (Math.abs(fullLayout.height - newHeight) > 1);
65459
65460 if(heightHasChanged || widthHasChanged) {
65461 if(widthHasChanged) fullLayout.width = newWidth;
65462 if(heightHasChanged) fullLayout.height = newHeight;
65463 }
65464
65465 // cache initial autosize value, used in relayout when
65466 // width or height values are set to null
65467 if(!gd._initialAutoSize) {
65468 gd._initialAutoSize = { width: newWidth, height: newHeight };
65469 }
65470
65471 plots.sanitizeMargins(fullLayout);
65472};
65473
65474plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, transitionData) {
65475 var componentsRegistry = Registry.componentsRegistry;
65476 var basePlotModules = layoutOut._basePlotModules;
65477 var component, i, _module;
65478
65479 var Cartesian = Registry.subplotsRegistry.cartesian;
65480
65481 // check if any components need to add more base plot modules
65482 // that weren't captured by traces
65483 for(component in componentsRegistry) {
65484 _module = componentsRegistry[component];
65485
65486 if(_module.includeBasePlot) {
65487 _module.includeBasePlot(layoutIn, layoutOut);
65488 }
65489 }
65490
65491 // make sure we *at least* have some cartesian axes
65492 if(!basePlotModules.length) {
65493 basePlotModules.push(Cartesian);
65494 }
65495
65496 // ensure all cartesian axes have at least one subplot
65497 if(layoutOut._has('cartesian')) {
65498 Registry.getComponentMethod('grid', 'contentDefaults')(layoutIn, layoutOut);
65499 Cartesian.finalizeSubplots(layoutIn, layoutOut);
65500 }
65501
65502 // sort subplot lists
65503 for(var subplotType in layoutOut._subplots) {
65504 layoutOut._subplots[subplotType].sort(Lib.subplotSort);
65505 }
65506
65507 // base plot module layout defaults
65508 for(i = 0; i < basePlotModules.length; i++) {
65509 _module = basePlotModules[i];
65510
65511 // e.g. pie does not have a layout-defaults step
65512 if(_module.supplyLayoutDefaults) {
65513 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65514 }
65515 }
65516
65517 // trace module layout defaults
65518 // use _modules rather than _visibleModules so that even
65519 // legendonly traces can include settings - eg barmode, which affects
65520 // legend.traceorder default value.
65521 var modules = layoutOut._modules;
65522 for(i = 0; i < modules.length; i++) {
65523 _module = modules[i];
65524
65525 if(_module.supplyLayoutDefaults) {
65526 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65527 }
65528 }
65529
65530 // transform module layout defaults
65531 var transformModules = layoutOut._transformModules;
65532 for(i = 0; i < transformModules.length; i++) {
65533 _module = transformModules[i];
65534
65535 if(_module.supplyLayoutDefaults) {
65536 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData, transitionData);
65537 }
65538 }
65539
65540 for(component in componentsRegistry) {
65541 _module = componentsRegistry[component];
65542
65543 if(_module.supplyLayoutDefaults) {
65544 _module.supplyLayoutDefaults(layoutIn, layoutOut, fullData);
65545 }
65546 }
65547};
65548
65549// Remove all plotly attributes from a div so it can be replotted fresh
65550// TODO: these really need to be encapsulated into a much smaller set...
65551plots.purge = function(gd) {
65552 // note: we DO NOT remove _context because it doesn't change when we insert
65553 // a new plot, and may have been set outside of our scope.
65554
65555 var fullLayout = gd._fullLayout || {};
65556 if(fullLayout._glcontainer !== undefined) {
65557 fullLayout._glcontainer.selectAll('.gl-canvas').remove();
65558 fullLayout._glcontainer.remove();
65559 fullLayout._glcanvas = null;
65560 }
65561
65562 // remove modebar
65563 if(fullLayout._modeBar) fullLayout._modeBar.destroy();
65564
65565 if(gd._transitionData) {
65566 // Ensure any dangling callbacks are simply dropped if the plot is purged.
65567 // This is more or less only actually important for testing.
65568 if(gd._transitionData._interruptCallbacks) {
65569 gd._transitionData._interruptCallbacks.length = 0;
65570 }
65571
65572 if(gd._transitionData._animationRaf) {
65573 window.cancelAnimationFrame(gd._transitionData._animationRaf);
65574 }
65575 }
65576
65577 // remove any planned throttles
65578 Lib.clearThrottle();
65579
65580 // remove responsive handler
65581 Lib.clearResponsive(gd);
65582
65583 // data and layout
65584 delete gd.data;
65585 delete gd.layout;
65586 delete gd._fullData;
65587 delete gd._fullLayout;
65588 delete gd.calcdata;
65589 delete gd.framework;
65590 delete gd.empty;
65591
65592 delete gd.fid;
65593
65594 delete gd.undoqueue; // action queue
65595 delete gd.undonum;
65596 delete gd.autoplay; // are we doing an action that doesn't go in undo queue?
65597 delete gd.changed;
65598
65599 // these get recreated on Plotly.plot anyway, but just to be safe
65600 // (and to have a record of them...)
65601 delete gd._promises;
65602 delete gd._redrawTimer;
65603 delete gd._hmlumcount;
65604 delete gd._hmpixcount;
65605 delete gd._transitionData;
65606 delete gd._transitioning;
65607 delete gd._initialAutoSize;
65608 delete gd._transitioningWithDuration;
65609
65610 // created during certain events, that *should* clean them up
65611 // themselves, but may not if there was an error
65612 delete gd._dragging;
65613 delete gd._dragged;
65614 delete gd._dragdata;
65615 delete gd._hoverdata;
65616 delete gd._snapshotInProgress;
65617 delete gd._editing;
65618 delete gd._mouseDownTime;
65619 delete gd._legendMouseDownTime;
65620
65621 // remove all event listeners
65622 if(gd.removeAllListeners) gd.removeAllListeners();
65623};
65624
65625plots.style = function(gd) {
65626 var _modules = gd._fullLayout._visibleModules;
65627 var styleModules = [];
65628 var i;
65629
65630 // some trace modules reuse the same style method,
65631 // make sure to not unnecessary call them multiple times.
65632
65633 for(i = 0; i < _modules.length; i++) {
65634 var _module = _modules[i];
65635 if(_module.style) {
65636 Lib.pushUnique(styleModules, _module.style);
65637 }
65638 }
65639
65640 for(i = 0; i < styleModules.length; i++) {
65641 styleModules[i](gd);
65642 }
65643};
65644
65645plots.sanitizeMargins = function(fullLayout) {
65646 // polar doesn't do margins...
65647 if(!fullLayout || !fullLayout.margin) return;
65648
65649 var width = fullLayout.width;
65650 var height = fullLayout.height;
65651 var margin = fullLayout.margin;
65652 var plotWidth = width - (margin.l + margin.r);
65653 var plotHeight = height - (margin.t + margin.b);
65654 var correction;
65655
65656 // if margin.l + margin.r = 0 then plotWidth > 0
65657 // as width >= 10 by supplyDefaults
65658 // similarly for margin.t + margin.b
65659
65660 if(plotWidth < 0) {
65661 correction = (width - 1) / (margin.l + margin.r);
65662 margin.l = Math.floor(correction * margin.l);
65663 margin.r = Math.floor(correction * margin.r);
65664 }
65665
65666 if(plotHeight < 0) {
65667 correction = (height - 1) / (margin.t + margin.b);
65668 margin.t = Math.floor(correction * margin.t);
65669 margin.b = Math.floor(correction * margin.b);
65670 }
65671};
65672
65673plots.clearAutoMarginIds = function(gd) {
65674 gd._fullLayout._pushmarginIds = {};
65675};
65676
65677plots.allowAutoMargin = function(gd, id) {
65678 gd._fullLayout._pushmarginIds[id] = 1;
65679};
65680
65681function initMargins(fullLayout) {
65682 var margin = fullLayout.margin;
65683
65684 if(!fullLayout._size) {
65685 var gs = fullLayout._size = {
65686 l: Math.round(margin.l),
65687 r: Math.round(margin.r),
65688 t: Math.round(margin.t),
65689 b: Math.round(margin.b),
65690 p: Math.round(margin.pad)
65691 };
65692 gs.w = Math.round(fullLayout.width) - gs.l - gs.r;
65693 gs.h = Math.round(fullLayout.height) - gs.t - gs.b;
65694 }
65695 if(!fullLayout._pushmargin) fullLayout._pushmargin = {};
65696 if(!fullLayout._pushmarginIds) fullLayout._pushmarginIds = {};
65697}
65698
65699/**
65700 * autoMargin: called by components that may need to expand the margins to
65701 * be rendered on-plot.
65702 *
65703 * @param {DOM element} gd
65704 * @param {string} id - an identifier unique (within this plot) to this object,
65705 * so we can remove a previous margin expansion from the same object.
65706 * @param {object} o - the margin requirements of this object, or omit to delete
65707 * this entry (like if it's hidden). Keys are:
65708 * x, y: plot fraction of the anchor point.
65709 * xl, xr, yt, yb: if the object has an extent defined in plot fraction,
65710 * you can specify both edges as plot fractions in each dimension
65711 * l, r, t, b: the pixels to pad past the plot fraction x[l|r] and y[t|b]
65712 * pad: extra pixels to add in all directions, default 12 (why?)
65713 */
65714plots.autoMargin = function(gd, id, o) {
65715 var fullLayout = gd._fullLayout;
65716
65717 var pushMargin = fullLayout._pushmargin;
65718 var pushMarginIds = fullLayout._pushmarginIds;
65719
65720 if(fullLayout.margin.autoexpand !== false) {
65721 if(!o) {
65722 delete pushMargin[id];
65723 delete pushMarginIds[id];
65724 } else {
65725 var pad = o.pad;
65726 if(pad === undefined) {
65727 var margin = fullLayout.margin;
65728 // if no explicit pad is given, use 12px unless there's a
65729 // specified margin that's smaller than that
65730 pad = Math.min(12, margin.l, margin.r, margin.t, margin.b);
65731 }
65732
65733 // if the item is too big, just give it enough automargin to
65734 // make sure you can still grab it and bring it back
65735 if(o.l + o.r > fullLayout.width * 0.5) {
65736 Lib.log('Margin push', id, 'is too big in x, dropping');
65737 o.l = o.r = 0;
65738 }
65739 if(o.b + o.t > fullLayout.height * 0.5) {
65740 Lib.log('Margin push', id, 'is too big in y, dropping');
65741 o.b = o.t = 0;
65742 }
65743
65744 var xl = o.xl !== undefined ? o.xl : o.x;
65745 var xr = o.xr !== undefined ? o.xr : o.x;
65746 var yt = o.yt !== undefined ? o.yt : o.y;
65747 var yb = o.yb !== undefined ? o.yb : o.y;
65748
65749 pushMargin[id] = {
65750 l: {val: xl, size: o.l + pad},
65751 r: {val: xr, size: o.r + pad},
65752 b: {val: yb, size: o.b + pad},
65753 t: {val: yt, size: o.t + pad}
65754 };
65755 pushMarginIds[id] = 1;
65756 }
65757
65758 if(!fullLayout._replotting) {
65759 return plots.doAutoMargin(gd);
65760 }
65761 }
65762};
65763
65764plots.doAutoMargin = function(gd) {
65765 var fullLayout = gd._fullLayout;
65766 if(!fullLayout._size) fullLayout._size = {};
65767 initMargins(fullLayout);
65768
65769 var gs = fullLayout._size;
65770 var margin = fullLayout.margin;
65771 var oldMargins = Lib.extendFlat({}, gs);
65772
65773 // adjust margins for outside components
65774 // fullLayout.margin is the requested margin,
65775 // fullLayout._size has margins and plotsize after adjustment
65776 var ml = margin.l;
65777 var mr = margin.r;
65778 var mt = margin.t;
65779 var mb = margin.b;
65780 var width = fullLayout.width;
65781 var height = fullLayout.height;
65782 var pushMargin = fullLayout._pushmargin;
65783 var pushMarginIds = fullLayout._pushmarginIds;
65784
65785 if(fullLayout.margin.autoexpand !== false) {
65786 for(var k in pushMargin) {
65787 if(!pushMarginIds[k]) delete pushMargin[k];
65788 }
65789
65790 // fill in the requested margins
65791 pushMargin.base = {
65792 l: {val: 0, size: ml},
65793 r: {val: 1, size: mr},
65794 t: {val: 1, size: mt},
65795 b: {val: 0, size: mb}
65796 };
65797
65798 // now cycle through all the combinations of l and r
65799 // (and t and b) to find the required margins
65800
65801 for(var k1 in pushMargin) {
65802 var pushleft = pushMargin[k1].l || {};
65803 var pushbottom = pushMargin[k1].b || {};
65804 var fl = pushleft.val;
65805 var pl = pushleft.size;
65806 var fb = pushbottom.val;
65807 var pb = pushbottom.size;
65808
65809 for(var k2 in pushMargin) {
65810 if(isNumeric(pl) && pushMargin[k2].r) {
65811 var fr = pushMargin[k2].r.val;
65812 var pr = pushMargin[k2].r.size;
65813
65814 if(fr > fl) {
65815 var newL = (pl * fr + (pr - width) * fl) / (fr - fl);
65816 var newR = (pr * (1 - fl) + (pl - width) * (1 - fr)) / (fr - fl);
65817 if(newL >= 0 && newR >= 0 && width - (newL + newR) > 0 && newL + newR > ml + mr) {
65818 ml = newL;
65819 mr = newR;
65820 }
65821 }
65822 }
65823
65824 if(isNumeric(pb) && pushMargin[k2].t) {
65825 var ft = pushMargin[k2].t.val;
65826 var pt = pushMargin[k2].t.size;
65827
65828 if(ft > fb) {
65829 var newB = (pb * ft + (pt - height) * fb) / (ft - fb);
65830 var newT = (pt * (1 - fb) + (pb - height) * (1 - ft)) / (ft - fb);
65831 if(newB >= 0 && newT >= 0 && height - (newT + newB) > 0 && newB + newT > mb + mt) {
65832 mb = newB;
65833 mt = newT;
65834 }
65835 }
65836 }
65837 }
65838 }
65839 }
65840
65841 gs.l = Math.round(ml);
65842 gs.r = Math.round(mr);
65843 gs.t = Math.round(mt);
65844 gs.b = Math.round(mb);
65845 gs.p = Math.round(margin.pad);
65846 gs.w = Math.round(width) - gs.l - gs.r;
65847 gs.h = Math.round(height) - gs.t - gs.b;
65848
65849 // if things changed and we're not already redrawing, trigger a redraw
65850 if(!fullLayout._replotting && plots.didMarginChange(oldMargins, gs)) {
65851 if('_redrawFromAutoMarginCount' in fullLayout) {
65852 fullLayout._redrawFromAutoMarginCount++;
65853 } else {
65854 fullLayout._redrawFromAutoMarginCount = 1;
65855 }
65856
65857 // Always allow at least one redraw and give each margin-push
65858 // call 3 loops to converge. Of course, for most cases this way too many,
65859 // but let's keep things on the safe side until we fix our
65860 // auto-margin pipeline problems:
65861 // https://github.com/plotly/plotly.js/issues/2704
65862 var maxNumberOfRedraws = 3 * (1 + Object.keys(pushMarginIds).length);
65863
65864 if(fullLayout._redrawFromAutoMarginCount < maxNumberOfRedraws) {
65865 return Registry.call('plot', gd);
65866 } else {
65867 Lib.warn('Too many auto-margin redraws.');
65868 }
65869 }
65870};
65871
65872var marginKeys = ['l', 'r', 't', 'b', 'p', 'w', 'h'];
65873
65874plots.didMarginChange = function(margin0, margin1) {
65875 for(var i = 0; i < marginKeys.length; i++) {
65876 var k = marginKeys[i];
65877 var m0 = margin0[k];
65878 var m1 = margin1[k];
65879 // use 1px tolerance in case we old/new differ only
65880 // by rounding errors, which can lead to infinite loops
65881 if(!isNumeric(m0) || Math.abs(m1 - m0) > 1) {
65882 return true;
65883 }
65884 }
65885 return false;
65886};
65887
65888/**
65889 * JSONify the graph data and layout
65890 *
65891 * This function needs to recurse because some src can be inside
65892 * sub-objects.
65893 *
65894 * It also strips out functions and private (starts with _) elements.
65895 * Therefore, we can add temporary things to data and layout that don't
65896 * get saved.
65897 *
65898 * @param gd The graphDiv
65899 * @param {Boolean} dataonly If true, don't return layout.
65900 * @param {'keepref'|'keepdata'|'keepall'} [mode='keepref'] Filter what's kept
65901 * keepref: remove data for which there's a src present
65902 * eg if there's xsrc present (and xsrc is well-formed,
65903 * ie has : and some chars before it), strip out x
65904 * keepdata: remove all src tags, don't remove the data itself
65905 * keepall: keep data and src
65906 * @param {String} output If you specify 'object', the result will not be stringified
65907 * @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData
65908 * @param {Boolean} includeConfig If truthy, include _context
65909 * @returns {Object|String}
65910 */
65911plots.graphJson = function(gd, dataonly, mode, output, useDefaults, includeConfig) {
65912 // if the defaults aren't supplied yet, we need to do that...
65913 if((useDefaults && dataonly && !gd._fullData) ||
65914 (useDefaults && !dataonly && !gd._fullLayout)) {
65915 plots.supplyDefaults(gd);
65916 }
65917
65918 var data = (useDefaults) ? gd._fullData : gd.data;
65919 var layout = (useDefaults) ? gd._fullLayout : gd.layout;
65920 var frames = (gd._transitionData || {})._frames;
65921
65922 function stripObj(d, keepFunction) {
65923 if(typeof d === 'function') {
65924 return keepFunction ? '_function_' : null;
65925 }
65926 if(Lib.isPlainObject(d)) {
65927 var o = {};
65928 var src;
65929 Object.keys(d).sort().forEach(function(v) {
65930 // remove private elements and functions
65931 // _ is for private, [ is a mistake ie [object Object]
65932 if(['_', '['].indexOf(v.charAt(0)) !== -1) return;
65933
65934 // if a function, add if necessary then move on
65935 if(typeof d[v] === 'function') {
65936 if(keepFunction) o[v] = '_function';
65937 return;
65938 }
65939
65940 // look for src/data matches and remove the appropriate one
65941 if(mode === 'keepdata') {
65942 // keepdata: remove all ...src tags
65943 if(v.substr(v.length - 3) === 'src') {
65944 return;
65945 }
65946 } else if(mode === 'keepstream') {
65947 // keep sourced data if it's being streamed.
65948 // similar to keepref, but if the 'stream' object exists
65949 // in a trace, we will keep the data array.
65950 src = d[v + 'src'];
65951 if(typeof src === 'string' && src.indexOf(':') > 0) {
65952 if(!Lib.isPlainObject(d.stream)) {
65953 return;
65954 }
65955 }
65956 } else if(mode !== 'keepall') {
65957 // keepref: remove sourced data but only
65958 // if the source tag is well-formed
65959 src = d[v + 'src'];
65960 if(typeof src === 'string' && src.indexOf(':') > 0) {
65961 return;
65962 }
65963 }
65964
65965 // OK, we're including this... recurse into it
65966 o[v] = stripObj(d[v], keepFunction);
65967 });
65968 return o;
65969 }
65970
65971 if(Array.isArray(d)) {
65972 return d.map(function(x) {return stripObj(x, keepFunction);});
65973 }
65974
65975 if(Lib.isTypedArray(d)) {
65976 return Lib.simpleMap(d, Lib.identity);
65977 }
65978
65979 // convert native dates to date strings...
65980 // mostly for external users exporting to plotly
65981 if(Lib.isJSDate(d)) return Lib.ms2DateTimeLocal(+d);
65982
65983 return d;
65984 }
65985
65986 var obj = {
65987 data: (data || []).map(function(v) {
65988 var d = stripObj(v);
65989 // fit has some little arrays in it that don't contain data,
65990 // just fit params and meta
65991 if(dataonly) { delete d.fit; }
65992 return d;
65993 })
65994 };
65995 if(!dataonly) { obj.layout = stripObj(layout); }
65996
65997 if(gd.framework && gd.framework.isPolar) obj = gd.framework.getConfig();
65998
65999 if(frames) obj.frames = stripObj(frames);
66000
66001 if(includeConfig) obj.config = stripObj(gd._context, true);
66002
66003 return (output === 'object') ? obj : JSON.stringify(obj);
66004};
66005
66006/**
66007 * Modify a keyframe using a list of operations:
66008 *
66009 * @param {array of objects} operations
66010 * Sequence of operations to be performed on the keyframes
66011 */
66012plots.modifyFrames = function(gd, operations) {
66013 var i, op, frame;
66014 var _frames = gd._transitionData._frames;
66015 var _frameHash = gd._transitionData._frameHash;
66016
66017 for(i = 0; i < operations.length; i++) {
66018 op = operations[i];
66019
66020 switch(op.type) {
66021 // No reason this couldn't exist, but is currently unused/untested:
66022 /* case 'rename':
66023 frame = _frames[op.index];
66024 delete _frameHash[frame.name];
66025 _frameHash[op.name] = frame;
66026 frame.name = op.name;
66027 break;*/
66028 case 'replace':
66029 frame = op.value;
66030 var oldName = (_frames[op.index] || {}).name;
66031 var newName = frame.name;
66032 _frames[op.index] = _frameHash[newName] = frame;
66033
66034 if(newName !== oldName) {
66035 // If name has changed in addition to replacement, then update
66036 // the lookup table:
66037 delete _frameHash[oldName];
66038 _frameHash[newName] = frame;
66039 }
66040
66041 break;
66042 case 'insert':
66043 frame = op.value;
66044 _frameHash[frame.name] = frame;
66045 _frames.splice(op.index, 0, frame);
66046 break;
66047 case 'delete':
66048 frame = _frames[op.index];
66049 delete _frameHash[frame.name];
66050 _frames.splice(op.index, 1);
66051 break;
66052 }
66053 }
66054
66055 return Promise.resolve();
66056};
66057
66058/*
66059 * Compute a keyframe. Merge a keyframe into its base frame(s) and
66060 * expand properties.
66061 *
66062 * @param {object} frameLookup
66063 * An object containing frames keyed by name (i.e. gd._transitionData._frameHash)
66064 * @param {string} frame
66065 * The name of the keyframe to be computed
66066 *
66067 * Returns: a new object with the merged content
66068 */
66069plots.computeFrame = function(gd, frameName) {
66070 var frameLookup = gd._transitionData._frameHash;
66071 var i, traceIndices, traceIndex, destIndex;
66072
66073 // Null or undefined will fail on .toString(). We'll allow numbers since we
66074 // make it clear frames must be given string names, but we'll allow numbers
66075 // here since they're otherwise fine for looking up frames as long as they're
66076 // properly cast to strings. We really just want to ensure here that this
66077 // 1) doesn't fail, and
66078 // 2) doens't give an incorrect answer (which String(frameName) would)
66079 if(!frameName) {
66080 throw new Error('computeFrame must be given a string frame name');
66081 }
66082
66083 var framePtr = frameLookup[frameName.toString()];
66084
66085 // Return false if the name is invalid:
66086 if(!framePtr) {
66087 return false;
66088 }
66089
66090 var frameStack = [framePtr];
66091 var frameNameStack = [framePtr.name];
66092
66093 // Follow frame pointers:
66094 while(framePtr.baseframe && (framePtr = frameLookup[framePtr.baseframe.toString()])) {
66095 // Avoid infinite loops:
66096 if(frameNameStack.indexOf(framePtr.name) !== -1) break;
66097
66098 frameStack.push(framePtr);
66099 frameNameStack.push(framePtr.name);
66100 }
66101
66102 // A new object for the merged result:
66103 var result = {};
66104
66105 // Merge, starting with the last and ending with the desired frame:
66106 while((framePtr = frameStack.pop())) {
66107 if(framePtr.layout) {
66108 result.layout = plots.extendLayout(result.layout, framePtr.layout);
66109 }
66110
66111 if(framePtr.data) {
66112 if(!result.data) {
66113 result.data = [];
66114 }
66115 traceIndices = framePtr.traces;
66116
66117 if(!traceIndices) {
66118 // If not defined, assume serial order starting at zero
66119 traceIndices = [];
66120 for(i = 0; i < framePtr.data.length; i++) {
66121 traceIndices[i] = i;
66122 }
66123 }
66124
66125 if(!result.traces) {
66126 result.traces = [];
66127 }
66128
66129 for(i = 0; i < framePtr.data.length; i++) {
66130 // Loop through this frames data, find out where it should go,
66131 // and merge it!
66132 traceIndex = traceIndices[i];
66133 if(traceIndex === undefined || traceIndex === null) {
66134 continue;
66135 }
66136
66137 destIndex = result.traces.indexOf(traceIndex);
66138 if(destIndex === -1) {
66139 destIndex = result.data.length;
66140 result.traces[destIndex] = traceIndex;
66141 }
66142
66143 result.data[destIndex] = plots.extendTrace(result.data[destIndex], framePtr.data[i]);
66144 }
66145 }
66146 }
66147
66148 return result;
66149};
66150
66151/*
66152 * Recompute the lookup table that maps frame name -> frame object. addFrames/
66153 * deleteFrames already manages this data one at a time, so the only time this
66154 * is necessary is if you poke around manually in `gd._transitionData._frames`
66155 * and create and haven't updated the lookup table.
66156 */
66157plots.recomputeFrameHash = function(gd) {
66158 var hash = gd._transitionData._frameHash = {};
66159 var frames = gd._transitionData._frames;
66160 for(var i = 0; i < frames.length; i++) {
66161 var frame = frames[i];
66162 if(frame && frame.name) {
66163 hash[frame.name] = frame;
66164 }
66165 }
66166};
66167
66168/**
66169 * Extend an object, treating container arrays very differently by extracting
66170 * their contents and merging them separately.
66171 *
66172 * This exists so that we can extendDeepNoArrays and avoid stepping into data
66173 * arrays without knowledge of the plot schema, but so that we may also manually
66174 * recurse into known container arrays, such as transforms.
66175 *
66176 * See extendTrace and extendLayout below for usage.
66177 */
66178plots.extendObjectWithContainers = function(dest, src, containerPaths) {
66179 var containerProp, containerVal, i, j, srcProp, destProp, srcContainer, destContainer;
66180 var copy = Lib.extendDeepNoArrays({}, src || {});
66181 var expandedObj = Lib.expandObjectPaths(copy);
66182 var containerObj = {};
66183
66184 // Step through and extract any container properties. Otherwise extendDeepNoArrays
66185 // will clobber any existing properties with an empty array and then supplyDefaults
66186 // will reset everything to defaults.
66187 if(containerPaths && containerPaths.length) {
66188 for(i = 0; i < containerPaths.length; i++) {
66189 containerProp = Lib.nestedProperty(expandedObj, containerPaths[i]);
66190 containerVal = containerProp.get();
66191
66192 if(containerVal === undefined) {
66193 Lib.nestedProperty(containerObj, containerPaths[i]).set(null);
66194 } else {
66195 containerProp.set(null);
66196 Lib.nestedProperty(containerObj, containerPaths[i]).set(containerVal);
66197 }
66198 }
66199 }
66200
66201 dest = Lib.extendDeepNoArrays(dest || {}, expandedObj);
66202
66203 if(containerPaths && containerPaths.length) {
66204 for(i = 0; i < containerPaths.length; i++) {
66205 srcProp = Lib.nestedProperty(containerObj, containerPaths[i]);
66206 srcContainer = srcProp.get();
66207
66208 if(!srcContainer) continue;
66209
66210 destProp = Lib.nestedProperty(dest, containerPaths[i]);
66211 destContainer = destProp.get();
66212
66213 if(!Array.isArray(destContainer)) {
66214 destContainer = [];
66215 destProp.set(destContainer);
66216 }
66217
66218 for(j = 0; j < srcContainer.length; j++) {
66219 var srcObj = srcContainer[j];
66220
66221 if(srcObj === null) destContainer[j] = null;
66222 else {
66223 destContainer[j] = plots.extendObjectWithContainers(destContainer[j], srcObj);
66224 }
66225 }
66226
66227 destProp.set(destContainer);
66228 }
66229 }
66230
66231 return dest;
66232};
66233
66234plots.dataArrayContainers = ['transforms', 'dimensions'];
66235plots.layoutArrayContainers = Registry.layoutArrayContainers;
66236
66237/*
66238 * Extend a trace definition. This method:
66239 *
66240 * 1. directly transfers any array references
66241 * 2. manually recurses into container arrays like transforms
66242 *
66243 * The result is the original object reference with the new contents merged in.
66244 */
66245plots.extendTrace = function(destTrace, srcTrace) {
66246 return plots.extendObjectWithContainers(destTrace, srcTrace, plots.dataArrayContainers);
66247};
66248
66249/*
66250 * Extend a layout definition. This method:
66251 *
66252 * 1. directly transfers any array references (not critically important for
66253 * layout since there aren't really data arrays)
66254 * 2. manually recurses into container arrays like annotations
66255 *
66256 * The result is the original object reference with the new contents merged in.
66257 */
66258plots.extendLayout = function(destLayout, srcLayout) {
66259 return plots.extendObjectWithContainers(destLayout, srcLayout, plots.layoutArrayContainers);
66260};
66261
66262/**
66263 * Transition to a set of new data and layout properties from Plotly.animate
66264 *
66265 * @param {DOM element} gd
66266 * @param {Object[]} data
66267 * an array of data objects following the normal Plotly data definition format
66268 * @param {Object} layout
66269 * a layout object, following normal Plotly layout format
66270 * @param {Number[]} traces
66271 * indices of the corresponding traces specified in `data`
66272 * @param {Object} frameOpts
66273 * options for the frame (i.e. whether to redraw post-transition)
66274 * @param {Object} transitionOpts
66275 * options for the transition
66276 */
66277plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) {
66278 var opts = {redraw: frameOpts.redraw};
66279 var transitionedTraces = {};
66280 var axEdits = [];
66281
66282 opts.prepareFn = function() {
66283 var dataLength = Array.isArray(data) ? data.length : 0;
66284 var traceIndices = traces.slice(0, dataLength);
66285
66286 for(var i = 0; i < traceIndices.length; i++) {
66287 var traceIdx = traceIndices[i];
66288 var trace = gd._fullData[traceIdx];
66289 var _module = trace._module;
66290
66291 // There's nothing to do if this module is not defined:
66292 if(!_module) continue;
66293
66294 // Don't register the trace as transitioned if it doesn't know what to do.
66295 // If it *is* registered, it will receive a callback that it's responsible
66296 // for calling in order to register the transition as having completed.
66297 if(_module.animatable) {
66298 var n = _module.basePlotModule.name;
66299 if(!transitionedTraces[n]) transitionedTraces[n] = [];
66300 transitionedTraces[n].push(traceIdx);
66301 }
66302
66303 gd.data[traceIndices[i]] = plots.extendTrace(gd.data[traceIndices[i]], data[i]);
66304 }
66305
66306 // Follow the same procedure. Clone it so we don't mangle the input, then
66307 // expand any object paths so we can merge deep into gd.layout:
66308 var layoutUpdate = Lib.expandObjectPaths(Lib.extendDeepNoArrays({}, layout));
66309
66310 // Before merging though, we need to modify the incoming layout. We only
66311 // know how to *transition* layout ranges, so it's imperative that a new
66312 // range not be sent to the layout before the transition has started. So
66313 // we must remove the things we can transition:
66314 var axisAttrRe = /^[xy]axis[0-9]*$/;
66315 for(var attr in layoutUpdate) {
66316 if(!axisAttrRe.test(attr)) continue;
66317 delete layoutUpdate[attr].range;
66318 }
66319
66320 plots.extendLayout(gd.layout, layoutUpdate);
66321
66322 // Supply defaults after applying the incoming properties. Note that any attempt
66323 // to simplify this step and reduce the amount of work resulted in the reconstruction
66324 // of essentially the whole supplyDefaults step, so that it seems sensible to just use
66325 // supplyDefaults even though it's heavier than would otherwise be desired for
66326 // transitions:
66327
66328 // first delete calcdata so supplyDefaults knows a calc step is coming
66329 delete gd.calcdata;
66330
66331 plots.supplyDefaults(gd);
66332 plots.doCalcdata(gd);
66333
66334 var newLayout = Lib.expandObjectPaths(layout);
66335
66336 if(newLayout) {
66337 var subplots = gd._fullLayout._plots;
66338
66339 for(var k in subplots) {
66340 var plotinfo = subplots[k];
66341 var xa = plotinfo.xaxis;
66342 var ya = plotinfo.yaxis;
66343 var xr0 = xa.range.slice();
66344 var yr0 = ya.range.slice();
66345
66346 var xr1 = null;
66347 var yr1 = null;
66348 var editX = null;
66349 var editY = null;
66350
66351 if(Array.isArray(newLayout[xa._name + '.range'])) {
66352 xr1 = newLayout[xa._name + '.range'].slice();
66353 } else if(Array.isArray((newLayout[xa._name] || {}).range)) {
66354 xr1 = newLayout[xa._name].range.slice();
66355 }
66356 if(Array.isArray(newLayout[ya._name + '.range'])) {
66357 yr1 = newLayout[ya._name + '.range'].slice();
66358 } else if(Array.isArray((newLayout[ya._name] || {}).range)) {
66359 yr1 = newLayout[ya._name].range.slice();
66360 }
66361
66362 if(xr0 && xr1 &&
66363 (xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1]))
66364 ) {
66365 editX = {xr0: xr0, xr1: xr1};
66366 }
66367 if(yr0 && yr1 &&
66368 (ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1]))
66369 ) {
66370 editY = {yr0: yr0, yr1: yr1};
66371 }
66372
66373 if(editX || editY) {
66374 axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY));
66375 }
66376 }
66377 }
66378
66379 return Promise.resolve();
66380 };
66381
66382 opts.runFn = function(makeCallback) {
66383 var traceTransitionOpts;
66384 var basePlotModules = gd._fullLayout._basePlotModules;
66385 var hasAxisTransition = axEdits.length;
66386 var i;
66387
66388 if(layout) {
66389 for(i = 0; i < basePlotModules.length; i++) {
66390 if(basePlotModules[i].transitionAxes) {
66391 basePlotModules[i].transitionAxes(gd, axEdits, transitionOpts, makeCallback);
66392 }
66393 }
66394 }
66395
66396 // Here handle the exception that we refuse to animate scales and axes at the same
66397 // time. In other words, if there's an axis transition, then set the data transition
66398 // to instantaneous.
66399 if(hasAxisTransition) {
66400 traceTransitionOpts = Lib.extendFlat({}, transitionOpts);
66401 traceTransitionOpts.duration = 0;
66402 // This means do not transition cartesian traces,
66403 // this happens on layout-only (e.g. axis range) animations
66404 delete transitionedTraces.cartesian;
66405 } else {
66406 traceTransitionOpts = transitionOpts;
66407 }
66408
66409 // Note that we pass a callback to *create* the callback that must be invoked on completion.
66410 // This is since not all traces know about transitions, so it greatly simplifies matters if
66411 // the trace is responsible for creating a callback, if needed, and then executing it when
66412 // the time is right.
66413 for(var n in transitionedTraces) {
66414 var traceIndices = transitionedTraces[n];
66415 var _module = gd._fullData[traceIndices[0]]._module;
66416 _module.basePlotModule.plot(gd, traceIndices, traceTransitionOpts, makeCallback);
66417 }
66418 };
66419
66420 return _transition(gd, transitionOpts, opts);
66421};
66422
66423/**
66424 * Transition to a set of new data and layout properties from Plotly.react
66425 *
66426 * @param {DOM element} gd
66427 * @param {object} restyleFlags
66428 * - anim {'all'|'some'}
66429 * @param {object} relayoutFlags
66430 * - anim {'all'|'some'}
66431 * @param {object} oldFullLayout : old (pre Plotly.react) fullLayout
66432 */
66433plots.transitionFromReact = function(gd, restyleFlags, relayoutFlags, oldFullLayout) {
66434 var fullLayout = gd._fullLayout;
66435 var transitionOpts = fullLayout.transition;
66436 var opts = {};
66437 var axEdits = [];
66438
66439 opts.prepareFn = function() {
66440 var subplots = fullLayout._plots;
66441
66442 // no need to redraw at end of transition,
66443 // if all changes are animatable
66444 opts.redraw = false;
66445 if(restyleFlags.anim === 'some') opts.redraw = true;
66446 if(relayoutFlags.anim === 'some') opts.redraw = true;
66447
66448 for(var k in subplots) {
66449 var plotinfo = subplots[k];
66450 var xa = plotinfo.xaxis;
66451 var ya = plotinfo.yaxis;
66452 var xr0 = oldFullLayout[xa._name].range.slice();
66453 var yr0 = oldFullLayout[ya._name].range.slice();
66454 var xr1 = xa.range.slice();
66455 var yr1 = ya.range.slice();
66456
66457 xa.setScale();
66458 ya.setScale();
66459
66460 var editX = null;
66461 var editY = null;
66462
66463 if(xa.r2l(xr0[0]) !== xa.r2l(xr1[0]) || xa.r2l(xr0[1]) !== xa.r2l(xr1[1])) {
66464 editX = {xr0: xr0, xr1: xr1};
66465 }
66466 if(ya.r2l(yr0[0]) !== ya.r2l(yr1[0]) || ya.r2l(yr0[1]) !== ya.r2l(yr1[1])) {
66467 editY = {yr0: yr0, yr1: yr1};
66468 }
66469
66470 if(editX || editY) {
66471 axEdits.push(Lib.extendFlat({plotinfo: plotinfo}, editX, editY));
66472 }
66473 }
66474
66475 return Promise.resolve();
66476 };
66477
66478 opts.runFn = function(makeCallback) {
66479 var fullData = gd._fullData;
66480 var fullLayout = gd._fullLayout;
66481 var basePlotModules = fullLayout._basePlotModules;
66482
66483 var axisTransitionOpts;
66484 var traceTransitionOpts;
66485 var transitionedTraces;
66486
66487 var allTraceIndices = [];
66488 for(var i = 0; i < fullData.length; i++) {
66489 allTraceIndices.push(i);
66490 }
66491
66492 function transitionAxes() {
66493 for(var j = 0; j < basePlotModules.length; j++) {
66494 if(basePlotModules[j].transitionAxes) {
66495 basePlotModules[j].transitionAxes(gd, axEdits, axisTransitionOpts, makeCallback);
66496 }
66497 }
66498 }
66499
66500 function transitionTraces() {
66501 for(var j = 0; j < basePlotModules.length; j++) {
66502 basePlotModules[j].plot(gd, transitionedTraces, traceTransitionOpts, makeCallback);
66503 }
66504 }
66505
66506 if(axEdits.length && restyleFlags.anim) {
66507 if(transitionOpts.ordering === 'traces first') {
66508 axisTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
66509 transitionedTraces = allTraceIndices;
66510 traceTransitionOpts = transitionOpts;
66511 setTimeout(transitionAxes, transitionOpts.duration);
66512 transitionTraces();
66513 } else {
66514 axisTransitionOpts = transitionOpts;
66515 transitionedTraces = null;
66516 traceTransitionOpts = Lib.extendFlat({}, transitionOpts, {duration: 0});
66517 setTimeout(transitionTraces, axisTransitionOpts.duration);
66518 transitionAxes();
66519 }
66520 } else if(axEdits.length) {
66521 axisTransitionOpts = transitionOpts;
66522 transitionAxes();
66523 } else if(restyleFlags.anim) {
66524 transitionedTraces = allTraceIndices;
66525 traceTransitionOpts = transitionOpts;
66526 transitionTraces();
66527 }
66528 };
66529
66530 return _transition(gd, transitionOpts, opts);
66531};
66532
66533/**
66534 * trace/layout transition wrapper that works
66535 * for transitions initiated by Plotly.animate and Plotly.react.
66536 *
66537 * @param {DOM element} gd
66538 * @param {object} transitionOpts
66539 * @param {object} opts
66540 * - redraw {boolean}
66541 * - prepareFn {function} *should return a Promise*
66542 * - runFn {function} ran inside executeTransitions
66543 */
66544function _transition(gd, transitionOpts, opts) {
66545 var aborted = false;
66546
66547 function executeCallbacks(list) {
66548 var p = Promise.resolve();
66549 if(!list) return p;
66550 while(list.length) {
66551 p = p.then((list.shift()));
66552 }
66553 return p;
66554 }
66555
66556 function flushCallbacks(list) {
66557 if(!list) return;
66558 while(list.length) {
66559 list.shift();
66560 }
66561 }
66562
66563 function executeTransitions() {
66564 gd.emit('plotly_transitioning', []);
66565
66566 return new Promise(function(resolve) {
66567 // This flag is used to disabled things like autorange:
66568 gd._transitioning = true;
66569
66570 // When instantaneous updates are coming through quickly, it's too much to simply disable
66571 // all interaction, so store this flag so we can disambiguate whether mouse interactions
66572 // should be fully disabled or not:
66573 if(transitionOpts.duration > 0) {
66574 gd._transitioningWithDuration = true;
66575 }
66576
66577 // If another transition is triggered, this callback will be executed simply because it's
66578 // in the interruptCallbacks queue. If this transition completes, it will instead flush
66579 // that queue and forget about this callback.
66580 gd._transitionData._interruptCallbacks.push(function() {
66581 aborted = true;
66582 });
66583
66584 if(opts.redraw) {
66585 gd._transitionData._interruptCallbacks.push(function() {
66586 return Registry.call('redraw', gd);
66587 });
66588 }
66589
66590 // Emit this and make sure it happens last:
66591 gd._transitionData._interruptCallbacks.push(function() {
66592 gd.emit('plotly_transitioninterrupted', []);
66593 });
66594
66595 // Construct callbacks that are executed on transition end. This ensures the d3 transitions
66596 // are *complete* before anything else is done.
66597 var numCallbacks = 0;
66598 var numCompleted = 0;
66599 function makeCallback() {
66600 numCallbacks++;
66601 return function() {
66602 numCompleted++;
66603 // When all are complete, perform a redraw:
66604 if(!aborted && numCompleted === numCallbacks) {
66605 completeTransition(resolve);
66606 }
66607 };
66608 }
66609
66610 opts.runFn(makeCallback);
66611
66612 // If nothing else creates a callback, then this will trigger the completion in the next tick:
66613 setTimeout(makeCallback());
66614 });
66615 }
66616
66617 function completeTransition(callback) {
66618 // This a simple workaround for tests which purge the graph before animations
66619 // have completed. That's not a very common case, so this is the simplest
66620 // fix.
66621 if(!gd._transitionData) return;
66622
66623 flushCallbacks(gd._transitionData._interruptCallbacks);
66624
66625 return Promise.resolve().then(function() {
66626 if(opts.redraw) {
66627 return Registry.call('redraw', gd);
66628 }
66629 }).then(function() {
66630 // Set transitioning false again once the redraw has occurred. This is used, for example,
66631 // to prevent the trailing redraw from autoranging:
66632 gd._transitioning = false;
66633 gd._transitioningWithDuration = false;
66634
66635 gd.emit('plotly_transitioned', []);
66636 }).then(callback);
66637 }
66638
66639 function interruptPreviousTransitions() {
66640 // Fail-safe against purged plot:
66641 if(!gd._transitionData) return;
66642
66643 // If a transition is interrupted, set this to false. At the moment, the only thing that would
66644 // interrupt a transition is another transition, so that it will momentarily be set to true
66645 // again, but this determines whether autorange or dragbox work, so it's for the sake of
66646 // cleanliness:
66647 gd._transitioning = false;
66648
66649 return executeCallbacks(gd._transitionData._interruptCallbacks);
66650 }
66651
66652 var seq = [
66653 plots.previousPromises,
66654 interruptPreviousTransitions,
66655 opts.prepareFn,
66656 plots.rehover,
66657 executeTransitions
66658 ];
66659
66660 var transitionStarting = Lib.syncOrAsync(seq, gd);
66661
66662 if(!transitionStarting || !transitionStarting.then) {
66663 transitionStarting = Promise.resolve();
66664 }
66665
66666 return transitionStarting.then(function() { return gd; });
66667}
66668
66669plots.doCalcdata = function(gd, traces) {
66670 var axList = axisIDs.list(gd);
66671 var fullData = gd._fullData;
66672 var fullLayout = gd._fullLayout;
66673
66674 var trace, _module, i, j;
66675
66676 // XXX: Is this correct? Needs a closer look so that *some* traces can be recomputed without
66677 // *all* needing doCalcdata:
66678 var calcdata = new Array(fullData.length);
66679 var oldCalcdata = (gd.calcdata || []).slice();
66680 gd.calcdata = calcdata;
66681
66682 // extra helper variables
66683
66684 // how many box/violins plots do we have (in case they're grouped)
66685 fullLayout._numBoxes = 0;
66686 fullLayout._numViolins = 0;
66687
66688 // initialize violin per-scale-group stats container
66689 fullLayout._violinScaleGroupStats = {};
66690
66691 // for calculating avg luminosity of heatmaps
66692 gd._hmpixcount = 0;
66693 gd._hmlumcount = 0;
66694
66695 // for sharing colors across pies / sunbursts / treemap / funnelarea (and for legend)
66696 fullLayout._piecolormap = {};
66697 fullLayout._sunburstcolormap = {};
66698 fullLayout._treemapcolormap = {};
66699 fullLayout._funnelareacolormap = {};
66700
66701 // If traces were specified and this trace was not included,
66702 // then transfer it over from the old calcdata:
66703 for(i = 0; i < fullData.length; i++) {
66704 if(Array.isArray(traces) && traces.indexOf(i) === -1) {
66705 calcdata[i] = oldCalcdata[i];
66706 continue;
66707 }
66708 }
66709
66710 for(i = 0; i < fullData.length; i++) {
66711 trace = fullData[i];
66712
66713 trace._arrayAttrs = PlotSchema.findArrayAttributes(trace);
66714
66715 // keep track of trace extremes (for autorange) in here
66716 trace._extremes = {};
66717 }
66718
66719 // add polar axes to axis list
66720 var polarIds = fullLayout._subplots.polar || [];
66721 for(i = 0; i < polarIds.length; i++) {
66722 axList.push(
66723 fullLayout[polarIds[i]].radialaxis,
66724 fullLayout[polarIds[i]].angularaxis
66725 );
66726 }
66727
66728 // clear relinked cmin/cmax values in shared axes to start aggregation from scratch
66729 for(var k in fullLayout._colorAxes) {
66730 var cOpts = fullLayout[k];
66731 if(cOpts.cauto !== false) {
66732 delete cOpts.cmin;
66733 delete cOpts.cmax;
66734 }
66735 }
66736
66737 var hasCalcTransform = false;
66738
66739 function transformCalci(i) {
66740 trace = fullData[i];
66741 _module = trace._module;
66742
66743 if(trace.visible === true && trace.transforms) {
66744 // we need one round of trace module calc before
66745 // the calc transform to 'fill in' the categories list
66746 // used for example in the data-to-coordinate method
66747 if(_module && _module.calc) {
66748 var cdi = _module.calc(gd, trace);
66749
66750 // must clear scene 'batches', so that 2nd
66751 // _module.calc call starts from scratch
66752 if(cdi[0] && cdi[0].t && cdi[0].t._scene) {
66753 delete cdi[0].t._scene.dirty;
66754 }
66755 }
66756
66757 for(j = 0; j < trace.transforms.length; j++) {
66758 var transform = trace.transforms[j];
66759
66760 _module = transformsRegistry[transform.type];
66761 if(_module && _module.calcTransform) {
66762 trace._hasCalcTransform = true;
66763 hasCalcTransform = true;
66764 _module.calcTransform(gd, trace, transform);
66765 }
66766 }
66767 }
66768 }
66769
66770 function calci(i, isContainer) {
66771 trace = fullData[i];
66772 _module = trace._module;
66773
66774 if(!!_module.isContainer !== isContainer) return;
66775
66776 var cd = [];
66777
66778 if(trace.visible === true && trace._length !== 0) {
66779 // clear existing ref in case it got relinked
66780 delete trace._indexToPoints;
66781 // keep ref of index-to-points map object of the *last* enabled transform,
66782 // this index-to-points map object is required to determine the calcdata indices
66783 // that correspond to input indices (e.g. from 'selectedpoints')
66784 var transforms = trace.transforms || [];
66785 for(j = transforms.length - 1; j >= 0; j--) {
66786 if(transforms[j].enabled) {
66787 trace._indexToPoints = transforms[j]._indexToPoints;
66788 break;
66789 }
66790 }
66791
66792 if(_module && _module.calc) {
66793 cd = _module.calc(gd, trace);
66794 }
66795 }
66796
66797 // Make sure there is a first point.
66798 //
66799 // This ensures there is a calcdata item for every trace,
66800 // even if cartesian logic doesn't handle it (for things like legends).
66801 if(!Array.isArray(cd) || !cd[0]) {
66802 cd = [{x: BADNUM, y: BADNUM}];
66803 }
66804
66805 // add the trace-wide properties to the first point,
66806 // per point properties to every point
66807 // t is the holder for trace-wide properties
66808 if(!cd[0].t) cd[0].t = {};
66809 cd[0].trace = trace;
66810
66811 calcdata[i] = cd;
66812 }
66813
66814 setupAxisCategories(axList, fullData, fullLayout);
66815
66816 // 'transform' loop - must calc container traces first
66817 // so that if their dependent traces can get transform properly
66818 for(i = 0; i < fullData.length; i++) calci(i, true);
66819 for(i = 0; i < fullData.length; i++) transformCalci(i);
66820
66821 // clear stuff that should recomputed in 'regular' loop
66822 if(hasCalcTransform) setupAxisCategories(axList, fullData, fullLayout);
66823
66824 // 'regular' loop - make sure container traces (eg carpet) calc before
66825 // contained traces (eg contourcarpet)
66826 for(i = 0; i < fullData.length; i++) calci(i, true);
66827 for(i = 0; i < fullData.length; i++) calci(i, false);
66828
66829 doCrossTraceCalc(gd);
66830
66831 // Sort axis categories per value if specified
66832 var sorted = sortAxisCategoriesByValue(axList, gd);
66833 if(sorted.length) {
66834 // how many box/violins plots do we have (in case they're grouped)
66835 fullLayout._numBoxes = 0;
66836 fullLayout._numViolins = 0;
66837 // If a sort operation was performed, run calc() again
66838 for(i = 0; i < sorted.length; i++) calci(sorted[i], true);
66839 for(i = 0; i < sorted.length; i++) calci(sorted[i], false);
66840 doCrossTraceCalc(gd);
66841 }
66842
66843 Registry.getComponentMethod('fx', 'calc')(gd);
66844 Registry.getComponentMethod('errorbars', 'calc')(gd);
66845};
66846
66847var sortAxisCategoriesByValueRegex = /(total|sum|min|max|mean|median) (ascending|descending)/;
66848
66849function sortAxisCategoriesByValue(axList, gd) {
66850 var affectedTraces = [];
66851 var i, j, k, l, o;
66852
66853 function zMapCategory(type, ax, value) {
66854 var axLetter = ax._id.charAt(0);
66855 if(type === 'histogram2dcontour') {
66856 var counterAxLetter = ax._counterAxes[0];
66857 var counterAx = axisIDs.getFromId(gd, counterAxLetter);
66858
66859 var xCategorical = axLetter === 'x' || (counterAxLetter === 'x' && counterAx.type === 'category');
66860 var yCategorical = axLetter === 'y' || (counterAxLetter === 'y' && counterAx.type === 'category');
66861
66862 return function(o, l) {
66863 if(o === 0 || l === 0) return -1; // Skip first row and column
66864 if(xCategorical && o === value[l].length - 1) return -1;
66865 if(yCategorical && l === value.length - 1) return -1;
66866
66867 return (axLetter === 'y' ? l : o) - 1;
66868 };
66869 } else {
66870 return function(o, l) {
66871 return axLetter === 'y' ? l : o;
66872 };
66873 }
66874 }
66875
66876 var aggFn = {
66877 'min': function(values) {return Lib.aggNums(Math.min, null, values);},
66878 'max': function(values) {return Lib.aggNums(Math.max, null, values);},
66879 'sum': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
66880 'total': function(values) {return Lib.aggNums(function(a, b) { return a + b;}, null, values);},
66881 'mean': function(values) {return Lib.mean(values);},
66882 'median': function(values) {return Lib.median(values);}
66883 };
66884
66885 for(i = 0; i < axList.length; i++) {
66886 var ax = axList[i];
66887 if(ax.type !== 'category') continue;
66888
66889 // Order by value
66890 var match = ax.categoryorder.match(sortAxisCategoriesByValueRegex);
66891 if(match) {
66892 var aggregator = match[1];
66893 var order = match[2];
66894
66895 // Store values associated with each category
66896 var categoriesValue = [];
66897 for(j = 0; j < ax._categories.length; j++) {
66898 categoriesValue.push([ax._categories[j], []]);
66899 }
66900
66901 // Collect values across traces
66902 for(j = 0; j < ax._traceIndices.length; j++) {
66903 var traceIndex = ax._traceIndices[j];
66904 var fullTrace = gd._fullData[traceIndex];
66905 var axLetter = ax._id.charAt(0);
66906
66907 // Skip over invisible traces
66908 if(fullTrace.visible !== true) continue;
66909
66910 var type = fullTrace.type;
66911 if(Registry.traceIs(fullTrace, 'histogram')) {
66912 delete fullTrace._xautoBinFinished;
66913 delete fullTrace._yautoBinFinished;
66914 }
66915
66916 var cd = gd.calcdata[traceIndex];
66917 for(k = 0; k < cd.length; k++) {
66918 var cdi = cd[k];
66919 var cat, catIndex, value;
66920
66921 if(type === 'splom') {
66922 // If `splom`, collect values across dimensions
66923 // Find which dimension the current axis is representing
66924 var currentDimensionIndex = fullTrace._axesDim[ax._id];
66925
66926 // Apply logic to associated x axis if it's defined
66927 if(axLetter === 'y') {
66928 var associatedXAxisID = fullTrace._diag[currentDimensionIndex][0];
66929 if(associatedXAxisID) ax = gd._fullLayout[axisIDs.id2name(associatedXAxisID)];
66930 }
66931
66932 var categories = cdi.trace.dimensions[currentDimensionIndex].values;
66933 for(l = 0; l < categories.length; l++) {
66934 cat = categories[l];
66935 catIndex = ax._categoriesMap[cat];
66936
66937 // Collect associated values at index `l` over all other dimensions
66938 for(o = 0; o < cdi.trace.dimensions.length; o++) {
66939 if(o === currentDimensionIndex) continue;
66940 var dimension = cdi.trace.dimensions[o];
66941 categoriesValue[catIndex][1].push(dimension.values[l]);
66942 }
66943 }
66944 } else if(type === 'scattergl') {
66945 // If `scattergl`, collect all values stashed under cdi.t
66946 for(l = 0; l < cdi.t.x.length; l++) {
66947 if(axLetter === 'x') {
66948 cat = cdi.t.x[l];
66949 catIndex = cat;
66950 value = cdi.t.y[l];
66951 }
66952
66953 if(axLetter === 'y') {
66954 cat = cdi.t.y[l];
66955 catIndex = cat;
66956 value = cdi.t.x[l];
66957 }
66958 categoriesValue[catIndex][1].push(value);
66959 }
66960 // must clear scene 'batches', so that 2nd
66961 // _module.calc call starts from scratch
66962 if(cdi.t && cdi.t._scene) {
66963 delete cdi.t._scene.dirty;
66964 }
66965 } else if(cdi.hasOwnProperty('z')) {
66966 // If 2dMap, collect values in `z`
66967 value = cdi.z;
66968 var mapping = zMapCategory(fullTrace.type, ax, value);
66969
66970 for(l = 0; l < value.length; l++) {
66971 for(o = 0; o < value[l].length; o++) {
66972 catIndex = mapping(o, l);
66973 if(catIndex + 1) categoriesValue[catIndex][1].push(value[l][o]);
66974 }
66975 }
66976 } else {
66977 // For all other 2d cartesian traces
66978 if(axLetter === 'x') {
66979 cat = cdi.p + 1 ? cdi.p : cdi.x;
66980 value = cdi.s || cdi.v || cdi.y;
66981 } else if(axLetter === 'y') {
66982 cat = cdi.p + 1 ? cdi.p : cdi.y;
66983 value = cdi.s || cdi.v || cdi.x;
66984 }
66985 if(!Array.isArray(value)) value = [value];
66986 for(l = 0; l < value.length; l++) {
66987 categoriesValue[cat][1].push(value[l]);
66988 }
66989 }
66990 }
66991 }
66992
66993 ax._categoriesValue = categoriesValue;
66994
66995 var categoriesAggregatedValue = [];
66996 for(j = 0; j < categoriesValue.length; j++) {
66997 categoriesAggregatedValue.push([
66998 categoriesValue[j][0],
66999 aggFn[aggregator](categoriesValue[j][1])
67000 ]);
67001 }
67002
67003 // Sort by aggregated value
67004 categoriesAggregatedValue.sort(function(a, b) {
67005 return a[1] - b[1];
67006 });
67007
67008 ax._categoriesAggregatedValue = categoriesAggregatedValue;
67009
67010 // Set new category order
67011 ax._initialCategories = categoriesAggregatedValue.map(function(c) {
67012 return c[0];
67013 });
67014
67015 // Reverse if descending
67016 if(order === 'descending') {
67017 ax._initialCategories.reverse();
67018 }
67019
67020 // Sort all matching axes
67021 affectedTraces = affectedTraces.concat(ax.sortByInitialCategories());
67022 }
67023 }
67024 return affectedTraces;
67025}
67026
67027function setupAxisCategories(axList, fullData, fullLayout) {
67028 var axLookup = {};
67029 var i, ax, axId;
67030
67031 for(i = 0; i < axList.length; i++) {
67032 ax = axList[i];
67033 axId = ax._id;
67034
67035 ax.clearCalc();
67036 if(ax.type === 'multicategory') {
67037 ax.setupMultiCategory(fullData);
67038 }
67039
67040 axLookup[ax._id] = 1;
67041 }
67042
67043 // look into match groups for 'missing' axes
67044 var matchGroups = fullLayout._axisMatchGroups || [];
67045 for(i = 0; i < matchGroups.length; i++) {
67046 for(axId in matchGroups[i]) {
67047 if(!axLookup[axId]) {
67048 ax = fullLayout[axisIDs.id2name(axId)];
67049 ax.clearCalc();
67050 }
67051 }
67052 }
67053}
67054
67055function doCrossTraceCalc(gd) {
67056 var fullLayout = gd._fullLayout;
67057 var modules = fullLayout._visibleModules;
67058 var hash = {};
67059 var i, j, k;
67060
67061 // position and range calculations for traces that
67062 // depend on each other ie bars (stacked or grouped)
67063 // and boxes (grouped) push each other out of the way
67064
67065 for(j = 0; j < modules.length; j++) {
67066 var _module = modules[j];
67067 var fn = _module.crossTraceCalc;
67068 if(fn) {
67069 var spType = _module.basePlotModule.name;
67070 if(hash[spType]) {
67071 Lib.pushUnique(hash[spType], fn);
67072 } else {
67073 hash[spType] = [fn];
67074 }
67075 }
67076 }
67077
67078 for(k in hash) {
67079 var methods = hash[k];
67080 var subplots = fullLayout._subplots[k];
67081
67082 if(Array.isArray(subplots)) {
67083 for(i = 0; i < subplots.length; i++) {
67084 var sp = subplots[i];
67085 var spInfo = k === 'cartesian' ?
67086 fullLayout._plots[sp] :
67087 fullLayout[sp];
67088
67089 for(j = 0; j < methods.length; j++) {
67090 methods[j](gd, spInfo, sp);
67091 }
67092 }
67093 } else {
67094 for(j = 0; j < methods.length; j++) {
67095 methods[j](gd);
67096 }
67097 }
67098 }
67099}
67100
67101plots.rehover = function(gd) {
67102 if(gd._fullLayout._rehover) {
67103 gd._fullLayout._rehover();
67104 }
67105};
67106
67107plots.redrag = function(gd) {
67108 if(gd._fullLayout._redrag) {
67109 gd._fullLayout._redrag();
67110 }
67111};
67112
67113plots.generalUpdatePerTraceModule = function(gd, subplot, subplotCalcData, subplotLayout) {
67114 var traceHashOld = subplot.traceHash;
67115 var traceHash = {};
67116 var i;
67117
67118 // build up moduleName -> calcData hash
67119 for(i = 0; i < subplotCalcData.length; i++) {
67120 var calcTraces = subplotCalcData[i];
67121 var trace = calcTraces[0].trace;
67122
67123 // skip over visible === false traces
67124 // as they don't have `_module` ref
67125 if(trace.visible) {
67126 traceHash[trace.type] = traceHash[trace.type] || [];
67127 traceHash[trace.type].push(calcTraces);
67128 }
67129 }
67130
67131 // when a trace gets deleted, make sure that its module's
67132 // plot method is called so that it is properly
67133 // removed from the DOM.
67134 for(var moduleNameOld in traceHashOld) {
67135 if(!traceHash[moduleNameOld]) {
67136 var fakeCalcTrace = traceHashOld[moduleNameOld][0];
67137 var fakeTrace = fakeCalcTrace[0].trace;
67138
67139 fakeTrace.visible = false;
67140 traceHash[moduleNameOld] = [fakeCalcTrace];
67141 }
67142 }
67143
67144 // call module plot method
67145 for(var moduleName in traceHash) {
67146 var moduleCalcData = traceHash[moduleName];
67147 var _module = moduleCalcData[0][0].trace._module;
67148
67149 _module.plot(gd, subplot, Lib.filterVisible(moduleCalcData), subplotLayout);
67150 }
67151
67152 // update moduleName -> calcData hash
67153 subplot.traceHash = traceHash;
67154};
67155
67156plots.plotBasePlot = function(desiredType, gd, traces, transitionOpts, makeOnCompleteCallback) {
67157 var _module = Registry.getModule(desiredType);
67158 var cdmodule = getModuleCalcData(gd.calcdata, _module)[0];
67159 _module.plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback);
67160};
67161
67162plots.cleanBasePlot = function(desiredType, newFullData, newFullLayout, oldFullData, oldFullLayout) {
67163 var had = (oldFullLayout._has && oldFullLayout._has(desiredType));
67164 var has = (newFullLayout._has && newFullLayout._has(desiredType));
67165
67166 if(had && !has) {
67167 oldFullLayout['_' + desiredType + 'layer'].selectAll('g.trace').remove();
67168 }
67169};
67170
67171},{"../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){
67172/**
67173* Copyright 2012-2020, Plotly, Inc.
67174* All rights reserved.
67175*
67176* This source code is licensed under the MIT license found in the
67177* LICENSE file in the root directory of this source tree.
67178*/
67179
67180'use strict';
67181
67182var scatterAttrs = _dereq_('../../../traces/scatter/attributes');
67183var scatterMarkerAttrs = scatterAttrs.marker;
67184var extendFlat = _dereq_('../../../lib/extend').extendFlat;
67185
67186var deprecationWarning = [
67187 'Area traces are deprecated!',
67188 'Please switch to the *barpolar* trace type.'
67189].join(' ');
67190
67191module.exports = {
67192 r: extendFlat({}, scatterAttrs.r, {
67193
67194 }),
67195 t: extendFlat({}, scatterAttrs.t, {
67196
67197 }),
67198 marker: {
67199 color: extendFlat({}, scatterMarkerAttrs.color, {
67200
67201 }),
67202 size: extendFlat({}, scatterMarkerAttrs.size, {
67203
67204 }),
67205 symbol: extendFlat({}, scatterMarkerAttrs.symbol, {
67206
67207 }),
67208 opacity: extendFlat({}, scatterMarkerAttrs.opacity, {
67209
67210 }),
67211 editType: 'calc'
67212 }
67213};
67214
67215},{"../../../lib/extend":173,"../../../traces/scatter/attributes":389}],258:[function(_dereq_,module,exports){
67216/**
67217* Copyright 2012-2020, Plotly, Inc.
67218* All rights reserved.
67219*
67220* This source code is licensed under the MIT license found in the
67221* LICENSE file in the root directory of this source tree.
67222*/
67223
67224
67225'use strict';
67226
67227var axesAttrs = _dereq_('../../cartesian/layout_attributes');
67228var extendFlat = _dereq_('../../../lib/extend').extendFlat;
67229var overrideAll = _dereq_('../../../plot_api/edit_types').overrideAll;
67230
67231var deprecationWarning = [
67232 'Legacy polar charts are deprecated!',
67233 'Please switch to *polar* subplots.'
67234].join(' ');
67235
67236var domainAttr = extendFlat({}, axesAttrs.domain, {
67237
67238});
67239
67240function mergeAttrs(axisName, nonCommonAttrs) {
67241 var commonAttrs = {
67242 showline: {
67243 valType: 'boolean',
67244
67245
67246 },
67247 showticklabels: {
67248 valType: 'boolean',
67249
67250
67251 },
67252 tickorientation: {
67253 valType: 'enumerated',
67254 values: ['horizontal', 'vertical'],
67255
67256
67257 },
67258 ticklen: {
67259 valType: 'number',
67260 min: 0,
67261
67262
67263 },
67264 tickcolor: {
67265 valType: 'color',
67266
67267
67268 },
67269 ticksuffix: {
67270 valType: 'string',
67271
67272
67273 },
67274 endpadding: {
67275 valType: 'number',
67276
67277 description: deprecationWarning,
67278 },
67279 visible: {
67280 valType: 'boolean',
67281
67282
67283 }
67284 };
67285
67286 return extendFlat({}, nonCommonAttrs, commonAttrs);
67287}
67288
67289module.exports = overrideAll({
67290 radialaxis: mergeAttrs('radial', {
67291 range: {
67292 valType: 'info_array',
67293
67294 items: [
67295 { valType: 'number' },
67296 { valType: 'number' }
67297 ],
67298
67299 },
67300 domain: domainAttr,
67301 orientation: {
67302 valType: 'number',
67303
67304
67305 }
67306 }),
67307
67308 angularaxis: mergeAttrs('angular', {
67309 range: {
67310 valType: 'info_array',
67311
67312 items: [
67313 { valType: 'number', dflt: 0 },
67314 { valType: 'number', dflt: 360 }
67315 ],
67316
67317 },
67318 domain: domainAttr
67319 }),
67320
67321 // attributes that appear at layout root
67322 layout: {
67323 direction: {
67324 valType: 'enumerated',
67325 values: ['clockwise', 'counterclockwise'],
67326
67327
67328 },
67329 orientation: {
67330 valType: 'angle',
67331
67332
67333 }
67334 }
67335}, 'plot', 'nested');
67336
67337},{"../../../lib/extend":173,"../../../plot_api/edit_types":205,"../../cartesian/layout_attributes":236}],259:[function(_dereq_,module,exports){
67338/**
67339* Copyright 2012-2020, Plotly, Inc.
67340* All rights reserved.
67341*
67342* This source code is licensed under the MIT license found in the
67343* LICENSE file in the root directory of this source tree.
67344*/
67345
67346'use strict';
67347
67348var Polar = module.exports = _dereq_('./micropolar');
67349
67350Polar.manager = _dereq_('./micropolar_manager');
67351
67352},{"./micropolar":260,"./micropolar_manager":261}],260:[function(_dereq_,module,exports){
67353/**
67354* Copyright 2012-2020, Plotly, Inc.
67355* All rights reserved.
67356*
67357* This source code is licensed under the MIT license found in the
67358* LICENSE file in the root directory of this source tree.
67359*/
67360
67361var d3 = _dereq_('d3');
67362var Lib = _dereq_('../../../lib');
67363var extendDeepAll = Lib.extendDeepAll;
67364var MID_SHIFT = _dereq_('../../../constants/alignment').MID_SHIFT;
67365
67366var µ = module.exports = { version: '0.2.2' };
67367
67368µ.Axis = function module() {
67369 var config = {
67370 data: [],
67371 layout: {}
67372 }, inputConfig = {}, liveConfig = {};
67373 var svg, container, dispatch = d3.dispatch('hover'), radialScale, angularScale;
67374 var exports = {};
67375 function render(_container) {
67376 container = _container || container;
67377 var data = config.data;
67378 var axisConfig = config.layout;
67379 if (typeof container == 'string' || container.nodeName) container = d3.select(container);
67380 container.datum(data).each(function(_data, _index) {
67381 var dataOriginal = _data.slice();
67382 liveConfig = {
67383 data: µ.util.cloneJson(dataOriginal),
67384 layout: µ.util.cloneJson(axisConfig)
67385 };
67386 var colorIndex = 0;
67387 dataOriginal.forEach(function(d, i) {
67388 if (!d.color) {
67389 d.color = axisConfig.defaultColorRange[colorIndex];
67390 colorIndex = (colorIndex + 1) % axisConfig.defaultColorRange.length;
67391 }
67392 if (!d.strokeColor) {
67393 d.strokeColor = d.geometry === 'LinePlot' ? d.color : d3.rgb(d.color).darker().toString();
67394 }
67395 liveConfig.data[i].color = d.color;
67396 liveConfig.data[i].strokeColor = d.strokeColor;
67397 liveConfig.data[i].strokeDash = d.strokeDash;
67398 liveConfig.data[i].strokeSize = d.strokeSize;
67399 });
67400 var data = dataOriginal.filter(function(d, i) {
67401 var visible = d.visible;
67402 return typeof visible === 'undefined' || visible === true;
67403 });
67404 var isStacked = false;
67405 var dataWithGroupId = data.map(function(d, i) {
67406 isStacked = isStacked || typeof d.groupId !== 'undefined';
67407 return d;
67408 });
67409 if (isStacked) {
67410 var grouped = d3.nest().key(function(d, i) {
67411 return typeof d.groupId != 'undefined' ? d.groupId : 'unstacked';
67412 }).entries(dataWithGroupId);
67413 var dataYStack = [];
67414 var stacked = grouped.map(function(d, i) {
67415 if (d.key === 'unstacked') return d.values; else {
67416 var prevArray = d.values[0].r.map(function(d, i) {
67417 return 0;
67418 });
67419 d.values.forEach(function(d, i, a) {
67420 d.yStack = [ prevArray ];
67421 dataYStack.push(prevArray);
67422 prevArray = µ.util.sumArrays(d.r, prevArray);
67423 });
67424 return d.values;
67425 }
67426 });
67427 data = d3.merge(stacked);
67428 }
67429 data.forEach(function(d, i) {
67430 d.t = Array.isArray(d.t[0]) ? d.t : [ d.t ];
67431 d.r = Array.isArray(d.r[0]) ? d.r : [ d.r ];
67432 });
67433 var radius = Math.min(axisConfig.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2;
67434 radius = Math.max(10, radius);
67435 var chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ];
67436 var extent;
67437 if (isStacked) {
67438 var highestStackedValue = d3.max(µ.util.sumArrays(µ.util.arrayLast(data).r[0], µ.util.arrayLast(dataYStack)));
67439 extent = [ 0, highestStackedValue ];
67440 } else extent = d3.extent(µ.util.flattenArray(data.map(function(d, i) {
67441 return d.r;
67442 })));
67443 if (axisConfig.radialAxis.domain != µ.DATAEXTENT) extent[0] = 0;
67444 radialScale = d3.scale.linear().domain(axisConfig.radialAxis.domain != µ.DATAEXTENT && axisConfig.radialAxis.domain ? axisConfig.radialAxis.domain : extent).range([ 0, radius ]);
67445 liveConfig.layout.radialAxis.domain = radialScale.domain();
67446 var angularDataMerged = µ.util.flattenArray(data.map(function(d, i) {
67447 return d.t;
67448 }));
67449 var isOrdinal = typeof angularDataMerged[0] === 'string';
67450 var ticks;
67451 if (isOrdinal) {
67452 angularDataMerged = µ.util.deduplicate(angularDataMerged);
67453 ticks = angularDataMerged.slice();
67454 angularDataMerged = d3.range(angularDataMerged.length);
67455 data = data.map(function(d, i) {
67456 var result = d;
67457 d.t = [ angularDataMerged ];
67458 if (isStacked) result.yStack = d.yStack;
67459 return result;
67460 });
67461 }
67462 var hasOnlyLineOrDotPlot = data.filter(function(d, i) {
67463 return d.geometry === 'LinePlot' || d.geometry === 'DotPlot';
67464 }).length === data.length;
67465 var needsEndSpacing = axisConfig.needsEndSpacing === null ? isOrdinal || !hasOnlyLineOrDotPlot : axisConfig.needsEndSpacing;
67466 var useProvidedDomain = axisConfig.angularAxis.domain && axisConfig.angularAxis.domain != µ.DATAEXTENT && !isOrdinal && axisConfig.angularAxis.domain[0] >= 0;
67467 var angularDomain = useProvidedDomain ? axisConfig.angularAxis.domain : d3.extent(angularDataMerged);
67468 var angularDomainStep = Math.abs(angularDataMerged[1] - angularDataMerged[0]);
67469 if (hasOnlyLineOrDotPlot && !isOrdinal) angularDomainStep = 0;
67470 var angularDomainWithPadding = angularDomain.slice();
67471 if (needsEndSpacing && isOrdinal) angularDomainWithPadding[1] += angularDomainStep;
67472 var tickCount = axisConfig.angularAxis.ticksCount || 4;
67473 if (tickCount > 8) tickCount = tickCount / (tickCount / 8) + tickCount % 8;
67474 if (axisConfig.angularAxis.ticksStep) {
67475 tickCount = (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / tickCount;
67476 }
67477 var angularTicksStep = axisConfig.angularAxis.ticksStep || (angularDomainWithPadding[1] - angularDomainWithPadding[0]) / (tickCount * (axisConfig.minorTicks + 1));
67478 if (ticks) angularTicksStep = Math.max(Math.round(angularTicksStep), 1);
67479 if (!angularDomainWithPadding[2]) angularDomainWithPadding[2] = angularTicksStep;
67480 var angularAxisRange = d3.range.apply(this, angularDomainWithPadding);
67481 angularAxisRange = angularAxisRange.map(function(d, i) {
67482 return parseFloat(d.toPrecision(12));
67483 });
67484 angularScale = d3.scale.linear().domain(angularDomainWithPadding.slice(0, 2)).range(axisConfig.direction === 'clockwise' ? [ 0, 360 ] : [ 360, 0 ]);
67485 liveConfig.layout.angularAxis.domain = angularScale.domain();
67486 liveConfig.layout.angularAxis.endPadding = needsEndSpacing ? angularDomainStep : 0;
67487 svg = d3.select(this).select('svg.chart-root');
67488 if (typeof svg === 'undefined' || svg.empty()) {
67489 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>";
67490 var doc = new DOMParser().parseFromString(skeleton, 'application/xml');
67491 var newSvg = this.appendChild(this.ownerDocument.importNode(doc.documentElement, true));
67492 svg = d3.select(newSvg);
67493 }
67494 svg.select('.guides-group').style({
67495 'pointer-events': 'none'
67496 });
67497 svg.select('.angular.axis-group').style({
67498 'pointer-events': 'none'
67499 });
67500 svg.select('.radial.axis-group').style({
67501 'pointer-events': 'none'
67502 });
67503 var chartGroup = svg.select('.chart-group');
67504 var lineStyle = {
67505 fill: 'none',
67506 stroke: axisConfig.tickColor
67507 };
67508 var fontStyle = {
67509 'font-size': axisConfig.font.size,
67510 'font-family': axisConfig.font.family,
67511 fill: axisConfig.font.color,
67512 'text-shadow': [ '-1px 0px', '1px -1px', '-1px 1px', '1px 1px' ].map(function(d, i) {
67513 return ' ' + d + ' 0 ' + axisConfig.font.outlineColor;
67514 }).join(',')
67515 };
67516 var legendContainer;
67517 if (axisConfig.showLegend) {
67518 legendContainer = svg.select('.legend-group').attr({
67519 transform: 'translate(' + [ radius, axisConfig.margin.top ] + ')'
67520 }).style({
67521 display: 'block'
67522 });
67523 var elements = data.map(function(d, i) {
67524 var datumClone = µ.util.cloneJson(d);
67525 datumClone.symbol = d.geometry === 'DotPlot' ? d.dotType || 'circle' : d.geometry != 'LinePlot' ? 'square' : 'line';
67526 datumClone.visibleInLegend = typeof d.visibleInLegend === 'undefined' || d.visibleInLegend;
67527 datumClone.color = d.geometry === 'LinePlot' ? d.strokeColor : d.color;
67528 return datumClone;
67529 });
67530
67531 µ.Legend().config({
67532 data: data.map(function(d, i) {
67533 return d.name || 'Element' + i;
67534 }),
67535 legendConfig: extendDeepAll({},
67536 µ.Legend.defaultConfig().legendConfig,
67537 {
67538 container: legendContainer,
67539 elements: elements,
67540 reverseOrder: axisConfig.legend.reverseOrder
67541 }
67542 )
67543 })();
67544
67545 var legendBBox = legendContainer.node().getBBox();
67546 radius = Math.min(axisConfig.width - legendBBox.width - axisConfig.margin.left - axisConfig.margin.right, axisConfig.height - axisConfig.margin.top - axisConfig.margin.bottom) / 2;
67547 radius = Math.max(10, radius);
67548 chartCenter = [ axisConfig.margin.left + radius, axisConfig.margin.top + radius ];
67549 radialScale.range([ 0, radius ]);
67550 liveConfig.layout.radialAxis.domain = radialScale.domain();
67551 legendContainer.attr('transform', 'translate(' + [ chartCenter[0] + radius, chartCenter[1] - radius ] + ')');
67552 } else {
67553 legendContainer = svg.select('.legend-group').style({
67554 display: 'none'
67555 });
67556 }
67557 svg.attr({
67558 width: axisConfig.width,
67559 height: axisConfig.height
67560 }).style({
67561 opacity: axisConfig.opacity
67562 });
67563 chartGroup.attr('transform', 'translate(' + chartCenter + ')').style({
67564 cursor: 'crosshair'
67565 });
67566 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 ];
67567 centeringOffset[0] = Math.max(0, centeringOffset[0]);
67568 centeringOffset[1] = Math.max(0, centeringOffset[1]);
67569 svg.select('.outer-group').attr('transform', 'translate(' + centeringOffset + ')');
67570 if (axisConfig.title && axisConfig.title.text) {
67571 var title = svg.select('g.title-group text').style(fontStyle).text(axisConfig.title.text);
67572 var titleBBox = title.node().getBBox();
67573 title.attr({
67574 x: chartCenter[0] - titleBBox.width / 2,
67575 y: chartCenter[1] - radius - 20
67576 });
67577 }
67578 var radialAxis = svg.select('.radial.axis-group');
67579 if (axisConfig.radialAxis.gridLinesVisible) {
67580 var gridCircles = radialAxis.selectAll('circle.grid-circle').data(radialScale.ticks(5));
67581 gridCircles.enter().append('circle').attr({
67582 'class': 'grid-circle'
67583 }).style(lineStyle);
67584 gridCircles.attr('r', radialScale);
67585 gridCircles.exit().remove();
67586 }
67587 radialAxis.select('circle.outside-circle').attr({
67588 r: radius
67589 }).style(lineStyle);
67590 var backgroundCircle = svg.select('circle.background-circle').attr({
67591 r: radius
67592 }).style({
67593 fill: axisConfig.backgroundColor,
67594 stroke: axisConfig.stroke
67595 });
67596 function currentAngle(d, i) {
67597 return angularScale(d) % 360 + axisConfig.orientation;
67598 }
67599 if (axisConfig.radialAxis.visible) {
67600 var axis = d3.svg.axis().scale(radialScale).ticks(5).tickSize(5);
67601 radialAxis.call(axis).attr({
67602 transform: 'rotate(' + axisConfig.radialAxis.orientation + ')'
67603 });
67604 radialAxis.selectAll('.domain').style(lineStyle);
67605 radialAxis.selectAll('g>text').text(function(d, i) {
67606 return this.textContent + axisConfig.radialAxis.ticksSuffix;
67607 }).style(fontStyle).style({
67608 'text-anchor': 'start'
67609 }).attr({
67610 x: 0,
67611 y: 0,
67612 dx: 0,
67613 dy: 0,
67614 transform: function(d, i) {
67615 if (axisConfig.radialAxis.tickOrientation === 'horizontal') {
67616 return 'rotate(' + -axisConfig.radialAxis.orientation + ') translate(' + [ 0, fontStyle['font-size'] ] + ')';
67617 } else return 'translate(' + [ 0, fontStyle['font-size'] ] + ')';
67618 }
67619 });
67620 radialAxis.selectAll('g>line').style({
67621 stroke: 'black'
67622 });
67623 }
67624 var angularAxis = svg.select('.angular.axis-group').selectAll('g.angular-tick').data(angularAxisRange);
67625 var angularAxisEnter = angularAxis.enter().append('g').classed('angular-tick', true);
67626 angularAxis.attr({
67627 transform: function(d, i) {
67628 return 'rotate(' + currentAngle(d, i) + ')';
67629 }
67630 }).style({
67631 display: axisConfig.angularAxis.visible ? 'block' : 'none'
67632 });
67633 angularAxis.exit().remove();
67634 angularAxisEnter.append('line').classed('grid-line', true).classed('major', function(d, i) {
67635 return i % (axisConfig.minorTicks + 1) == 0;
67636 }).classed('minor', function(d, i) {
67637 return !(i % (axisConfig.minorTicks + 1) == 0);
67638 }).style(lineStyle);
67639 angularAxisEnter.selectAll('.minor').style({
67640 stroke: axisConfig.minorTickColor
67641 });
67642 angularAxis.select('line.grid-line').attr({
67643 x1: axisConfig.tickLength ? radius - axisConfig.tickLength : 0,
67644 x2: radius
67645 }).style({
67646 display: axisConfig.angularAxis.gridLinesVisible ? 'block' : 'none'
67647 });
67648 angularAxisEnter.append('text').classed('axis-text', true).style(fontStyle);
67649 var ticksText = angularAxis.select('text.axis-text').attr({
67650 x: radius + axisConfig.labelOffset,
67651 dy: MID_SHIFT + 'em',
67652 transform: function(d, i) {
67653 var angle = currentAngle(d, i);
67654 var rad = radius + axisConfig.labelOffset;
67655 var orient = axisConfig.angularAxis.tickOrientation;
67656 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)';
67657 }
67658 }).style({
67659 'text-anchor': 'middle',
67660 display: axisConfig.angularAxis.labelsVisible ? 'block' : 'none'
67661 }).text(function(d, i) {
67662 if (i % (axisConfig.minorTicks + 1) != 0) return '';
67663 if (ticks) {
67664 return ticks[d] + axisConfig.angularAxis.ticksSuffix;
67665 } else return d + axisConfig.angularAxis.ticksSuffix;
67666 }).style(fontStyle);
67667 if (axisConfig.angularAxis.rewriteTicks) ticksText.text(function(d, i) {
67668 if (i % (axisConfig.minorTicks + 1) != 0) return '';
67669 return axisConfig.angularAxis.rewriteTicks(this.textContent, i);
67670 });
67671 var rightmostTickEndX = d3.max(chartGroup.selectAll('.angular-tick text')[0].map(function(d, i) {
67672 return d.getCTM().e + d.getBBox().width;
67673 }));
67674 legendContainer.attr({
67675 transform: 'translate(' + [ radius + rightmostTickEndX, axisConfig.margin.top ] + ')'
67676 });
67677 var hasGeometry = svg.select('g.geometry-group').selectAll('g').size() > 0;
67678 var geometryContainer = svg.select('g.geometry-group').selectAll('g.geometry').data(data);
67679 geometryContainer.enter().append('g').attr({
67680 'class': function(d, i) {
67681 return 'geometry geometry' + i;
67682 }
67683 });
67684 geometryContainer.exit().remove();
67685 if (data[0] || hasGeometry) {
67686 var geometryConfigs = [];
67687 data.forEach(function(d, i) {
67688 var geometryConfig = {};
67689 geometryConfig.radialScale = radialScale;
67690 geometryConfig.angularScale = angularScale;
67691 geometryConfig.container = geometryContainer.filter(function(dB, iB) {
67692 return iB == i;
67693 });
67694 geometryConfig.geometry = d.geometry;
67695 geometryConfig.orientation = axisConfig.orientation;
67696 geometryConfig.direction = axisConfig.direction;
67697 geometryConfig.index = i;
67698 geometryConfigs.push({
67699 data: d,
67700 geometryConfig: geometryConfig
67701 });
67702 });
67703 var geometryConfigsGrouped = d3.nest().key(function(d, i) {
67704 return typeof d.data.groupId != 'undefined' || 'unstacked';
67705 }).entries(geometryConfigs);
67706 var geometryConfigsGrouped2 = [];
67707 geometryConfigsGrouped.forEach(function(d, i) {
67708 if (d.key === 'unstacked') geometryConfigsGrouped2 = geometryConfigsGrouped2.concat(d.values.map(function(d, i) {
67709 return [ d ];
67710 })); else geometryConfigsGrouped2.push(d.values);
67711 });
67712 geometryConfigsGrouped2.forEach(function(d, i) {
67713 var geometry;
67714 if (Array.isArray(d)) geometry = d[0].geometryConfig.geometry; else geometry = d.geometryConfig.geometry;
67715 var finalGeometryConfig = d.map(function(dB, iB) {
67716 return extendDeepAll(µ[geometry].defaultConfig(), dB);
67717 });
67718 µ[geometry]().config(finalGeometryConfig)();
67719 });
67720 }
67721 var guides = svg.select('.guides-group');
67722 var tooltipContainer = svg.select('.tooltips-group');
67723 var angularTooltip = µ.tooltipPanel().config({
67724 container: tooltipContainer,
67725 fontSize: 8
67726 })();
67727 var radialTooltip = µ.tooltipPanel().config({
67728 container: tooltipContainer,
67729 fontSize: 8
67730 })();
67731 var geometryTooltip = µ.tooltipPanel().config({
67732 container: tooltipContainer,
67733 hasTick: true
67734 })();
67735 var angularValue, radialValue;
67736 if (!isOrdinal) {
67737 var angularGuideLine = guides.select('line').attr({
67738 x1: 0,
67739 y1: 0,
67740 y2: 0
67741 }).style({
67742 stroke: 'grey',
67743 'pointer-events': 'none'
67744 });
67745 chartGroup.on('mousemove.angular-guide', function(d, i) {
67746 var mouseAngle = µ.util.getMousePos(backgroundCircle).angle;
67747 angularGuideLine.attr({
67748 x2: -radius,
67749 transform: 'rotate(' + mouseAngle + ')'
67750 }).style({
67751 opacity: .5
67752 });
67753 var angleWithOriginOffset = (mouseAngle + 180 + 360 - axisConfig.orientation) % 360;
67754 angularValue = angularScale.invert(angleWithOriginOffset);
67755 var pos = µ.util.convertToCartesian(radius + 12, mouseAngle + 180);
67756 angularTooltip.text(µ.util.round(angularValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]);
67757 }).on('mouseout.angular-guide', function(d, i) {
67758 guides.select('line').style({
67759 opacity: 0
67760 });
67761 });
67762 }
67763 var angularGuideCircle = guides.select('circle').style({
67764 stroke: 'grey',
67765 fill: 'none'
67766 });
67767 chartGroup.on('mousemove.radial-guide', function(d, i) {
67768 var r = µ.util.getMousePos(backgroundCircle).radius;
67769 angularGuideCircle.attr({
67770 r: r
67771 }).style({
67772 opacity: .5
67773 });
67774 radialValue = radialScale.invert(µ.util.getMousePos(backgroundCircle).radius);
67775 var pos = µ.util.convertToCartesian(r, axisConfig.radialAxis.orientation);
67776 radialTooltip.text(µ.util.round(radialValue)).move([ pos[0] + chartCenter[0], pos[1] + chartCenter[1] ]);
67777 }).on('mouseout.radial-guide', function(d, i) {
67778 angularGuideCircle.style({
67779 opacity: 0
67780 });
67781 geometryTooltip.hide();
67782 angularTooltip.hide();
67783 radialTooltip.hide();
67784 });
67785 svg.selectAll('.geometry-group .mark').on('mouseover.tooltip', function(d, i) {
67786 var el = d3.select(this);
67787 var color = this.style.fill;
67788 var newColor = 'black';
67789 var opacity = this.style.opacity || 1;
67790 el.attr({
67791 'data-opacity': opacity
67792 });
67793 if (color && color !== 'none') {
67794 el.attr({
67795 'data-fill': color
67796 });
67797 newColor = d3.hsl(color).darker().toString();
67798 el.style({
67799 fill: newColor,
67800 opacity: 1
67801 });
67802 var textData = {
67803 t: µ.util.round(d[0]),
67804 r: µ.util.round(d[1])
67805 };
67806 if (isOrdinal) textData.t = ticks[d[0]];
67807 var text = 't: ' + textData.t + ', r: ' + textData.r;
67808 var bbox = this.getBoundingClientRect();
67809 var svgBBox = svg.node().getBoundingClientRect();
67810 var pos = [ bbox.left + bbox.width / 2 - centeringOffset[0] - svgBBox.left, bbox.top + bbox.height / 2 - centeringOffset[1] - svgBBox.top ];
67811 geometryTooltip.config({
67812 color: newColor
67813 }).text(text);
67814 geometryTooltip.move(pos);
67815 } else {
67816 color = this.style.stroke || 'black';
67817 el.attr({
67818 'data-stroke': color
67819 });
67820 newColor = d3.hsl(color).darker().toString();
67821 el.style({
67822 stroke: newColor,
67823 opacity: 1
67824 });
67825 }
67826 }).on('mousemove.tooltip', function(d, i) {
67827 if (d3.event.which != 0) return false;
67828 if (d3.select(this).attr('data-fill')) geometryTooltip.show();
67829 }).on('mouseout.tooltip', function(d, i) {
67830 geometryTooltip.hide();
67831 var el = d3.select(this);
67832 var fillColor = el.attr('data-fill');
67833 if (fillColor) el.style({
67834 fill: fillColor,
67835 opacity: el.attr('data-opacity')
67836 }); else el.style({
67837 stroke: el.attr('data-stroke'),
67838 opacity: el.attr('data-opacity')
67839 });
67840 });
67841 });
67842 return exports;
67843 }
67844 exports.render = function(_container) {
67845 render(_container);
67846 return this;
67847 };
67848 exports.config = function(_x) {
67849 if (!arguments.length) return config;
67850 var xClone = µ.util.cloneJson(_x);
67851 xClone.data.forEach(function(d, i) {
67852 if (!config.data[i]) config.data[i] = {};
67853 extendDeepAll(config.data[i], µ.Axis.defaultConfig().data[0]);
67854 extendDeepAll(config.data[i], d);
67855 });
67856 extendDeepAll(config.layout, µ.Axis.defaultConfig().layout);
67857 extendDeepAll(config.layout, xClone.layout);
67858 return this;
67859 };
67860 exports.getLiveConfig = function() {
67861 return liveConfig;
67862 };
67863 exports.getinputConfig = function() {
67864 return inputConfig;
67865 };
67866 exports.radialScale = function(_x) {
67867 return radialScale;
67868 };
67869 exports.angularScale = function(_x) {
67870 return angularScale;
67871 };
67872 exports.svg = function() {
67873 return svg;
67874 };
67875 d3.rebind(exports, dispatch, 'on');
67876 return exports;
67877};
67878
67879µ.Axis.defaultConfig = function(d, i) {
67880 var config = {
67881 data: [ {
67882 t: [ 1, 2, 3, 4 ],
67883 r: [ 10, 11, 12, 13 ],
67884 name: 'Line1',
67885 geometry: 'LinePlot',
67886 color: null,
67887 strokeDash: 'solid',
67888 strokeColor: null,
67889 strokeSize: '1',
67890 visibleInLegend: true,
67891 opacity: 1
67892 } ],
67893 layout: {
67894 defaultColorRange: d3.scale.category10().range(),
67895 title: null,
67896 height: 450,
67897 width: 500,
67898 margin: {
67899 top: 40,
67900 right: 40,
67901 bottom: 40,
67902 left: 40
67903 },
67904 font: {
67905 size: 12,
67906 color: 'gray',
67907 outlineColor: 'white',
67908 family: 'Tahoma, sans-serif'
67909 },
67910 direction: 'clockwise',
67911 orientation: 0,
67912 labelOffset: 10,
67913 radialAxis: {
67914 domain: null,
67915 orientation: -45,
67916 ticksSuffix: '',
67917 visible: true,
67918 gridLinesVisible: true,
67919 tickOrientation: 'horizontal',
67920 rewriteTicks: null
67921 },
67922 angularAxis: {
67923 domain: [ 0, 360 ],
67924 ticksSuffix: '',
67925 visible: true,
67926 gridLinesVisible: true,
67927 labelsVisible: true,
67928 tickOrientation: 'horizontal',
67929 rewriteTicks: null,
67930 ticksCount: null,
67931 ticksStep: null
67932 },
67933 minorTicks: 0,
67934 tickLength: null,
67935 tickColor: 'silver',
67936 minorTickColor: '#eee',
67937 backgroundColor: 'none',
67938 needsEndSpacing: null,
67939 showLegend: true,
67940 legend: {
67941 reverseOrder: false
67942 },
67943 opacity: 1
67944 }
67945 };
67946 return config;
67947};
67948
67949µ.util = {};
67950
67951µ.DATAEXTENT = 'dataExtent';
67952
67953µ.AREA = 'AreaChart';
67954
67955µ.LINE = 'LinePlot';
67956
67957µ.DOT = 'DotPlot';
67958
67959µ.BAR = 'BarChart';
67960
67961µ.util._override = function(_objA, _objB) {
67962 for (var x in _objA) if (x in _objB) _objB[x] = _objA[x];
67963};
67964
67965µ.util._extend = function(_objA, _objB) {
67966 for (var x in _objA) _objB[x] = _objA[x];
67967};
67968
67969µ.util._rndSnd = function() {
67970 return Math.random() * 2 - 1 + (Math.random() * 2 - 1) + (Math.random() * 2 - 1);
67971};
67972
67973µ.util.dataFromEquation2 = function(_equation, _step) {
67974 var step = _step || 6;
67975 var data = d3.range(0, 360 + step, step).map(function(deg, index) {
67976 var theta = deg * Math.PI / 180;
67977 var radius = _equation(theta);
67978 return [ deg, radius ];
67979 });
67980 return data;
67981};
67982
67983µ.util.dataFromEquation = function(_equation, _step, _name) {
67984 var step = _step || 6;
67985 var t = [], r = [];
67986 d3.range(0, 360 + step, step).forEach(function(deg, index) {
67987 var theta = deg * Math.PI / 180;
67988 var radius = _equation(theta);
67989 t.push(deg);
67990 r.push(radius);
67991 });
67992 var result = {
67993 t: t,
67994 r: r
67995 };
67996 if (_name) result.name = _name;
67997 return result;
67998};
67999
68000µ.util.ensureArray = function(_val, _count) {
68001 if (typeof _val === 'undefined') return null;
68002 var arr = [].concat(_val);
68003 return d3.range(_count).map(function(d, i) {
68004 return arr[i] || arr[0];
68005 });
68006};
68007
68008µ.util.fillArrays = function(_obj, _valueNames, _count) {
68009 _valueNames.forEach(function(d, i) {
68010 _obj[d] = µ.util.ensureArray(_obj[d], _count);
68011 });
68012 return _obj;
68013};
68014
68015µ.util.cloneJson = function(json) {
68016 return JSON.parse(JSON.stringify(json));
68017};
68018
68019µ.util.validateKeys = function(obj, keys) {
68020 if (typeof keys === 'string') keys = keys.split('.');
68021 var next = keys.shift();
68022 return obj[next] && (!keys.length || objHasKeys(obj[next], keys));
68023};
68024
68025µ.util.sumArrays = function(a, b) {
68026 return d3.zip(a, b).map(function(d, i) {
68027 return d3.sum(d);
68028 });
68029};
68030
68031µ.util.arrayLast = function(a) {
68032 return a[a.length - 1];
68033};
68034
68035µ.util.arrayEqual = function(a, b) {
68036 var i = Math.max(a.length, b.length, 1);
68037 while (i-- >= 0 && a[i] === b[i]) ;
68038 return i === -2;
68039};
68040
68041µ.util.flattenArray = function(arr) {
68042 var r = [];
68043 while (!µ.util.arrayEqual(r, arr)) {
68044 r = arr;
68045 arr = [].concat.apply([], arr);
68046 }
68047 return arr;
68048};
68049
68050µ.util.deduplicate = function(arr) {
68051 return arr.filter(function(v, i, a) {
68052 return a.indexOf(v) == i;
68053 });
68054};
68055
68056µ.util.convertToCartesian = function(radius, theta) {
68057 var thetaRadians = theta * Math.PI / 180;
68058 var x = radius * Math.cos(thetaRadians);
68059 var y = radius * Math.sin(thetaRadians);
68060 return [ x, y ];
68061};
68062
68063µ.util.round = function(_value, _digits) {
68064 var digits = _digits || 2;
68065 var mult = Math.pow(10, digits);
68066 return Math.round(_value * mult) / mult;
68067};
68068
68069µ.util.getMousePos = function(_referenceElement) {
68070 var mousePos = d3.mouse(_referenceElement.node());
68071 var mouseX = mousePos[0];
68072 var mouseY = mousePos[1];
68073 var mouse = {};
68074 mouse.x = mouseX;
68075 mouse.y = mouseY;
68076 mouse.pos = mousePos;
68077 mouse.angle = (Math.atan2(mouseY, mouseX) + Math.PI) * 180 / Math.PI;
68078 mouse.radius = Math.sqrt(mouseX * mouseX + mouseY * mouseY);
68079 return mouse;
68080};
68081
68082µ.util.duplicatesCount = function(arr) {
68083 var uniques = {}, val;
68084 var dups = {};
68085 for (var i = 0, len = arr.length; i < len; i++) {
68086 val = arr[i];
68087 if (val in uniques) {
68088 uniques[val]++;
68089 dups[val] = uniques[val];
68090 } else {
68091 uniques[val] = 1;
68092 }
68093 }
68094 return dups;
68095};
68096
68097µ.util.duplicates = function(arr) {
68098 return Object.keys(µ.util.duplicatesCount(arr));
68099};
68100
68101µ.util.translator = function(obj, sourceBranch, targetBranch, reverse) {
68102 if (reverse) {
68103 var targetBranchCopy = targetBranch.slice();
68104 targetBranch = sourceBranch;
68105 sourceBranch = targetBranchCopy;
68106 }
68107 var value = sourceBranch.reduce(function(previousValue, currentValue) {
68108 if (typeof previousValue != 'undefined') return previousValue[currentValue];
68109 }, obj);
68110 if (typeof value === 'undefined') return;
68111 sourceBranch.reduce(function(previousValue, currentValue, index) {
68112 if (typeof previousValue == 'undefined') return;
68113 if (index === sourceBranch.length - 1) delete previousValue[currentValue];
68114 return previousValue[currentValue];
68115 }, obj);
68116 targetBranch.reduce(function(previousValue, currentValue, index) {
68117 if (typeof previousValue[currentValue] === 'undefined') previousValue[currentValue] = {};
68118 if (index === targetBranch.length - 1) previousValue[currentValue] = value;
68119 return previousValue[currentValue];
68120 }, obj);
68121};
68122
68123µ.PolyChart = function module() {
68124 var config = [ µ.PolyChart.defaultConfig() ];
68125 var dispatch = d3.dispatch('hover');
68126 var dashArray = {
68127 solid: 'none',
68128 dash: [ 5, 2 ],
68129 dot: [ 2, 5 ]
68130 };
68131 var colorScale;
68132 function exports() {
68133 var geometryConfig = config[0].geometryConfig;
68134 var container = geometryConfig.container;
68135 if (typeof container == 'string') container = d3.select(container);
68136 container.datum(config).each(function(_config, _index) {
68137 var isStack = !!_config[0].data.yStack;
68138 var data = _config.map(function(d, i) {
68139 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]);
68140 });
68141 var angularScale = geometryConfig.angularScale;
68142 var domainMin = geometryConfig.radialScale.domain()[0];
68143 var generator = {};
68144 generator.bar = function(d, i, pI) {
68145 var dataConfig = _config[pI].data;
68146 var h = geometryConfig.radialScale(d[1]) - geometryConfig.radialScale(0);
68147 var stackTop = geometryConfig.radialScale(d[2] || 0);
68148 var w = dataConfig.barWidth;
68149 d3.select(this).attr({
68150 'class': 'mark bar',
68151 d: 'M' + [ [ h + stackTop, -w / 2 ], [ h + stackTop, w / 2 ], [ stackTop, w / 2 ], [ stackTop, -w / 2 ] ].join('L') + 'Z',
68152 transform: function(d, i) {
68153 return 'rotate(' + (geometryConfig.orientation + angularScale(d[0])) + ')';
68154 }
68155 });
68156 };
68157 generator.dot = function(d, i, pI) {
68158 var stackedData = d[2] ? [ d[0], d[1] + d[2] ] : d;
68159 var symbol = d3.svg.symbol().size(_config[pI].data.dotSize).type(_config[pI].data.dotType)(d, i);
68160 d3.select(this).attr({
68161 'class': 'mark dot',
68162 d: symbol,
68163 transform: function(d, i) {
68164 var coord = convertToCartesian(getPolarCoordinates(stackedData));
68165 return 'translate(' + [ coord.x, coord.y ] + ')';
68166 }
68167 });
68168 };
68169 var line = d3.svg.line.radial().interpolate(_config[0].data.lineInterpolation).radius(function(d) {
68170 return geometryConfig.radialScale(d[1]);
68171 }).angle(function(d) {
68172 return geometryConfig.angularScale(d[0]) * Math.PI / 180;
68173 });
68174 generator.line = function(d, i, pI) {
68175 var lineData = d[2] ? data[pI].map(function(d, i) {
68176 return [ d[0], d[1] + d[2] ];
68177 }) : data[pI];
68178 d3.select(this).each(generator['dot']).style({
68179 opacity: function(dB, iB) {
68180 return +_config[pI].data.dotVisible;
68181 },
68182 fill: markStyle.stroke(d, i, pI)
68183 }).attr({
68184 'class': 'mark dot'
68185 });
68186 if (i > 0) return;
68187 var lineSelection = d3.select(this.parentNode).selectAll('path.line').data([ 0 ]);
68188 lineSelection.enter().insert('path');
68189 lineSelection.attr({
68190 'class': 'line',
68191 d: line(lineData),
68192 transform: function(dB, iB) {
68193 return 'rotate(' + (geometryConfig.orientation + 90) + ')';
68194 },
68195 'pointer-events': 'none'
68196 }).style({
68197 fill: function(dB, iB) {
68198 return markStyle.fill(d, i, pI);
68199 },
68200 'fill-opacity': 0,
68201 stroke: function(dB, iB) {
68202 return markStyle.stroke(d, i, pI);
68203 },
68204 'stroke-width': function(dB, iB) {
68205 return markStyle['stroke-width'](d, i, pI);
68206 },
68207 'stroke-dasharray': function(dB, iB) {
68208 return markStyle['stroke-dasharray'](d, i, pI);
68209 },
68210 opacity: function(dB, iB) {
68211 return markStyle.opacity(d, i, pI);
68212 },
68213 display: function(dB, iB) {
68214 return markStyle.display(d, i, pI);
68215 }
68216 });
68217 };
68218 var angularRange = geometryConfig.angularScale.range();
68219 var triangleAngle = Math.abs(angularRange[1] - angularRange[0]) / data[0].length * Math.PI / 180;
68220 var arc = d3.svg.arc().startAngle(function(d) {
68221 return -triangleAngle / 2;
68222 }).endAngle(function(d) {
68223 return triangleAngle / 2;
68224 }).innerRadius(function(d) {
68225 return geometryConfig.radialScale(domainMin + (d[2] || 0));
68226 }).outerRadius(function(d) {
68227 return geometryConfig.radialScale(domainMin + (d[2] || 0)) + geometryConfig.radialScale(d[1]);
68228 });
68229 generator.arc = function(d, i, pI) {
68230 d3.select(this).attr({
68231 'class': 'mark arc',
68232 d: arc,
68233 transform: function(d, i) {
68234 return 'rotate(' + (geometryConfig.orientation + angularScale(d[0]) + 90) + ')';
68235 }
68236 });
68237 };
68238 var markStyle = {
68239 fill: function(d, i, pI) {
68240 return _config[pI].data.color;
68241 },
68242 stroke: function(d, i, pI) {
68243 return _config[pI].data.strokeColor;
68244 },
68245 'stroke-width': function(d, i, pI) {
68246 return _config[pI].data.strokeSize + 'px';
68247 },
68248 'stroke-dasharray': function(d, i, pI) {
68249 return dashArray[_config[pI].data.strokeDash];
68250 },
68251 opacity: function(d, i, pI) {
68252 return _config[pI].data.opacity;
68253 },
68254 display: function(d, i, pI) {
68255 return typeof _config[pI].data.visible === 'undefined' || _config[pI].data.visible ? 'block' : 'none';
68256 }
68257 };
68258 var geometryLayer = d3.select(this).selectAll('g.layer').data(data);
68259 geometryLayer.enter().append('g').attr({
68260 'class': 'layer'
68261 });
68262 var geometry = geometryLayer.selectAll('path.mark').data(function(d, i) {
68263 return d;
68264 });
68265 geometry.enter().append('path').attr({
68266 'class': 'mark'
68267 });
68268 geometry.style(markStyle).each(generator[geometryConfig.geometryType]);
68269 geometry.exit().remove();
68270 geometryLayer.exit().remove();
68271 function getPolarCoordinates(d, i) {
68272 var r = geometryConfig.radialScale(d[1]);
68273 var t = (geometryConfig.angularScale(d[0]) + geometryConfig.orientation) * Math.PI / 180;
68274 return {
68275 r: r,
68276 t: t
68277 };
68278 }
68279 function convertToCartesian(polarCoordinates) {
68280 var x = polarCoordinates.r * Math.cos(polarCoordinates.t);
68281 var y = polarCoordinates.r * Math.sin(polarCoordinates.t);
68282 return {
68283 x: x,
68284 y: y
68285 };
68286 }
68287 });
68288 }
68289 exports.config = function(_x) {
68290 if (!arguments.length) return config;
68291 _x.forEach(function(d, i) {
68292 if (!config[i]) config[i] = {};
68293 extendDeepAll(config[i], µ.PolyChart.defaultConfig());
68294 extendDeepAll(config[i], d);
68295 });
68296 return this;
68297 };
68298 exports.getColorScale = function() {
68299 return colorScale;
68300 };
68301 d3.rebind(exports, dispatch, 'on');
68302 return exports;
68303};
68304
68305µ.PolyChart.defaultConfig = function() {
68306 var config = {
68307 data: {
68308 name: 'geom1',
68309 t: [ [ 1, 2, 3, 4 ] ],
68310 r: [ [ 1, 2, 3, 4 ] ],
68311 dotType: 'circle',
68312 dotSize: 64,
68313 dotVisible: false,
68314 barWidth: 20,
68315 color: '#ffa500',
68316 strokeSize: 1,
68317 strokeColor: 'silver',
68318 strokeDash: 'solid',
68319 opacity: 1,
68320 index: 0,
68321 visible: true,
68322 visibleInLegend: true
68323 },
68324 geometryConfig: {
68325 geometry: 'LinePlot',
68326 geometryType: 'arc',
68327 direction: 'clockwise',
68328 orientation: 0,
68329 container: 'body',
68330 radialScale: null,
68331 angularScale: null,
68332 colorScale: d3.scale.category20()
68333 }
68334 };
68335 return config;
68336};
68337
68338µ.BarChart = function module() {
68339 return µ.PolyChart();
68340};
68341
68342µ.BarChart.defaultConfig = function() {
68343 var config = {
68344 geometryConfig: {
68345 geometryType: 'bar'
68346 }
68347 };
68348 return config;
68349};
68350
68351µ.AreaChart = function module() {
68352 return µ.PolyChart();
68353};
68354
68355µ.AreaChart.defaultConfig = function() {
68356 var config = {
68357 geometryConfig: {
68358 geometryType: 'arc'
68359 }
68360 };
68361 return config;
68362};
68363
68364µ.DotPlot = function module() {
68365 return µ.PolyChart();
68366};
68367
68368µ.DotPlot.defaultConfig = function() {
68369 var config = {
68370 geometryConfig: {
68371 geometryType: 'dot',
68372 dotType: 'circle'
68373 }
68374 };
68375 return config;
68376};
68377
68378µ.LinePlot = function module() {
68379 return µ.PolyChart();
68380};
68381
68382µ.LinePlot.defaultConfig = function() {
68383 var config = {
68384 geometryConfig: {
68385 geometryType: 'line'
68386 }
68387 };
68388 return config;
68389};
68390
68391µ.Legend = function module() {
68392 var config = µ.Legend.defaultConfig();
68393 var dispatch = d3.dispatch('hover');
68394 function exports() {
68395 var legendConfig = config.legendConfig;
68396 var flattenData = config.data.map(function(d, i) {
68397 return [].concat(d).map(function(dB, iB) {
68398 var element = extendDeepAll({}, legendConfig.elements[i]);
68399 element.name = dB;
68400 element.color = [].concat(legendConfig.elements[i].color)[iB];
68401 return element;
68402 });
68403 });
68404 var data = d3.merge(flattenData);
68405 data = data.filter(function(d, i) {
68406 return legendConfig.elements[i] && (legendConfig.elements[i].visibleInLegend || typeof legendConfig.elements[i].visibleInLegend === 'undefined');
68407 });
68408 if (legendConfig.reverseOrder) data = data.reverse();
68409 var container = legendConfig.container;
68410 if (typeof container == 'string' || container.nodeName) container = d3.select(container);
68411 var colors = data.map(function(d, i) {
68412 return d.color;
68413 });
68414 var lineHeight = legendConfig.fontSize;
68415 var isContinuous = legendConfig.isContinuous == null ? typeof data[0] === 'number' : legendConfig.isContinuous;
68416 var height = isContinuous ? legendConfig.height : lineHeight * data.length;
68417 var legendContainerGroup = container.classed('legend-group', true);
68418 var svg = legendContainerGroup.selectAll('svg').data([ 0 ]);
68419 var svgEnter = svg.enter().append('svg').attr({
68420 width: 300,
68421 height: height + lineHeight,
68422 xmlns: 'http://www.w3.org/2000/svg',
68423 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
68424 version: '1.1'
68425 });
68426 svgEnter.append('g').classed('legend-axis', true);
68427 svgEnter.append('g').classed('legend-marks', true);
68428 var dataNumbered = d3.range(data.length);
68429 var colorScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered).range(colors);
68430 var dataScale = d3.scale[isContinuous ? 'linear' : 'ordinal']().domain(dataNumbered)[isContinuous ? 'range' : 'rangePoints']([ 0, height ]);
68431 var shapeGenerator = function(_type, _size) {
68432 var squareSize = _size * 3;
68433 if (_type === 'line') {
68434 return 'M' + [ [ -_size / 2, -_size / 12 ], [ _size / 2, -_size / 12 ], [ _size / 2, _size / 12 ], [ -_size / 2, _size / 12 ] ] + 'Z';
68435 } 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)();
68436 };
68437 if (isContinuous) {
68438 var gradient = svg.select('.legend-marks').append('defs').append('linearGradient').attr({
68439 id: 'grad1',
68440 x1: '0%',
68441 y1: '0%',
68442 x2: '0%',
68443 y2: '100%'
68444 }).selectAll('stop').data(colors);
68445 gradient.enter().append('stop');
68446 gradient.attr({
68447 offset: function(d, i) {
68448 return i / (colors.length - 1) * 100 + '%';
68449 }
68450 }).style({
68451 'stop-color': function(d, i) {
68452 return d;
68453 }
68454 });
68455 svg.append('rect').classed('legend-mark', true).attr({
68456 height: legendConfig.height,
68457 width: legendConfig.colorBandWidth,
68458 fill: 'url(#grad1)'
68459 });
68460 } else {
68461 var legendElement = svg.select('.legend-marks').selectAll('path.legend-mark').data(data);
68462 legendElement.enter().append('path').classed('legend-mark', true);
68463 legendElement.attr({
68464 transform: function(d, i) {
68465 return 'translate(' + [ lineHeight / 2, dataScale(i) + lineHeight / 2 ] + ')';
68466 },
68467 d: function(d, i) {
68468 var symbolType = d.symbol;
68469 return shapeGenerator(symbolType, lineHeight);
68470 },
68471 fill: function(d, i) {
68472 return colorScale(i);
68473 }
68474 });
68475 legendElement.exit().remove();
68476 }
68477 var legendAxis = d3.svg.axis().scale(dataScale).orient('right');
68478 var axis = svg.select('g.legend-axis').attr({
68479 transform: 'translate(' + [ isContinuous ? legendConfig.colorBandWidth : lineHeight, lineHeight / 2 ] + ')'
68480 }).call(legendAxis);
68481 axis.selectAll('.domain').style({
68482 fill: 'none',
68483 stroke: 'none'
68484 });
68485 axis.selectAll('line').style({
68486 fill: 'none',
68487 stroke: isContinuous ? legendConfig.textColor : 'none'
68488 });
68489 axis.selectAll('text').style({
68490 fill: legendConfig.textColor,
68491 'font-size': legendConfig.fontSize
68492 }).text(function(d, i) {
68493 return data[i].name;
68494 });
68495 return exports;
68496 }
68497 exports.config = function(_x) {
68498 if (!arguments.length) return config;
68499 extendDeepAll(config, _x);
68500 return this;
68501 };
68502 d3.rebind(exports, dispatch, 'on');
68503 return exports;
68504};
68505
68506µ.Legend.defaultConfig = function(d, i) {
68507 var config = {
68508 data: [ 'a', 'b', 'c' ],
68509 legendConfig: {
68510 elements: [ {
68511 symbol: 'line',
68512 color: 'red'
68513 }, {
68514 symbol: 'square',
68515 color: 'yellow'
68516 }, {
68517 symbol: 'diamond',
68518 color: 'limegreen'
68519 } ],
68520 height: 150,
68521 colorBandWidth: 30,
68522 fontSize: 12,
68523 container: 'body',
68524 isContinuous: null,
68525 textColor: 'grey',
68526 reverseOrder: false
68527 }
68528 };
68529 return config;
68530};
68531
68532µ.tooltipPanel = function() {
68533 var tooltipEl, tooltipTextEl, backgroundEl;
68534 var config = {
68535 container: null,
68536 hasTick: false,
68537 fontSize: 12,
68538 color: 'white',
68539 padding: 5
68540 };
68541 var id = 'tooltip-' + µ.tooltipPanel.uid++;
68542 var tickSize = 10;
68543 var exports = function() {
68544 tooltipEl = config.container.selectAll('g.' + id).data([ 0 ]);
68545 var tooltipEnter = tooltipEl.enter().append('g').classed(id, true).style({
68546 'pointer-events': 'none',
68547 display: 'none'
68548 });
68549 backgroundEl = tooltipEnter.append('path').style({
68550 fill: 'white',
68551 'fill-opacity': .9
68552 }).attr({
68553 d: 'M0 0'
68554 });
68555 tooltipTextEl = tooltipEnter.append('text').attr({
68556 dx: config.padding + tickSize,
68557 dy: +config.fontSize * .3
68558 });
68559 return exports;
68560 };
68561 exports.text = function(_text) {
68562 var l = d3.hsl(config.color).l;
68563 var strokeColor = l >= .5 ? '#aaa' : 'white';
68564 var fillColor = l >= .5 ? 'black' : 'white';
68565 var text = _text || '';
68566 tooltipTextEl.style({
68567 fill: fillColor,
68568 'font-size': config.fontSize + 'px'
68569 }).text(text);
68570 var padding = config.padding;
68571 var bbox = tooltipTextEl.node().getBBox();
68572 var boxStyle = {
68573 fill: config.color,
68574 stroke: strokeColor,
68575 'stroke-width': '2px'
68576 };
68577 var backGroundW = bbox.width + padding * 2 + tickSize;
68578 var backGroundH = bbox.height + padding * 2;
68579 backgroundEl.attr({
68580 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'
68581 }).style(boxStyle);
68582 tooltipEl.attr({
68583 transform: 'translate(' + [ tickSize, -backGroundH / 2 + padding * 2 ] + ')'
68584 });
68585 tooltipEl.style({
68586 display: 'block'
68587 });
68588 return exports;
68589 };
68590 exports.move = function(_pos) {
68591 if (!tooltipEl) return;
68592 tooltipEl.attr({
68593 transform: 'translate(' + [ _pos[0], _pos[1] ] + ')'
68594 }).style({
68595 display: 'block'
68596 });
68597 return exports;
68598 };
68599 exports.hide = function() {
68600 if (!tooltipEl) return;
68601 tooltipEl.style({
68602 display: 'none'
68603 });
68604 return exports;
68605 };
68606 exports.show = function() {
68607 if (!tooltipEl) return;
68608 tooltipEl.style({
68609 display: 'block'
68610 });
68611 return exports;
68612 };
68613 exports.config = function(_x) {
68614 extendDeepAll(config, _x);
68615 return exports;
68616 };
68617 return exports;
68618};
68619
68620µ.tooltipPanel.uid = 1;
68621
68622µ.adapter = {};
68623
68624µ.adapter.plotly = function module() {
68625 var exports = {};
68626 exports.convert = function(_inputConfig, reverse) {
68627 var outputConfig = {};
68628 if (_inputConfig.data) {
68629 outputConfig.data = _inputConfig.data.map(function(d, i) {
68630 var r = extendDeepAll({}, d);
68631 var toTranslate = [
68632 [ r, [ 'marker', 'color' ], [ 'color' ] ],
68633 [ r, [ 'marker', 'opacity' ], [ 'opacity' ] ],
68634 [ r, [ 'marker', 'line', 'color' ], [ 'strokeColor' ] ],
68635 [ r, [ 'marker', 'line', 'dash' ], [ 'strokeDash' ] ],
68636 [ r, [ 'marker', 'line', 'width' ], [ 'strokeSize' ] ],
68637 [ r, [ 'marker', 'symbol' ], [ 'dotType' ] ],
68638 [ r, [ 'marker', 'size' ], [ 'dotSize' ] ],
68639 [ r, [ 'marker', 'barWidth' ], [ 'barWidth' ] ],
68640 [ r, [ 'line', 'interpolation' ], [ 'lineInterpolation' ] ],
68641 [ r, [ 'showlegend' ], [ 'visibleInLegend' ] ]
68642 ];
68643 toTranslate.forEach(function(d, i) {
68644 µ.util.translator.apply(null, d.concat(reverse));
68645 });
68646
68647 if (!reverse) delete r.marker;
68648 if (reverse) delete r.groupId;
68649 if (!reverse) {
68650 if (r.type === 'scatter') {
68651 if (r.mode === 'lines') r.geometry = 'LinePlot'; else if (r.mode === 'markers') r.geometry = 'DotPlot'; else if (r.mode === 'lines+markers') {
68652 r.geometry = 'LinePlot';
68653 r.dotVisible = true;
68654 }
68655 } else if (r.type === 'area') r.geometry = 'AreaChart'; else if (r.type === 'bar') r.geometry = 'BarChart';
68656 delete r.mode;
68657 delete r.type;
68658 } else {
68659 if (r.geometry === 'LinePlot') {
68660 r.type = 'scatter';
68661 if (r.dotVisible === true) {
68662 delete r.dotVisible;
68663 r.mode = 'lines+markers';
68664 } else r.mode = 'lines';
68665 } else if (r.geometry === 'DotPlot') {
68666 r.type = 'scatter';
68667 r.mode = 'markers';
68668 } else if (r.geometry === 'AreaChart') r.type = 'area'; else if (r.geometry === 'BarChart') r.type = 'bar';
68669 delete r.geometry;
68670 }
68671 return r;
68672 });
68673 if (!reverse && _inputConfig.layout && _inputConfig.layout.barmode === 'stack') {
68674 var duplicates = µ.util.duplicates(outputConfig.data.map(function(d, i) {
68675 return d.geometry;
68676 }));
68677 outputConfig.data.forEach(function(d, i) {
68678 var idx = duplicates.indexOf(d.geometry);
68679 if (idx != -1) outputConfig.data[i].groupId = idx;
68680 });
68681 }
68682 }
68683 if (_inputConfig.layout) {
68684 var r = extendDeepAll({}, _inputConfig.layout);
68685 var toTranslate = [
68686 [ r, [ 'plot_bgcolor' ], [ 'backgroundColor' ] ],
68687 [ r, [ 'showlegend' ], [ 'showLegend' ] ],
68688 [ r, [ 'radialaxis' ], [ 'radialAxis' ] ],
68689 [ r, [ 'angularaxis' ], [ 'angularAxis' ] ],
68690 [ r.angularaxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68691 [ r.angularaxis, [ 'showticklabels' ], [ 'labelsVisible' ] ],
68692 [ r.angularaxis, [ 'nticks' ], [ 'ticksCount' ] ],
68693 [ r.angularaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68694 [ r.angularaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68695 [ r.angularaxis, [ 'range' ], [ 'domain' ] ],
68696 [ r.angularaxis, [ 'endpadding' ], [ 'endPadding' ] ],
68697 [ r.radialaxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68698 [ r.radialaxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68699 [ r.radialaxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68700 [ r.radialaxis, [ 'range' ], [ 'domain' ] ],
68701 [ r.angularAxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68702 [ r.angularAxis, [ 'showticklabels' ], [ 'labelsVisible' ] ],
68703 [ r.angularAxis, [ 'nticks' ], [ 'ticksCount' ] ],
68704 [ r.angularAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68705 [ r.angularAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68706 [ r.angularAxis, [ 'range' ], [ 'domain' ] ],
68707 [ r.angularAxis, [ 'endpadding' ], [ 'endPadding' ] ],
68708 [ r.radialAxis, [ 'showline' ], [ 'gridLinesVisible' ] ],
68709 [ r.radialAxis, [ 'tickorientation' ], [ 'tickOrientation' ] ],
68710 [ r.radialAxis, [ 'ticksuffix' ], [ 'ticksSuffix' ] ],
68711 [ r.radialAxis, [ 'range' ], [ 'domain' ] ],
68712 [ r.font, [ 'outlinecolor' ], [ 'outlineColor' ] ],
68713 [ r.legend, [ 'traceorder' ], [ 'reverseOrder' ] ],
68714 [ r, [ 'labeloffset' ], [ 'labelOffset' ] ],
68715 [ r, [ 'defaultcolorrange' ], [ 'defaultColorRange' ] ]
68716 ];
68717 toTranslate.forEach(function(d, i) {
68718 µ.util.translator.apply(null, d.concat(reverse));
68719 });
68720
68721 if (!reverse) {
68722 if (r.angularAxis && typeof r.angularAxis.ticklen !== 'undefined') r.tickLength = r.angularAxis.ticklen;
68723 if (r.angularAxis && typeof r.angularAxis.tickcolor !== 'undefined') r.tickColor = r.angularAxis.tickcolor;
68724 } else {
68725 if (typeof r.tickLength !== 'undefined') {
68726 r.angularaxis.ticklen = r.tickLength;
68727 delete r.tickLength;
68728 }
68729 if (r.tickColor) {
68730 r.angularaxis.tickcolor = r.tickColor;
68731 delete r.tickColor;
68732 }
68733 }
68734 if (r.legend && typeof r.legend.reverseOrder != 'boolean') {
68735 r.legend.reverseOrder = r.legend.reverseOrder != 'normal';
68736 }
68737 if (r.legend && typeof r.legend.traceorder == 'boolean') {
68738 r.legend.traceorder = r.legend.traceorder ? 'reversed' : 'normal';
68739 delete r.legend.reverseOrder;
68740 }
68741 if (r.margin && typeof r.margin.t != 'undefined') {
68742 var source = [ 't', 'r', 'b', 'l', 'pad' ];
68743 var target = [ 'top', 'right', 'bottom', 'left', 'pad' ];
68744 var margin = {};
68745 d3.entries(r.margin).forEach(function(dB, iB) {
68746 margin[target[source.indexOf(dB.key)]] = dB.value;
68747 });
68748 r.margin = margin;
68749 }
68750 if (reverse) {
68751 delete r.needsEndSpacing;
68752 delete r.minorTickColor;
68753 delete r.minorTicks;
68754 delete r.angularaxis.ticksCount;
68755 delete r.angularaxis.ticksCount;
68756 delete r.angularaxis.ticksStep;
68757 delete r.angularaxis.rewriteTicks;
68758 delete r.angularaxis.nticks;
68759 delete r.radialaxis.ticksCount;
68760 delete r.radialaxis.ticksCount;
68761 delete r.radialaxis.ticksStep;
68762 delete r.radialaxis.rewriteTicks;
68763 delete r.radialaxis.nticks;
68764 }
68765 outputConfig.layout = r;
68766 }
68767 return outputConfig;
68768 };
68769 return exports;
68770};
68771
68772},{"../../../constants/alignment":154,"../../../lib":178,"d3":16}],261:[function(_dereq_,module,exports){
68773/**
68774* Copyright 2012-2020, Plotly, Inc.
68775* All rights reserved.
68776*
68777* This source code is licensed under the MIT license found in the
68778* LICENSE file in the root directory of this source tree.
68779*/
68780
68781/* eslint-disable new-cap */
68782
68783'use strict';
68784
68785var d3 = _dereq_('d3');
68786var Lib = _dereq_('../../../lib');
68787var Color = _dereq_('../../../components/color');
68788
68789var micropolar = _dereq_('./micropolar');
68790var UndoManager = _dereq_('./undo_manager');
68791var extendDeepAll = Lib.extendDeepAll;
68792
68793var manager = module.exports = {};
68794
68795manager.framework = function(_gd) {
68796 var config, previousConfigClone, plot, convertedInput, container;
68797 var undoManager = new UndoManager();
68798
68799 function exports(_inputConfig, _container) {
68800 if(_container) container = _container;
68801 d3.select(d3.select(container).node().parentNode).selectAll('.svg-container>*:not(.chart-root)').remove();
68802
68803 config = (!config) ?
68804 _inputConfig :
68805 extendDeepAll(config, _inputConfig);
68806
68807 if(!plot) plot = micropolar.Axis();
68808 convertedInput = micropolar.adapter.plotly().convert(config);
68809 plot.config(convertedInput).render(container);
68810 _gd.data = config.data;
68811 _gd.layout = config.layout;
68812 manager.fillLayout(_gd);
68813 return config;
68814 }
68815 exports.isPolar = true;
68816 exports.svg = function() { return plot.svg(); };
68817 exports.getConfig = function() { return config; };
68818 exports.getLiveConfig = function() {
68819 return micropolar.adapter.plotly().convert(plot.getLiveConfig(), true);
68820 };
68821 exports.getLiveScales = function() { return {t: plot.angularScale(), r: plot.radialScale()}; };
68822 exports.setUndoPoint = function() {
68823 var that = this;
68824 var configClone = micropolar.util.cloneJson(config);
68825 (function(_configClone, _previousConfigClone) {
68826 undoManager.add({
68827 undo: function() {
68828 if(_previousConfigClone) that(_previousConfigClone);
68829 },
68830 redo: function() {
68831 that(_configClone);
68832 }
68833 });
68834 })(configClone, previousConfigClone);
68835 previousConfigClone = micropolar.util.cloneJson(configClone);
68836 };
68837 exports.undo = function() { undoManager.undo(); };
68838 exports.redo = function() { undoManager.redo(); };
68839 return exports;
68840};
68841
68842manager.fillLayout = function(_gd) {
68843 var container = d3.select(_gd).selectAll('.plot-container');
68844 var paperDiv = container.selectAll('.svg-container');
68845 var paper = _gd.framework && _gd.framework.svg && _gd.framework.svg();
68846 var dflts = {
68847 width: 800,
68848 height: 600,
68849 paper_bgcolor: Color.background,
68850 _container: container,
68851 _paperdiv: paperDiv,
68852 _paper: paper
68853 };
68854
68855 _gd._fullLayout = extendDeepAll(dflts, _gd.layout);
68856};
68857
68858},{"../../../components/color":52,"../../../lib":178,"./micropolar":260,"./undo_manager":262,"d3":16}],262:[function(_dereq_,module,exports){
68859/**
68860* Copyright 2012-2020, Plotly, Inc.
68861* All rights reserved.
68862*
68863* This source code is licensed under the MIT license found in the
68864* LICENSE file in the root directory of this source tree.
68865*/
68866
68867'use strict';
68868
68869// Modified from https://github.com/ArthurClemens/Javascript-Undo-Manager
68870// Copyright (c) 2010-2013 Arthur Clemens, arthur@visiblearea.com
68871module.exports = function UndoManager() {
68872 var undoCommands = [];
68873 var index = -1;
68874 var isExecuting = false;
68875 var callback;
68876
68877 function execute(command, action) {
68878 if(!command) return this;
68879
68880 isExecuting = true;
68881 command[action]();
68882 isExecuting = false;
68883
68884 return this;
68885 }
68886
68887 return {
68888 add: function(command) {
68889 if(isExecuting) return this;
68890 undoCommands.splice(index + 1, undoCommands.length - index);
68891 undoCommands.push(command);
68892 index = undoCommands.length - 1;
68893 return this;
68894 },
68895 setCallback: function(callbackFunc) { callback = callbackFunc; },
68896 undo: function() {
68897 var command = undoCommands[index];
68898 if(!command) return this;
68899 execute(command, 'undo');
68900 index -= 1;
68901 if(callback) callback(command.undo);
68902 return this;
68903 },
68904 redo: function() {
68905 var command = undoCommands[index + 1];
68906 if(!command) return this;
68907 execute(command, 'redo');
68908 index += 1;
68909 if(callback) callback(command.redo);
68910 return this;
68911 },
68912 clear: function() {
68913 undoCommands = [];
68914 index = -1;
68915 },
68916 hasUndo: function() { return index !== -1; },
68917 hasRedo: function() { return index < (undoCommands.length - 1); },
68918 getCommands: function() { return undoCommands; },
68919 getPreviousCommand: function() { return undoCommands[index - 1]; },
68920 getIndex: function() { return index; }
68921 };
68922};
68923
68924},{}],263:[function(_dereq_,module,exports){
68925/**
68926* Copyright 2012-2020, Plotly, Inc.
68927* All rights reserved.
68928*
68929* This source code is licensed under the MIT license found in the
68930* LICENSE file in the root directory of this source tree.
68931*/
68932
68933
68934'use strict';
68935
68936var Lib = _dereq_('../lib');
68937var Template = _dereq_('../plot_api/plot_template');
68938var handleDomainDefaults = _dereq_('./domain').defaults;
68939
68940
68941/**
68942 * Find and supply defaults to all subplots of a given type
68943 * This handles subplots that are contained within one container - so
68944 * gl3d, geo, ternary... but not 2d axes which have separate x and y axes
68945 * finds subplots, coerces their `domain` attributes, then calls the
68946 * given handleDefaults function to fill in everything else.
68947 *
68948 * layoutIn: the complete user-supplied input layout
68949 * layoutOut: the complete finished layout
68950 * fullData: the finished data array, used only to find subplots
68951 * opts: {
68952 * type: subplot type string
68953 * attributes: subplot attributes object
68954 * partition: 'x' or 'y', which direction to divide domain space by default
68955 * (default 'x', ie side-by-side subplots)
68956 * TODO: this option is only here because 3D and geo made opposite
68957 * choices in this regard previously and I didn't want to change it.
68958 * Instead we should do:
68959 * - something consistent
68960 * - something more square (4 cuts 2x2, 5/6 cuts 2x3, etc.)
68961 * - something that includes all subplot types in one arrangement,
68962 * now that we can have them together!
68963 * handleDefaults: function of (subplotLayoutIn, subplotLayoutOut, coerce, opts)
68964 * this opts object is passed through to handleDefaults, so attach any
68965 * additional items needed by this function here as well
68966 * }
68967 */
68968module.exports = function handleSubplotDefaults(layoutIn, layoutOut, fullData, opts) {
68969 var subplotType = opts.type;
68970 var subplotAttributes = opts.attributes;
68971 var handleDefaults = opts.handleDefaults;
68972 var partition = opts.partition || 'x';
68973
68974 var ids = layoutOut._subplots[subplotType];
68975 var idsLength = ids.length;
68976
68977 var baseId = idsLength && ids[0].replace(/\d+$/, '');
68978
68979 var subplotLayoutIn, subplotLayoutOut;
68980
68981 function coerce(attr, dflt) {
68982 return Lib.coerce(subplotLayoutIn, subplotLayoutOut, subplotAttributes, attr, dflt);
68983 }
68984
68985 for(var i = 0; i < idsLength; i++) {
68986 var id = ids[i];
68987
68988 // ternary traces get a layout ternary for free!
68989 if(layoutIn[id]) subplotLayoutIn = layoutIn[id];
68990 else subplotLayoutIn = layoutIn[id] = {};
68991
68992 subplotLayoutOut = Template.newContainer(layoutOut, id, baseId);
68993
68994 // All subplot containers get a `uirevision` inheriting from the base.
68995 // Currently all subplots containers have some user interaction
68996 // attributes, but if we ever add one that doesn't, we would need an
68997 // option to skip this step.
68998 coerce('uirevision', layoutOut.uirevision);
68999
69000 var dfltDomains = {};
69001 dfltDomains[partition] = [i / idsLength, (i + 1) / idsLength];
69002 handleDomainDefaults(subplotLayoutOut, layoutOut, coerce, dfltDomains);
69003
69004 opts.id = id;
69005 handleDefaults(subplotLayoutIn, subplotLayoutOut, coerce, opts);
69006 }
69007};
69008
69009},{"../lib":178,"../plot_api/plot_template":212,"./domain":249}],264:[function(_dereq_,module,exports){
69010/**
69011* Copyright 2012-2020, Plotly, Inc.
69012* All rights reserved.
69013*
69014* This source code is licensed under the MIT license found in the
69015* LICENSE file in the root directory of this source tree.
69016*/
69017
69018'use strict';
69019
69020var FORMAT_LINK = _dereq_('../constants/docs').FORMAT_LINK;
69021var DATE_FORMAT_LINK = _dereq_('../constants/docs').DATE_FORMAT_LINK;
69022
69023var templateFormatStringDescription = [
69024 'Variables are inserted using %{variable}, for example "y: %{y}".',
69025 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".',
69026 FORMAT_LINK,
69027 'for details on the formatting syntax.',
69028 'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{2019-01-01|%A}".',
69029 DATE_FORMAT_LINK,
69030 'for details on the date formatting syntax.'
69031].join(' ');
69032
69033function describeVariables(extra) {
69034 var descPart = extra.description ? ' ' + extra.description : '';
69035 var keys = extra.keys || [];
69036 if(keys.length > 0) {
69037 var quotedKeys = [];
69038 for(var i = 0; i < keys.length; i++) {
69039 quotedKeys[i] = '`' + keys[i] + '`';
69040 }
69041 descPart = descPart + 'Finally, the template string has access to ';
69042 if(keys.length === 1) {
69043 descPart = 'variable ' + quotedKeys[0];
69044 } else {
69045 descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
69046 }
69047 }
69048 return descPart;
69049}
69050
69051exports.hovertemplateAttrs = function(opts, extra) {
69052 opts = opts || {};
69053 extra = extra || {};
69054
69055 var descPart = describeVariables(extra);
69056
69057 var hovertemplate = {
69058 valType: 'string',
69059
69060 dflt: '',
69061 editType: opts.editType || 'none',
69062
69063 };
69064
69065 if(opts.arrayOk !== false) {
69066 hovertemplate.arrayOk = true;
69067 }
69068
69069 return hovertemplate;
69070};
69071
69072exports.texttemplateAttrs = function(opts, extra) {
69073 opts = opts || {};
69074 extra = extra || {};
69075
69076 var descPart = describeVariables(extra);
69077
69078 var texttemplate = {
69079 valType: 'string',
69080
69081 dflt: '',
69082 editType: opts.editType || 'calc',
69083
69084 };
69085
69086 if(opts.arrayOk !== false) {
69087 texttemplate.arrayOk = true;
69088 }
69089 return texttemplate;
69090};
69091
69092},{"../constants/docs":155}],265:[function(_dereq_,module,exports){
69093/**
69094* Copyright 2012-2020, Plotly, Inc.
69095* All rights reserved.
69096*
69097* This source code is licensed under the MIT license found in the
69098* LICENSE file in the root directory of this source tree.
69099*/
69100
69101
69102'use strict';
69103
69104var Ternary = _dereq_('./ternary');
69105
69106var getSubplotCalcData = _dereq_('../../plots/get_data').getSubplotCalcData;
69107var counterRegex = _dereq_('../../lib').counterRegex;
69108var TERNARY = 'ternary';
69109
69110exports.name = TERNARY;
69111
69112var attr = exports.attr = 'subplot';
69113
69114exports.idRoot = TERNARY;
69115
69116exports.idRegex = exports.attrRegex = counterRegex(TERNARY);
69117
69118var attributes = exports.attributes = {};
69119attributes[attr] = {
69120 valType: 'subplotid',
69121
69122 dflt: 'ternary',
69123 editType: 'calc',
69124
69125};
69126
69127exports.layoutAttributes = _dereq_('./layout_attributes');
69128
69129exports.supplyLayoutDefaults = _dereq_('./layout_defaults');
69130
69131exports.plot = function plot(gd) {
69132 var fullLayout = gd._fullLayout;
69133 var calcData = gd.calcdata;
69134 var ternaryIds = fullLayout._subplots[TERNARY];
69135
69136 for(var i = 0; i < ternaryIds.length; i++) {
69137 var ternaryId = ternaryIds[i];
69138 var ternaryCalcData = getSubplotCalcData(calcData, TERNARY, ternaryId);
69139 var ternary = fullLayout[ternaryId]._subplot;
69140
69141 // If ternary is not instantiated, create one!
69142 if(!ternary) {
69143 ternary = new Ternary({
69144 id: ternaryId,
69145 graphDiv: gd,
69146 container: fullLayout._ternarylayer.node()
69147 },
69148 fullLayout
69149 );
69150
69151 fullLayout[ternaryId]._subplot = ternary;
69152 }
69153
69154 ternary.plot(ternaryCalcData, fullLayout, gd._promises);
69155 }
69156};
69157
69158exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
69159 var oldTernaryKeys = oldFullLayout._subplots[TERNARY] || [];
69160
69161 for(var i = 0; i < oldTernaryKeys.length; i++) {
69162 var oldTernaryKey = oldTernaryKeys[i];
69163 var oldTernary = oldFullLayout[oldTernaryKey]._subplot;
69164
69165 if(!newFullLayout[oldTernaryKey] && !!oldTernary) {
69166 oldTernary.plotContainer.remove();
69167 oldTernary.clipDef.remove();
69168 oldTernary.clipDefRelative.remove();
69169 oldTernary.layers['a-title'].remove();
69170 oldTernary.layers['b-title'].remove();
69171 oldTernary.layers['c-title'].remove();
69172 }
69173 }
69174};
69175
69176},{"../../lib":178,"../../plots/get_data":252,"./layout_attributes":266,"./layout_defaults":267,"./ternary":268}],266:[function(_dereq_,module,exports){
69177/**
69178* Copyright 2012-2020, Plotly, Inc.
69179* All rights reserved.
69180*
69181* This source code is licensed under the MIT license found in the
69182* LICENSE file in the root directory of this source tree.
69183*/
69184
69185'use strict';
69186
69187var colorAttrs = _dereq_('../../components/color/attributes');
69188var domainAttrs = _dereq_('../domain').attributes;
69189var axesAttrs = _dereq_('../cartesian/layout_attributes');
69190
69191var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
69192var extendFlat = _dereq_('../../lib/extend').extendFlat;
69193
69194var ternaryAxesAttrs = {
69195 title: {
69196 text: axesAttrs.title.text,
69197 font: axesAttrs.title.font
69198 // TODO does standoff here make sense?
69199 },
69200 color: axesAttrs.color,
69201 // ticks
69202 tickmode: axesAttrs.tickmode,
69203 nticks: extendFlat({}, axesAttrs.nticks, {dflt: 6, min: 1}),
69204 tick0: axesAttrs.tick0,
69205 dtick: axesAttrs.dtick,
69206 tickvals: axesAttrs.tickvals,
69207 ticktext: axesAttrs.ticktext,
69208 ticks: axesAttrs.ticks,
69209 ticklen: axesAttrs.ticklen,
69210 tickwidth: axesAttrs.tickwidth,
69211 tickcolor: axesAttrs.tickcolor,
69212 showticklabels: axesAttrs.showticklabels,
69213 showtickprefix: axesAttrs.showtickprefix,
69214 tickprefix: axesAttrs.tickprefix,
69215 showticksuffix: axesAttrs.showticksuffix,
69216 ticksuffix: axesAttrs.ticksuffix,
69217 showexponent: axesAttrs.showexponent,
69218 exponentformat: axesAttrs.exponentformat,
69219 separatethousands: axesAttrs.separatethousands,
69220 tickfont: axesAttrs.tickfont,
69221 tickangle: axesAttrs.tickangle,
69222 tickformat: axesAttrs.tickformat,
69223 tickformatstops: axesAttrs.tickformatstops,
69224 hoverformat: axesAttrs.hoverformat,
69225 // lines and grids
69226 showline: extendFlat({}, axesAttrs.showline, {dflt: true}),
69227 linecolor: axesAttrs.linecolor,
69228 linewidth: axesAttrs.linewidth,
69229 showgrid: extendFlat({}, axesAttrs.showgrid, {dflt: true}),
69230 gridcolor: axesAttrs.gridcolor,
69231 gridwidth: axesAttrs.gridwidth,
69232 layer: axesAttrs.layer,
69233 // range
69234 min: {
69235 valType: 'number',
69236 dflt: 0,
69237
69238 min: 0,
69239
69240 },
69241 _deprecated: {
69242 title: axesAttrs._deprecated.title,
69243 titlefont: axesAttrs._deprecated.titlefont
69244 }
69245};
69246
69247var attrs = module.exports = overrideAll({
69248 domain: domainAttrs({name: 'ternary'}),
69249
69250 bgcolor: {
69251 valType: 'color',
69252
69253 dflt: colorAttrs.background,
69254
69255 },
69256 sum: {
69257 valType: 'number',
69258
69259 dflt: 1,
69260 min: 0,
69261
69262 },
69263 aaxis: ternaryAxesAttrs,
69264 baxis: ternaryAxesAttrs,
69265 caxis: ternaryAxesAttrs
69266}, 'plot', 'from-root');
69267
69268// set uirevisions outside of `overrideAll` so we can get `editType: none`
69269attrs.uirevision = {
69270 valType: 'any',
69271
69272 editType: 'none',
69273
69274};
69275
69276attrs.aaxis.uirevision = attrs.baxis.uirevision = attrs.caxis.uirevision = {
69277 valType: 'any',
69278
69279 editType: 'none',
69280
69281};
69282
69283},{"../../components/color/attributes":51,"../../lib/extend":173,"../../plot_api/edit_types":205,"../cartesian/layout_attributes":236,"../domain":249}],267:[function(_dereq_,module,exports){
69284/**
69285* Copyright 2012-2020, Plotly, Inc.
69286* All rights reserved.
69287*
69288* This source code is licensed under the MIT license found in the
69289* LICENSE file in the root directory of this source tree.
69290*/
69291
69292'use strict';
69293
69294var Color = _dereq_('../../components/color');
69295var Template = _dereq_('../../plot_api/plot_template');
69296var Lib = _dereq_('../../lib');
69297
69298var handleSubplotDefaults = _dereq_('../subplot_defaults');
69299var handleTickLabelDefaults = _dereq_('../cartesian/tick_label_defaults');
69300var handleTickMarkDefaults = _dereq_('../cartesian/tick_mark_defaults');
69301var handleTickValueDefaults = _dereq_('../cartesian/tick_value_defaults');
69302var handleLineGridDefaults = _dereq_('../cartesian/line_grid_defaults');
69303var layoutAttributes = _dereq_('./layout_attributes');
69304
69305var axesNames = ['aaxis', 'baxis', 'caxis'];
69306
69307module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
69308 handleSubplotDefaults(layoutIn, layoutOut, fullData, {
69309 type: 'ternary',
69310 attributes: layoutAttributes,
69311 handleDefaults: handleTernaryDefaults,
69312 font: layoutOut.font,
69313 paper_bgcolor: layoutOut.paper_bgcolor
69314 });
69315};
69316
69317function handleTernaryDefaults(ternaryLayoutIn, ternaryLayoutOut, coerce, options) {
69318 var bgColor = coerce('bgcolor');
69319 var sum = coerce('sum');
69320 options.bgColor = Color.combine(bgColor, options.paper_bgcolor);
69321 var axName, containerIn, containerOut;
69322
69323 // TODO: allow most (if not all) axis attributes to be set
69324 // in the outer container and used as defaults in the individual axes?
69325
69326 for(var j = 0; j < axesNames.length; j++) {
69327 axName = axesNames[j];
69328 containerIn = ternaryLayoutIn[axName] || {};
69329 containerOut = Template.newContainer(ternaryLayoutOut, axName);
69330 containerOut._name = axName;
69331
69332 handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut);
69333 }
69334
69335 // if the min values contradict each other, set them all to default (0)
69336 // and delete *all* the inputs so the user doesn't get confused later by
69337 // changing one and having them all change.
69338 var aaxis = ternaryLayoutOut.aaxis;
69339 var baxis = ternaryLayoutOut.baxis;
69340 var caxis = ternaryLayoutOut.caxis;
69341 if(aaxis.min + baxis.min + caxis.min >= sum) {
69342 aaxis.min = 0;
69343 baxis.min = 0;
69344 caxis.min = 0;
69345 if(ternaryLayoutIn.aaxis) delete ternaryLayoutIn.aaxis.min;
69346 if(ternaryLayoutIn.baxis) delete ternaryLayoutIn.baxis.min;
69347 if(ternaryLayoutIn.caxis) delete ternaryLayoutIn.caxis.min;
69348 }
69349}
69350
69351function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut) {
69352 var axAttrs = layoutAttributes[containerOut._name];
69353
69354 function coerce(attr, dflt) {
69355 return Lib.coerce(containerIn, containerOut, axAttrs, attr, dflt);
69356 }
69357
69358 coerce('uirevision', ternaryLayoutOut.uirevision);
69359
69360 containerOut.type = 'linear'; // no other types allowed for ternary
69361
69362 var dfltColor = coerce('color');
69363 // if axis.color was provided, use it for fonts too; otherwise,
69364 // inherit from global font color in case that was provided.
69365 var dfltFontColor = (dfltColor !== axAttrs.color.dflt) ? dfltColor : options.font.color;
69366
69367 var axName = containerOut._name;
69368 var letterUpper = axName.charAt(0).toUpperCase();
69369 var dfltTitle = 'Component ' + letterUpper;
69370
69371 var title = coerce('title.text', dfltTitle);
69372 containerOut._hovertitle = title === dfltTitle ? title : letterUpper;
69373
69374 Lib.coerceFont(coerce, 'title.font', {
69375 family: options.font.family,
69376 size: Math.round(options.font.size * 1.2),
69377 color: dfltFontColor
69378 });
69379
69380 // range is just set by 'min' - max is determined by the other axes mins
69381 coerce('min');
69382
69383 handleTickValueDefaults(containerIn, containerOut, coerce, 'linear');
69384 handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', {});
69385 handleTickMarkDefaults(containerIn, containerOut, coerce,
69386 { outerTicks: true });
69387
69388 var showTickLabels = coerce('showticklabels');
69389 if(showTickLabels) {
69390 Lib.coerceFont(coerce, 'tickfont', {
69391 family: options.font.family,
69392 size: options.font.size,
69393 color: dfltFontColor
69394 });
69395 coerce('tickangle');
69396 coerce('tickformat');
69397 }
69398
69399 handleLineGridDefaults(containerIn, containerOut, coerce, {
69400 dfltColor: dfltColor,
69401 bgColor: options.bgColor,
69402 // default grid color is darker here (60%, vs cartesian default ~91%)
69403 // because the grid is not square so the eye needs heavier cues to follow
69404 blend: 60,
69405 showLine: true,
69406 showGrid: true,
69407 noZeroLine: true,
69408 attributes: axAttrs
69409 });
69410
69411 coerce('hoverformat');
69412 coerce('layer');
69413}
69414
69415},{"../../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){
69416/**
69417* Copyright 2012-2020, Plotly, Inc.
69418* All rights reserved.
69419*
69420* This source code is licensed under the MIT license found in the
69421* LICENSE file in the root directory of this source tree.
69422*/
69423
69424
69425'use strict';
69426
69427var d3 = _dereq_('d3');
69428var tinycolor = _dereq_('tinycolor2');
69429
69430var Registry = _dereq_('../../registry');
69431var Lib = _dereq_('../../lib');
69432var _ = Lib._;
69433var Color = _dereq_('../../components/color');
69434var Drawing = _dereq_('../../components/drawing');
69435var setConvert = _dereq_('../cartesian/set_convert');
69436var extendFlat = _dereq_('../../lib/extend').extendFlat;
69437var Plots = _dereq_('../plots');
69438var Axes = _dereq_('../cartesian/axes');
69439var dragElement = _dereq_('../../components/dragelement');
69440var Fx = _dereq_('../../components/fx');
69441var dragHelpers = _dereq_('../../components/dragelement/helpers');
69442var freeMode = dragHelpers.freeMode;
69443var rectMode = dragHelpers.rectMode;
69444var Titles = _dereq_('../../components/titles');
69445var prepSelect = _dereq_('../cartesian/select').prepSelect;
69446var selectOnClick = _dereq_('../cartesian/select').selectOnClick;
69447var clearSelect = _dereq_('../cartesian/select').clearSelect;
69448var clearSelectionsCache = _dereq_('../cartesian/select').clearSelectionsCache;
69449var constants = _dereq_('../cartesian/constants');
69450
69451function Ternary(options, fullLayout) {
69452 this.id = options.id;
69453 this.graphDiv = options.graphDiv;
69454 this.init(fullLayout);
69455 this.makeFramework(fullLayout);
69456
69457 // unfortunately, we have to keep track of some axis tick settings
69458 // as ternary subplots do not implement the 'ticks' editType
69459 this.aTickLayout = null;
69460 this.bTickLayout = null;
69461 this.cTickLayout = null;
69462}
69463
69464module.exports = Ternary;
69465
69466var proto = Ternary.prototype;
69467
69468proto.init = function(fullLayout) {
69469 this.container = fullLayout._ternarylayer;
69470 this.defs = fullLayout._defs;
69471 this.layoutId = fullLayout._uid;
69472 this.traceHash = {};
69473 this.layers = {};
69474};
69475
69476proto.plot = function(ternaryCalcData, fullLayout) {
69477 var _this = this;
69478 var ternaryLayout = fullLayout[_this.id];
69479 var graphSize = fullLayout._size;
69480
69481 _this._hasClipOnAxisFalse = false;
69482 for(var i = 0; i < ternaryCalcData.length; i++) {
69483 var trace = ternaryCalcData[i][0].trace;
69484
69485 if(trace.cliponaxis === false) {
69486 _this._hasClipOnAxisFalse = true;
69487 break;
69488 }
69489 }
69490
69491 _this.updateLayers(ternaryLayout);
69492 _this.adjustLayout(ternaryLayout, graphSize);
69493 Plots.generalUpdatePerTraceModule(_this.graphDiv, _this, ternaryCalcData, ternaryLayout);
69494 _this.layers.plotbg.select('path').call(Color.fill, ternaryLayout.bgcolor);
69495};
69496
69497proto.makeFramework = function(fullLayout) {
69498 var _this = this;
69499 var gd = _this.graphDiv;
69500 var ternaryLayout = fullLayout[_this.id];
69501
69502 var clipId = _this.clipId = 'clip' + _this.layoutId + _this.id;
69503 var clipIdRelative = _this.clipIdRelative = 'clip-relative' + _this.layoutId + _this.id;
69504
69505 // clippath for this ternary subplot
69506 _this.clipDef = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
69507 s.append('path').attr('d', 'M0,0Z');
69508 });
69509
69510 // 'relative' clippath (i.e. no translation) for this ternary subplot
69511 _this.clipDefRelative = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipIdRelative, function(s) {
69512 s.append('path').attr('d', 'M0,0Z');
69513 });
69514
69515 // container for everything in this ternary subplot
69516 _this.plotContainer = Lib.ensureSingle(_this.container, 'g', _this.id);
69517 _this.updateLayers(ternaryLayout);
69518
69519 Drawing.setClipUrl(_this.layers.backplot, clipId, gd);
69520 Drawing.setClipUrl(_this.layers.grids, clipId, gd);
69521};
69522
69523proto.updateLayers = function(ternaryLayout) {
69524 var _this = this;
69525 var layers = _this.layers;
69526
69527 // inside that container, we have one container for the data, and
69528 // one each for the three axes around it.
69529
69530 var plotLayers = ['draglayer', 'plotbg', 'backplot', 'grids'];
69531
69532 if(ternaryLayout.aaxis.layer === 'below traces') {
69533 plotLayers.push('aaxis', 'aline');
69534 }
69535 if(ternaryLayout.baxis.layer === 'below traces') {
69536 plotLayers.push('baxis', 'bline');
69537 }
69538 if(ternaryLayout.caxis.layer === 'below traces') {
69539 plotLayers.push('caxis', 'cline');
69540 }
69541
69542 plotLayers.push('frontplot');
69543
69544 if(ternaryLayout.aaxis.layer === 'above traces') {
69545 plotLayers.push('aaxis', 'aline');
69546 }
69547 if(ternaryLayout.baxis.layer === 'above traces') {
69548 plotLayers.push('baxis', 'bline');
69549 }
69550 if(ternaryLayout.caxis.layer === 'above traces') {
69551 plotLayers.push('caxis', 'cline');
69552 }
69553
69554 var toplevel = _this.plotContainer.selectAll('g.toplevel')
69555 .data(plotLayers, String);
69556
69557 var grids = ['agrid', 'bgrid', 'cgrid'];
69558
69559 toplevel.enter().append('g')
69560 .attr('class', function(d) { return 'toplevel ' + d; })
69561 .each(function(d) {
69562 var s = d3.select(this);
69563 layers[d] = s;
69564
69565 // containers for different trace types.
69566 // NOTE - this is different from cartesian, where all traces
69567 // are in front of grids. Here I'm putting maps behind the grids
69568 // so the grids will always be visible if they're requested.
69569 // Perhaps we want that for cartesian too?
69570 if(d === 'frontplot') {
69571 s.append('g').classed('scatterlayer', true);
69572 } else if(d === 'backplot') {
69573 s.append('g').classed('maplayer', true);
69574 } else if(d === 'plotbg') {
69575 s.append('path').attr('d', 'M0,0Z');
69576 } else if(d === 'aline' || d === 'bline' || d === 'cline') {
69577 s.append('path');
69578 } else if(d === 'grids') {
69579 grids.forEach(function(d) {
69580 layers[d] = s.append('g').classed('grid ' + d, true);
69581 });
69582 }
69583 });
69584
69585 toplevel.order();
69586};
69587
69588var whRatio = Math.sqrt(4 / 3);
69589
69590proto.adjustLayout = function(ternaryLayout, graphSize) {
69591 var _this = this;
69592 var domain = ternaryLayout.domain;
69593 var xDomainCenter = (domain.x[0] + domain.x[1]) / 2;
69594 var yDomainCenter = (domain.y[0] + domain.y[1]) / 2;
69595 var xDomain = domain.x[1] - domain.x[0];
69596 var yDomain = domain.y[1] - domain.y[0];
69597 var wmax = xDomain * graphSize.w;
69598 var hmax = yDomain * graphSize.h;
69599 var sum = ternaryLayout.sum;
69600 var amin = ternaryLayout.aaxis.min;
69601 var bmin = ternaryLayout.baxis.min;
69602 var cmin = ternaryLayout.caxis.min;
69603
69604 var x0, y0, w, h, xDomainFinal, yDomainFinal;
69605
69606 if(wmax > whRatio * hmax) {
69607 h = hmax;
69608 w = h * whRatio;
69609 } else {
69610 w = wmax;
69611 h = w / whRatio;
69612 }
69613
69614 xDomainFinal = xDomain * w / wmax;
69615 yDomainFinal = yDomain * h / hmax;
69616
69617 x0 = graphSize.l + graphSize.w * xDomainCenter - w / 2;
69618 y0 = graphSize.t + graphSize.h * (1 - yDomainCenter) - h / 2;
69619
69620 _this.x0 = x0;
69621 _this.y0 = y0;
69622 _this.w = w;
69623 _this.h = h;
69624 _this.sum = sum;
69625
69626 // set up the x and y axis objects we'll use to lay out the points
69627 _this.xaxis = {
69628 type: 'linear',
69629 range: [amin + 2 * cmin - sum, sum - amin - 2 * bmin],
69630 domain: [
69631 xDomainCenter - xDomainFinal / 2,
69632 xDomainCenter + xDomainFinal / 2
69633 ],
69634 _id: 'x'
69635 };
69636 setConvert(_this.xaxis, _this.graphDiv._fullLayout);
69637 _this.xaxis.setScale();
69638 _this.xaxis.isPtWithinRange = function(d) {
69639 return (
69640 d.a >= _this.aaxis.range[0] &&
69641 d.a <= _this.aaxis.range[1] &&
69642 d.b >= _this.baxis.range[1] &&
69643 d.b <= _this.baxis.range[0] &&
69644 d.c >= _this.caxis.range[1] &&
69645 d.c <= _this.caxis.range[0]
69646 );
69647 };
69648
69649 _this.yaxis = {
69650 type: 'linear',
69651 range: [amin, sum - bmin - cmin],
69652 domain: [
69653 yDomainCenter - yDomainFinal / 2,
69654 yDomainCenter + yDomainFinal / 2
69655 ],
69656 _id: 'y'
69657 };
69658 setConvert(_this.yaxis, _this.graphDiv._fullLayout);
69659 _this.yaxis.setScale();
69660 _this.yaxis.isPtWithinRange = function() { return true; };
69661
69662 // set up the modified axes for tick drawing
69663 var yDomain0 = _this.yaxis.domain[0];
69664
69665 // aaxis goes up the left side. Set it up as a y axis, but with
69666 // fictitious angles and domain, but then rotate and translate
69667 // it into place at the end
69668 var aaxis = _this.aaxis = extendFlat({}, ternaryLayout.aaxis, {
69669 range: [amin, sum - bmin - cmin],
69670 side: 'left',
69671 // tickangle = 'auto' means 0 anyway for a y axis, need to coerce to 0 here
69672 // so we can shift by 30.
69673 tickangle: (+ternaryLayout.aaxis.tickangle || 0) - 30,
69674 domain: [yDomain0, yDomain0 + yDomainFinal * whRatio],
69675 anchor: 'free',
69676 position: 0,
69677 _id: 'y',
69678 _length: w
69679 });
69680 setConvert(aaxis, _this.graphDiv._fullLayout);
69681 aaxis.setScale();
69682
69683 // baxis goes across the bottom (backward). We can set it up as an x axis
69684 // without any enclosing transformation.
69685 var baxis = _this.baxis = extendFlat({}, ternaryLayout.baxis, {
69686 range: [sum - amin - cmin, bmin],
69687 side: 'bottom',
69688 domain: _this.xaxis.domain,
69689 anchor: 'free',
69690 position: 0,
69691 _id: 'x',
69692 _length: w
69693 });
69694 setConvert(baxis, _this.graphDiv._fullLayout);
69695 baxis.setScale();
69696
69697 // caxis goes down the right side. Set it up as a y axis, with
69698 // post-transformation similar to aaxis
69699 var caxis = _this.caxis = extendFlat({}, ternaryLayout.caxis, {
69700 range: [sum - amin - bmin, cmin],
69701 side: 'right',
69702 tickangle: (+ternaryLayout.caxis.tickangle || 0) + 30,
69703 domain: [yDomain0, yDomain0 + yDomainFinal * whRatio],
69704 anchor: 'free',
69705 position: 0,
69706 _id: 'y',
69707 _length: w
69708 });
69709 setConvert(caxis, _this.graphDiv._fullLayout);
69710 caxis.setScale();
69711
69712 var triangleClip = 'M' + x0 + ',' + (y0 + h) + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z';
69713 _this.clipDef.select('path').attr('d', triangleClip);
69714 _this.layers.plotbg.select('path').attr('d', triangleClip);
69715
69716 var triangleClipRelative = 'M0,' + h + 'h' + w + 'l-' + (w / 2) + ',-' + h + 'Z';
69717 _this.clipDefRelative.select('path').attr('d', triangleClipRelative);
69718
69719 var plotTransform = 'translate(' + x0 + ',' + y0 + ')';
69720 _this.plotContainer.selectAll('.scatterlayer,.maplayer')
69721 .attr('transform', plotTransform);
69722
69723 _this.clipDefRelative.select('path').attr('transform', null);
69724
69725 // TODO: shift axes to accommodate linewidth*sin(30) tick mark angle
69726
69727 // TODO: there's probably an easier way to handle these translations/offsets now...
69728 var bTransform = 'translate(' + (x0 - baxis._offset) + ',' + (y0 + h) + ')';
69729
69730 _this.layers.baxis.attr('transform', bTransform);
69731 _this.layers.bgrid.attr('transform', bTransform);
69732
69733 var aTransform = 'translate(' + (x0 + w / 2) + ',' + y0 +
69734 ')rotate(30)translate(0,' + -aaxis._offset + ')';
69735 _this.layers.aaxis.attr('transform', aTransform);
69736 _this.layers.agrid.attr('transform', aTransform);
69737
69738 var cTransform = 'translate(' + (x0 + w / 2) + ',' + y0 +
69739 ')rotate(-30)translate(0,' + -caxis._offset + ')';
69740 _this.layers.caxis.attr('transform', cTransform);
69741 _this.layers.cgrid.attr('transform', cTransform);
69742
69743 _this.drawAxes(true);
69744
69745 _this.layers.aline.select('path')
69746 .attr('d', aaxis.showline ?
69747 'M' + x0 + ',' + (y0 + h) + 'l' + (w / 2) + ',-' + h : 'M0,0')
69748 .call(Color.stroke, aaxis.linecolor || '#000')
69749 .style('stroke-width', (aaxis.linewidth || 0) + 'px');
69750 _this.layers.bline.select('path')
69751 .attr('d', baxis.showline ?
69752 'M' + x0 + ',' + (y0 + h) + 'h' + w : 'M0,0')
69753 .call(Color.stroke, baxis.linecolor || '#000')
69754 .style('stroke-width', (baxis.linewidth || 0) + 'px');
69755 _this.layers.cline.select('path')
69756 .attr('d', caxis.showline ?
69757 'M' + (x0 + w / 2) + ',' + y0 + 'l' + (w / 2) + ',' + h : 'M0,0')
69758 .call(Color.stroke, caxis.linecolor || '#000')
69759 .style('stroke-width', (caxis.linewidth || 0) + 'px');
69760
69761 if(!_this.graphDiv._context.staticPlot) {
69762 _this.initInteractions();
69763 }
69764
69765 Drawing.setClipUrl(
69766 _this.layers.frontplot,
69767 _this._hasClipOnAxisFalse ? null : _this.clipId,
69768 _this.graphDiv
69769 );
69770};
69771
69772proto.drawAxes = function(doTitles) {
69773 var _this = this;
69774 var gd = _this.graphDiv;
69775 var titlesuffix = _this.id.substr(7) + 'title';
69776 var layers = _this.layers;
69777 var aaxis = _this.aaxis;
69778 var baxis = _this.baxis;
69779 var caxis = _this.caxis;
69780
69781 _this.drawAx(aaxis);
69782 _this.drawAx(baxis);
69783 _this.drawAx(caxis);
69784
69785 if(doTitles) {
69786 var apad = Math.max(aaxis.showticklabels ? aaxis.tickfont.size / 2 : 0,
69787 (caxis.showticklabels ? caxis.tickfont.size * 0.75 : 0) +
69788 (caxis.ticks === 'outside' ? caxis.ticklen * 0.87 : 0));
69789 var bpad = (baxis.showticklabels ? baxis.tickfont.size : 0) +
69790 (baxis.ticks === 'outside' ? baxis.ticklen : 0) + 3;
69791
69792 layers['a-title'] = Titles.draw(gd, 'a' + titlesuffix, {
69793 propContainer: aaxis,
69794 propName: _this.id + '.aaxis.title',
69795 placeholder: _(gd, 'Click to enter Component A title'),
69796 attributes: {
69797 x: _this.x0 + _this.w / 2,
69798 y: _this.y0 - aaxis.title.font.size / 3 - apad,
69799 'text-anchor': 'middle'
69800 }
69801 });
69802 layers['b-title'] = Titles.draw(gd, 'b' + titlesuffix, {
69803 propContainer: baxis,
69804 propName: _this.id + '.baxis.title',
69805 placeholder: _(gd, 'Click to enter Component B title'),
69806 attributes: {
69807 x: _this.x0 - bpad,
69808 y: _this.y0 + _this.h + baxis.title.font.size * 0.83 + bpad,
69809 'text-anchor': 'middle'
69810 }
69811 });
69812 layers['c-title'] = Titles.draw(gd, 'c' + titlesuffix, {
69813 propContainer: caxis,
69814 propName: _this.id + '.caxis.title',
69815 placeholder: _(gd, 'Click to enter Component C title'),
69816 attributes: {
69817 x: _this.x0 + _this.w + bpad,
69818 y: _this.y0 + _this.h + caxis.title.font.size * 0.83 + bpad,
69819 'text-anchor': 'middle'
69820 }
69821 });
69822 }
69823};
69824
69825proto.drawAx = function(ax) {
69826 var _this = this;
69827 var gd = _this.graphDiv;
69828 var axName = ax._name;
69829 var axLetter = axName.charAt(0);
69830 var axId = ax._id;
69831 var axLayer = _this.layers[axName];
69832 var counterAngle = 30;
69833
69834 var stashKey = axLetter + 'tickLayout';
69835 var newTickLayout = strTickLayout(ax);
69836 if(_this[stashKey] !== newTickLayout) {
69837 axLayer.selectAll('.' + axId + 'tick').remove();
69838 _this[stashKey] = newTickLayout;
69839 }
69840
69841 ax.setScale();
69842
69843 var vals = Axes.calcTicks(ax);
69844 var valsClipped = Axes.clipEnds(ax, vals);
69845 var transFn = Axes.makeTransFn(ax);
69846 var tickSign = Axes.getTickSigns(ax)[2];
69847
69848 var caRad = Lib.deg2rad(counterAngle);
69849 var pad = tickSign * (ax.linewidth || 1) / 2;
69850 var len = tickSign * ax.ticklen;
69851 var w = _this.w;
69852 var h = _this.h;
69853
69854 var tickPath = axLetter === 'b' ?
69855 'M0,' + pad + 'l' + (Math.sin(caRad) * len) + ',' + (Math.cos(caRad) * len) :
69856 'M' + pad + ',0l' + (Math.cos(caRad) * len) + ',' + (-Math.sin(caRad) * len);
69857
69858 var gridPath = {
69859 a: 'M0,0l' + h + ',-' + (w / 2),
69860 b: 'M0,0l-' + (w / 2) + ',-' + h,
69861 c: 'M0,0l-' + h + ',' + (w / 2)
69862 }[axLetter];
69863
69864 Axes.drawTicks(gd, ax, {
69865 vals: ax.ticks === 'inside' ? valsClipped : vals,
69866 layer: axLayer,
69867 path: tickPath,
69868 transFn: transFn,
69869 crisp: false
69870 });
69871
69872 Axes.drawGrid(gd, ax, {
69873 vals: valsClipped,
69874 layer: _this.layers[axLetter + 'grid'],
69875 path: gridPath,
69876 transFn: transFn,
69877 crisp: false
69878 });
69879
69880 Axes.drawLabels(gd, ax, {
69881 vals: vals,
69882 layer: axLayer,
69883 transFn: transFn,
69884 labelFns: Axes.makeLabelFns(ax, 0, counterAngle)
69885 });
69886};
69887
69888function strTickLayout(axLayout) {
69889 return axLayout.ticks + String(axLayout.ticklen) + String(axLayout.showticklabels);
69890}
69891
69892// hard coded paths for zoom corners
69893// uses the same sizing as cartesian, length is MINZOOM/2, width is 3px
69894var CLEN = constants.MINZOOM / 2 + 0.87;
69895var BLPATH = 'm-0.87,.5h' + CLEN + 'v3h-' + (CLEN + 5.2) +
69896 'l' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69897 'l2.6,1.5l-' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z';
69898var BRPATH = 'm0.87,.5h-' + CLEN + 'v3h' + (CLEN + 5.2) +
69899 'l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69900 'l-2.6,1.5l' + (CLEN / 2) + ',' + (CLEN * 0.87) + 'Z';
69901var TOPPATH = 'm0,1l' + (CLEN / 2) + ',' + (CLEN * 0.87) +
69902 'l2.6,-1.5l-' + (CLEN / 2 + 2.6) + ',-' + (CLEN * 0.87 + 4.5) +
69903 'l-' + (CLEN / 2 + 2.6) + ',' + (CLEN * 0.87 + 4.5) +
69904 'l2.6,1.5l' + (CLEN / 2) + ',-' + (CLEN * 0.87) + 'Z';
69905var STARTMARKER = 'm0.5,0.5h5v-2h-5v-5h-2v5h-5v2h5v5h2Z';
69906
69907// I guess this could be shared with cartesian... but for now it's separate.
69908var SHOWZOOMOUTTIP = true;
69909
69910proto.clearSelect = function() {
69911 clearSelectionsCache(this.dragOptions);
69912 clearSelect(this.dragOptions.gd);
69913};
69914
69915proto.initInteractions = function() {
69916 var _this = this;
69917 var dragger = _this.layers.plotbg.select('path').node();
69918 var gd = _this.graphDiv;
69919 var zoomLayer = gd._fullLayout._zoomlayer;
69920
69921 // use plotbg for the main interactions
69922 this.dragOptions = {
69923 element: dragger,
69924 gd: gd,
69925 plotinfo: {
69926 id: _this.id,
69927 domain: gd._fullLayout[_this.id].domain,
69928 xaxis: _this.xaxis,
69929 yaxis: _this.yaxis
69930 },
69931 subplot: _this.id,
69932 prepFn: function(e, startX, startY) {
69933 // these aren't available yet when initInteractions
69934 // is called
69935 _this.dragOptions.xaxes = [_this.xaxis];
69936 _this.dragOptions.yaxes = [_this.yaxis];
69937
69938 var dragModeNow = _this.dragOptions.dragmode = gd._fullLayout.dragmode;
69939
69940 if(freeMode(dragModeNow)) _this.dragOptions.minDrag = 1;
69941 else _this.dragOptions.minDrag = undefined;
69942
69943 if(dragModeNow === 'zoom') {
69944 _this.dragOptions.moveFn = zoomMove;
69945 _this.dragOptions.clickFn = clickZoomPan;
69946 _this.dragOptions.doneFn = zoomDone;
69947 zoomPrep(e, startX, startY);
69948 } else if(dragModeNow === 'pan') {
69949 _this.dragOptions.moveFn = plotDrag;
69950 _this.dragOptions.clickFn = clickZoomPan;
69951 _this.dragOptions.doneFn = dragDone;
69952 panPrep();
69953 _this.clearSelect(gd);
69954 } else if(rectMode(dragModeNow) || freeMode(dragModeNow)) {
69955 prepSelect(e, startX, startY, _this.dragOptions, dragModeNow);
69956 }
69957 }
69958 };
69959
69960 var x0, y0, mins0, span0, mins, lum, path0, dimmed, zb, corners;
69961
69962 function makeUpdate(_mins) {
69963 var attrs = {};
69964 attrs[_this.id + '.aaxis.min'] = _mins.a;
69965 attrs[_this.id + '.baxis.min'] = _mins.b;
69966 attrs[_this.id + '.caxis.min'] = _mins.c;
69967 return attrs;
69968 }
69969
69970 function clickZoomPan(numClicks, evt) {
69971 var clickMode = gd._fullLayout.clickmode;
69972
69973 removeZoombox(gd);
69974
69975 if(numClicks === 2) {
69976 gd.emit('plotly_doubleclick', null);
69977 Registry.call('_guiRelayout', gd, makeUpdate({a: 0, b: 0, c: 0}));
69978 }
69979
69980 if(clickMode.indexOf('select') > -1 && numClicks === 1) {
69981 selectOnClick(evt, gd, [_this.xaxis], [_this.yaxis], _this.id, _this.dragOptions);
69982 }
69983
69984 if(clickMode.indexOf('event') > -1) {
69985 Fx.click(gd, evt, _this.id);
69986 }
69987 }
69988
69989 function zoomPrep(e, startX, startY) {
69990 var dragBBox = dragger.getBoundingClientRect();
69991 x0 = startX - dragBBox.left;
69992 y0 = startY - dragBBox.top;
69993 mins0 = {
69994 a: _this.aaxis.range[0],
69995 b: _this.baxis.range[1],
69996 c: _this.caxis.range[1]
69997 };
69998 mins = mins0;
69999 span0 = _this.aaxis.range[1] - mins0.a;
70000 lum = tinycolor(_this.graphDiv._fullLayout[_this.id].bgcolor).getLuminance();
70001 path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z';
70002 dimmed = false;
70003
70004 zb = zoomLayer.append('path')
70005 .attr('class', 'zoombox')
70006 .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')')
70007 .style({
70008 'fill': lum > 0.2 ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)',
70009 'stroke-width': 0
70010 })
70011 .attr('d', path0);
70012
70013 corners = zoomLayer.append('path')
70014 .attr('class', 'zoombox-corners')
70015 .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')')
70016 .style({
70017 fill: Color.background,
70018 stroke: Color.defaultLine,
70019 'stroke-width': 1,
70020 opacity: 0
70021 })
70022 .attr('d', 'M0,0Z');
70023
70024 _this.clearSelect(gd);
70025 }
70026
70027 function getAFrac(x, y) { return 1 - (y / _this.h); }
70028 function getBFrac(x, y) { return 1 - ((x + (_this.h - y) / Math.sqrt(3)) / _this.w); }
70029 function getCFrac(x, y) { return ((x - (_this.h - y) / Math.sqrt(3)) / _this.w); }
70030
70031 function zoomMove(dx0, dy0) {
70032 var x1 = x0 + dx0;
70033 var y1 = y0 + dy0;
70034 var afrac = Math.max(0, Math.min(1, getAFrac(x0, y0), getAFrac(x1, y1)));
70035 var bfrac = Math.max(0, Math.min(1, getBFrac(x0, y0), getBFrac(x1, y1)));
70036 var cfrac = Math.max(0, Math.min(1, getCFrac(x0, y0), getCFrac(x1, y1)));
70037 var xLeft = ((afrac / 2) + cfrac) * _this.w;
70038 var xRight = (1 - (afrac / 2) - bfrac) * _this.w;
70039 var xCenter = (xLeft + xRight) / 2;
70040 var xSpan = xRight - xLeft;
70041 var yBottom = (1 - afrac) * _this.h;
70042 var yTop = yBottom - xSpan / whRatio;
70043
70044 if(xSpan < constants.MINZOOM) {
70045 mins = mins0;
70046 zb.attr('d', path0);
70047 corners.attr('d', 'M0,0Z');
70048 } else {
70049 mins = {
70050 a: mins0.a + afrac * span0,
70051 b: mins0.b + bfrac * span0,
70052 c: mins0.c + cfrac * span0
70053 };
70054 zb.attr('d', path0 + 'M' + xLeft + ',' + yBottom +
70055 'H' + xRight + 'L' + xCenter + ',' + yTop +
70056 'L' + xLeft + ',' + yBottom + 'Z');
70057 corners.attr('d', 'M' + x0 + ',' + y0 + STARTMARKER +
70058 'M' + xLeft + ',' + yBottom + BLPATH +
70059 'M' + xRight + ',' + yBottom + BRPATH +
70060 'M' + xCenter + ',' + yTop + TOPPATH);
70061 }
70062
70063 if(!dimmed) {
70064 zb.transition()
70065 .style('fill', lum > 0.2 ? 'rgba(0,0,0,0.4)' :
70066 'rgba(255,255,255,0.3)')
70067 .duration(200);
70068 corners.transition()
70069 .style('opacity', 1)
70070 .duration(200);
70071 dimmed = true;
70072 }
70073
70074 gd.emit('plotly_relayouting', makeUpdate(mins));
70075 }
70076
70077 function zoomDone() {
70078 removeZoombox(gd);
70079
70080 if(mins === mins0) return;
70081
70082 Registry.call('_guiRelayout', gd, makeUpdate(mins));
70083
70084 if(SHOWZOOMOUTTIP && gd.data && gd._context.showTips) {
70085 Lib.notifier(_(gd, 'Double-click to zoom back out'), 'long');
70086 SHOWZOOMOUTTIP = false;
70087 }
70088 }
70089
70090 function panPrep() {
70091 mins0 = {
70092 a: _this.aaxis.range[0],
70093 b: _this.baxis.range[1],
70094 c: _this.caxis.range[1]
70095 };
70096 mins = mins0;
70097 }
70098
70099 function plotDrag(dx, dy) {
70100 var dxScaled = dx / _this.xaxis._m;
70101 var dyScaled = dy / _this.yaxis._m;
70102 mins = {
70103 a: mins0.a - dyScaled,
70104 b: mins0.b + (dxScaled + dyScaled) / 2,
70105 c: mins0.c - (dxScaled - dyScaled) / 2
70106 };
70107 var minsorted = [mins.a, mins.b, mins.c].sort();
70108 var minindices = {
70109 a: minsorted.indexOf(mins.a),
70110 b: minsorted.indexOf(mins.b),
70111 c: minsorted.indexOf(mins.c)
70112 };
70113 if(minsorted[0] < 0) {
70114 if(minsorted[1] + minsorted[0] / 2 < 0) {
70115 minsorted[2] += minsorted[0] + minsorted[1];
70116 minsorted[0] = minsorted[1] = 0;
70117 } else {
70118 minsorted[2] += minsorted[0] / 2;
70119 minsorted[1] += minsorted[0] / 2;
70120 minsorted[0] = 0;
70121 }
70122 mins = {
70123 a: minsorted[minindices.a],
70124 b: minsorted[minindices.b],
70125 c: minsorted[minindices.c]
70126 };
70127 dy = (mins0.a - mins.a) * _this.yaxis._m;
70128 dx = (mins0.c - mins.c - mins0.b + mins.b) * _this.xaxis._m;
70129 }
70130
70131 // move the data (translate, don't redraw)
70132 var plotTransform = 'translate(' + (_this.x0 + dx) + ',' + (_this.y0 + dy) + ')';
70133 _this.plotContainer.selectAll('.scatterlayer,.maplayer')
70134 .attr('transform', plotTransform);
70135
70136 var plotTransform2 = 'translate(' + -dx + ',' + -dy + ')';
70137 _this.clipDefRelative.select('path').attr('transform', plotTransform2);
70138
70139 // move the ticks
70140 _this.aaxis.range = [mins.a, _this.sum - mins.b - mins.c];
70141 _this.baxis.range = [_this.sum - mins.a - mins.c, mins.b];
70142 _this.caxis.range = [_this.sum - mins.a - mins.b, mins.c];
70143
70144 _this.drawAxes(false);
70145
70146 if(_this._hasClipOnAxisFalse) {
70147 _this.plotContainer
70148 .select('.scatterlayer').selectAll('.trace')
70149 .call(Drawing.hideOutsideRangePoints, _this);
70150 }
70151
70152 gd.emit('plotly_relayouting', makeUpdate(mins));
70153 }
70154
70155 function dragDone() {
70156 Registry.call('_guiRelayout', gd, makeUpdate(mins));
70157 }
70158
70159 // finally, set up hover and click
70160 // these event handlers must already be set before dragElement.init
70161 // so it can stash them and override them.
70162 dragger.onmousemove = function(evt) {
70163 Fx.hover(gd, evt, _this.id);
70164 gd._fullLayout._lasthover = dragger;
70165 gd._fullLayout._hoversubplot = _this.id;
70166 };
70167
70168 dragger.onmouseout = function(evt) {
70169 if(gd._dragging) return;
70170
70171 dragElement.unhover(gd, evt);
70172 };
70173
70174 dragElement.init(this.dragOptions);
70175};
70176
70177function removeZoombox(gd) {
70178 d3.select(gd)
70179 .selectAll('.zoombox,.js-zoombox-backdrop,.js-zoombox-menu,.zoombox-corners')
70180 .remove();
70181}
70182
70183},{"../../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){
70184/**
70185* Copyright 2012-2020, Plotly, Inc.
70186* All rights reserved.
70187*
70188* This source code is licensed under the MIT license found in the
70189* LICENSE file in the root directory of this source tree.
70190*/
70191
70192'use strict';
70193
70194var Loggers = _dereq_('./lib/loggers');
70195var noop = _dereq_('./lib/noop');
70196var pushUnique = _dereq_('./lib/push_unique');
70197var isPlainObject = _dereq_('./lib/is_plain_object');
70198var addStyleRule = _dereq_('./lib/dom').addStyleRule;
70199var ExtendModule = _dereq_('./lib/extend');
70200
70201var basePlotAttributes = _dereq_('./plots/attributes');
70202var baseLayoutAttributes = _dereq_('./plots/layout_attributes');
70203
70204var extendFlat = ExtendModule.extendFlat;
70205var extendDeepAll = ExtendModule.extendDeepAll;
70206
70207exports.modules = {};
70208exports.allCategories = {};
70209exports.allTypes = [];
70210exports.subplotsRegistry = {};
70211exports.transformsRegistry = {};
70212exports.componentsRegistry = {};
70213exports.layoutArrayContainers = [];
70214exports.layoutArrayRegexes = [];
70215exports.traceLayoutAttributes = {};
70216exports.localeRegistry = {};
70217exports.apiMethodRegistry = {};
70218exports.collectableSubplotTypes = null;
70219
70220/**
70221 * Top-level register routine, exported as Plotly.register
70222 *
70223 * @param {object array or array of objects} _modules :
70224 * module object or list of module object to register.
70225 *
70226 * A valid `moduleType: 'trace'` module has fields:
70227 * - name {string} : the trace type
70228 * - categories {array} : categories associated with this trace type,
70229 * tested with Register.traceIs()
70230 * - meta {object} : meta info (mostly for plot-schema)
70231 *
70232 * A valid `moduleType: 'locale'` module has fields:
70233 * - name {string} : the locale name. Should be a 2-digit language string ('en', 'de')
70234 * optionally with a country/region code ('en-GB', 'de-CH'). If a country
70235 * code is used but the base language locale has not yet been supplied,
70236 * we will use this locale for the base as well.
70237 * - dictionary {object} : the dictionary mapping input strings to localized strings
70238 * generally the keys should be the literal input strings, but
70239 * if default translations are provided you can use any string as a key.
70240 * - format {object} : a `d3.locale` format specifier for this locale
70241 * any omitted keys we'll fall back on en-US.
70242 *
70243 * A valid `moduleType: 'transform'` module has fields:
70244 * - name {string} : transform name
70245 * - transform {function} : default-level transform function
70246 * - calcTransform {function} : calc-level transform function
70247 * - attributes {object} : transform attributes declarations
70248 * - supplyDefaults {function} : attributes default-supply function
70249 *
70250 * A valid `moduleType: 'component'` module has fields:
70251 * - name {string} : the component name, used it with Register.getComponentMethod()
70252 * to employ component method.
70253 *
70254 * A valid `moduleType: 'apiMethod'` module has fields:
70255 * - name {string} : the api method name.
70256 * - fn {function} : the api method called with Register.call();
70257 *
70258 */
70259exports.register = function register(_modules) {
70260 exports.collectableSubplotTypes = null;
70261
70262 if(!_modules) {
70263 throw new Error('No argument passed to Plotly.register.');
70264 } else if(_modules && !Array.isArray(_modules)) {
70265 _modules = [_modules];
70266 }
70267
70268 for(var i = 0; i < _modules.length; i++) {
70269 var newModule = _modules[i];
70270
70271 if(!newModule) {
70272 throw new Error('Invalid module was attempted to be registered!');
70273 }
70274
70275 switch(newModule.moduleType) {
70276 case 'trace':
70277 registerTraceModule(newModule);
70278 break;
70279 case 'transform':
70280 registerTransformModule(newModule);
70281 break;
70282 case 'component':
70283 registerComponentModule(newModule);
70284 break;
70285 case 'locale':
70286 registerLocale(newModule);
70287 break;
70288 case 'apiMethod':
70289 var name = newModule.name;
70290 exports.apiMethodRegistry[name] = newModule.fn;
70291 break;
70292 default:
70293 throw new Error('Invalid module was attempted to be registered!');
70294 }
70295 }
70296};
70297
70298/**
70299 * Get registered module using trace object or trace type
70300 *
70301 * @param {object||string} trace
70302 * trace object with prop 'type' or trace type as a string
70303 * @return {object}
70304 * module object corresponding to trace type
70305 */
70306exports.getModule = function(trace) {
70307 var _module = exports.modules[getTraceType(trace)];
70308 if(!_module) return false;
70309 return _module._module;
70310};
70311
70312/**
70313 * Determine if this trace type is in a given category
70314 *
70315 * @param {object||string} traceType
70316 * a trace (object) or trace type (string)
70317 * @param {string} category
70318 * category in question
70319 * @return {boolean}
70320 */
70321exports.traceIs = function(traceType, category) {
70322 traceType = getTraceType(traceType);
70323
70324 // old Chart Studio Cloud workspace hack, nothing to see here
70325 if(traceType === 'various') return false;
70326
70327 var _module = exports.modules[traceType];
70328
70329 if(!_module) {
70330 if(traceType && traceType !== 'area') {
70331 Loggers.log('Unrecognized trace type ' + traceType + '.');
70332 }
70333
70334 _module = exports.modules[basePlotAttributes.type.dflt];
70335 }
70336
70337 return !!_module.categories[category];
70338};
70339
70340/**
70341 * Determine if this trace has a transform of the given type and return
70342 * array of matching indices.
70343 *
70344 * @param {object} data
70345 * a trace object (member of data or fullData)
70346 * @param {string} type
70347 * type of trace to test
70348 * @return {array}
70349 * array of matching indices. If none found, returns []
70350 */
70351exports.getTransformIndices = function(data, type) {
70352 var indices = [];
70353 var transforms = data.transforms || [];
70354 for(var i = 0; i < transforms.length; i++) {
70355 if(transforms[i].type === type) {
70356 indices.push(i);
70357 }
70358 }
70359 return indices;
70360};
70361
70362/**
70363 * Determine if this trace has a transform of the given type
70364 *
70365 * @param {object} data
70366 * a trace object (member of data or fullData)
70367 * @param {string} type
70368 * type of trace to test
70369 * @return {boolean}
70370 */
70371exports.hasTransform = function(data, type) {
70372 var transforms = data.transforms || [];
70373 for(var i = 0; i < transforms.length; i++) {
70374 if(transforms[i].type === type) {
70375 return true;
70376 }
70377 }
70378 return false;
70379};
70380
70381/**
70382 * Retrieve component module method. Falls back on noop if either the
70383 * module or the method is missing, so the result can always be safely called
70384 *
70385 * @param {string} name
70386 * name of component (as declared in component module)
70387 * @param {string} method
70388 * name of component module method
70389 * @return {function}
70390 */
70391exports.getComponentMethod = function(name, method) {
70392 var _module = exports.componentsRegistry[name];
70393
70394 if(!_module) return noop;
70395 return _module[method] || noop;
70396};
70397
70398/**
70399 * Call registered api method.
70400 *
70401 * @param {string} name : api method name
70402 * @param {...array} args : arguments passed to api method
70403 * @return {any} : returns api method output
70404 */
70405exports.call = function() {
70406 var name = arguments[0];
70407 var args = [].slice.call(arguments, 1);
70408 return exports.apiMethodRegistry[name].apply(null, args);
70409};
70410
70411function registerTraceModule(_module) {
70412 var thisType = _module.name;
70413 var categoriesIn = _module.categories;
70414 var meta = _module.meta;
70415
70416 if(exports.modules[thisType]) {
70417 Loggers.log('Type ' + thisType + ' already registered');
70418 return;
70419 }
70420
70421 if(!exports.subplotsRegistry[_module.basePlotModule.name]) {
70422 registerSubplot(_module.basePlotModule);
70423 }
70424
70425 var categoryObj = {};
70426 for(var i = 0; i < categoriesIn.length; i++) {
70427 categoryObj[categoriesIn[i]] = true;
70428 exports.allCategories[categoriesIn[i]] = true;
70429 }
70430
70431 exports.modules[thisType] = {
70432 _module: _module,
70433 categories: categoryObj
70434 };
70435
70436 if(meta && Object.keys(meta).length) {
70437 exports.modules[thisType].meta = meta;
70438 }
70439
70440 exports.allTypes.push(thisType);
70441
70442 for(var componentName in exports.componentsRegistry) {
70443 mergeComponentAttrsToTrace(componentName, thisType);
70444 }
70445
70446 /*
70447 * Collect all trace layout attributes in one place for easier lookup later
70448 * but don't merge them into the base schema as it would confuse the docs
70449 * (at least after https://github.com/plotly/documentation/issues/202 gets done!)
70450 */
70451 if(_module.layoutAttributes) {
70452 extendFlat(exports.traceLayoutAttributes, _module.layoutAttributes);
70453 }
70454
70455 var basePlotModule = _module.basePlotModule;
70456 var bpmName = basePlotModule.name;
70457
70458 // add mapbox-gl CSS here to avoid console warning on instantiation
70459 if(bpmName === 'mapbox') {
70460 var styleRules = basePlotModule.constants.styleRules;
70461 for(var k in styleRules) {
70462 addStyleRule('.js-plotly-plot .plotly .mapboxgl-' + k, styleRules[k]);
70463 }
70464 }
70465
70466 // if `plotly-geo-assets.js` is not included,
70467 // add `PlotlyGeoAssets` global to stash references to all fetched
70468 // topojson / geojson data
70469 if((bpmName === 'geo' || bpmName === 'mapbox') &&
70470 (typeof window !== undefined && window.PlotlyGeoAssets === undefined)
70471 ) {
70472 window.PlotlyGeoAssets = {topojson: {}};
70473 }
70474}
70475
70476function registerSubplot(_module) {
70477 var plotType = _module.name;
70478
70479 if(exports.subplotsRegistry[plotType]) {
70480 Loggers.log('Plot type ' + plotType + ' already registered.');
70481 return;
70482 }
70483
70484 // relayout array handling will look for component module methods with this
70485 // name and won't find them because this is a subplot module... but that
70486 // should be fine, it will just fall back on redrawing the plot.
70487 findArrayRegexps(_module);
70488
70489 // not sure what's best for the 'cartesian' type at this point
70490 exports.subplotsRegistry[plotType] = _module;
70491
70492 for(var componentName in exports.componentsRegistry) {
70493 mergeComponentAttrsToSubplot(componentName, _module.name);
70494 }
70495}
70496
70497function registerComponentModule(_module) {
70498 if(typeof _module.name !== 'string') {
70499 throw new Error('Component module *name* must be a string.');
70500 }
70501
70502 var name = _module.name;
70503 exports.componentsRegistry[name] = _module;
70504
70505 if(_module.layoutAttributes) {
70506 if(_module.layoutAttributes._isLinkedToArray) {
70507 pushUnique(exports.layoutArrayContainers, name);
70508 }
70509 findArrayRegexps(_module);
70510 }
70511
70512 for(var traceType in exports.modules) {
70513 mergeComponentAttrsToTrace(name, traceType);
70514 }
70515
70516 for(var subplotName in exports.subplotsRegistry) {
70517 mergeComponentAttrsToSubplot(name, subplotName);
70518 }
70519
70520 for(var transformType in exports.transformsRegistry) {
70521 mergeComponentAttrsToTransform(name, transformType);
70522 }
70523
70524 if(_module.schema && _module.schema.layout) {
70525 extendDeepAll(baseLayoutAttributes, _module.schema.layout);
70526 }
70527}
70528
70529function registerTransformModule(_module) {
70530 if(typeof _module.name !== 'string') {
70531 throw new Error('Transform module *name* must be a string.');
70532 }
70533
70534 var prefix = 'Transform module ' + _module.name;
70535 var hasTransform = typeof _module.transform === 'function';
70536 var hasCalcTransform = typeof _module.calcTransform === 'function';
70537
70538 if(!hasTransform && !hasCalcTransform) {
70539 throw new Error(prefix + ' is missing a *transform* or *calcTransform* method.');
70540 }
70541 if(hasTransform && hasCalcTransform) {
70542 Loggers.log([
70543 prefix + ' has both a *transform* and *calcTransform* methods.',
70544 'Please note that all *transform* methods are executed',
70545 'before all *calcTransform* methods.'
70546 ].join(' '));
70547 }
70548 if(!isPlainObject(_module.attributes)) {
70549 Loggers.log(prefix + ' registered without an *attributes* object.');
70550 }
70551 if(typeof _module.supplyDefaults !== 'function') {
70552 Loggers.log(prefix + ' registered without a *supplyDefaults* method.');
70553 }
70554
70555 exports.transformsRegistry[_module.name] = _module;
70556
70557 for(var componentName in exports.componentsRegistry) {
70558 mergeComponentAttrsToTransform(componentName, _module.name);
70559 }
70560}
70561
70562function registerLocale(_module) {
70563 var locale = _module.name;
70564 var baseLocale = locale.split('-')[0];
70565
70566 var newDict = _module.dictionary;
70567 var newFormat = _module.format;
70568 var hasDict = newDict && Object.keys(newDict).length;
70569 var hasFormat = newFormat && Object.keys(newFormat).length;
70570
70571 var locales = exports.localeRegistry;
70572
70573 var localeObj = locales[locale];
70574 if(!localeObj) locales[locale] = localeObj = {};
70575
70576 // Should we use this dict for the base locale?
70577 // In case we're overwriting a previous dict for this locale, check
70578 // whether the base matches the full locale dict now. If we're not
70579 // overwriting, locales[locale] is undefined so this just checks if
70580 // baseLocale already had a dict or not.
70581 // Same logic for dateFormats
70582 if(baseLocale !== locale) {
70583 var baseLocaleObj = locales[baseLocale];
70584 if(!baseLocaleObj) locales[baseLocale] = baseLocaleObj = {};
70585
70586 if(hasDict && baseLocaleObj.dictionary === localeObj.dictionary) {
70587 baseLocaleObj.dictionary = newDict;
70588 }
70589 if(hasFormat && baseLocaleObj.format === localeObj.format) {
70590 baseLocaleObj.format = newFormat;
70591 }
70592 }
70593
70594 if(hasDict) localeObj.dictionary = newDict;
70595 if(hasFormat) localeObj.format = newFormat;
70596}
70597
70598function findArrayRegexps(_module) {
70599 if(_module.layoutAttributes) {
70600 var arrayAttrRegexps = _module.layoutAttributes._arrayAttrRegexps;
70601 if(arrayAttrRegexps) {
70602 for(var i = 0; i < arrayAttrRegexps.length; i++) {
70603 pushUnique(exports.layoutArrayRegexes, arrayAttrRegexps[i]);
70604 }
70605 }
70606 }
70607}
70608
70609function mergeComponentAttrsToTrace(componentName, traceType) {
70610 var componentSchema = exports.componentsRegistry[componentName].schema;
70611 if(!componentSchema || !componentSchema.traces) return;
70612
70613 var traceAttrs = componentSchema.traces[traceType];
70614 if(traceAttrs) {
70615 extendDeepAll(exports.modules[traceType]._module.attributes, traceAttrs);
70616 }
70617}
70618
70619function mergeComponentAttrsToTransform(componentName, transformType) {
70620 var componentSchema = exports.componentsRegistry[componentName].schema;
70621 if(!componentSchema || !componentSchema.transforms) return;
70622
70623 var transformAttrs = componentSchema.transforms[transformType];
70624 if(transformAttrs) {
70625 extendDeepAll(exports.transformsRegistry[transformType].attributes, transformAttrs);
70626 }
70627}
70628
70629function mergeComponentAttrsToSubplot(componentName, subplotName) {
70630 var componentSchema = exports.componentsRegistry[componentName].schema;
70631 if(!componentSchema || !componentSchema.subplots) return;
70632
70633 var subplotModule = exports.subplotsRegistry[subplotName];
70634 var subplotAttrs = subplotModule.layoutAttributes;
70635 var subplotAttr = subplotModule.attr === 'subplot' ? subplotModule.name : subplotModule.attr;
70636 if(Array.isArray(subplotAttr)) subplotAttr = subplotAttr[0];
70637
70638 var componentLayoutAttrs = componentSchema.subplots[subplotAttr];
70639 if(subplotAttrs && componentLayoutAttrs) {
70640 extendDeepAll(subplotAttrs, componentLayoutAttrs);
70641 }
70642}
70643
70644function getTraceType(traceType) {
70645 if(typeof traceType === 'object') traceType = traceType.type;
70646 return traceType;
70647}
70648
70649},{"./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){
70650/**
70651* Copyright 2012-2020, Plotly, Inc.
70652* All rights reserved.
70653*
70654* This source code is licensed under the MIT license found in the
70655* LICENSE file in the root directory of this source tree.
70656*/
70657
70658'use strict';
70659
70660var Registry = _dereq_('../registry');
70661var Lib = _dereq_('../lib');
70662
70663var extendFlat = Lib.extendFlat;
70664var extendDeep = Lib.extendDeep;
70665
70666// Put default plotTile layouts here
70667function cloneLayoutOverride(tileClass) {
70668 var override;
70669
70670 switch(tileClass) {
70671 case 'themes__thumb':
70672 override = {
70673 autosize: true,
70674 width: 150,
70675 height: 150,
70676 title: {text: ''},
70677 showlegend: false,
70678 margin: {l: 5, r: 5, t: 5, b: 5, pad: 0},
70679 annotations: []
70680 };
70681 break;
70682
70683 case 'thumbnail':
70684 override = {
70685 title: {text: ''},
70686 hidesources: true,
70687 showlegend: false,
70688 borderwidth: 0,
70689 bordercolor: '',
70690 margin: {l: 1, r: 1, t: 1, b: 1, pad: 0},
70691 annotations: []
70692 };
70693 break;
70694
70695 default:
70696 override = {};
70697 }
70698
70699
70700 return override;
70701}
70702
70703function keyIsAxis(keyName) {
70704 var types = ['xaxis', 'yaxis', 'zaxis'];
70705 return (types.indexOf(keyName.slice(0, 5)) > -1);
70706}
70707
70708
70709module.exports = function clonePlot(graphObj, options) {
70710 // Polar plot compatibility
70711 if(graphObj.framework && graphObj.framework.isPolar) {
70712 graphObj = graphObj.framework.getConfig();
70713 }
70714
70715 var i;
70716 var oldData = graphObj.data;
70717 var oldLayout = graphObj.layout;
70718 var newData = extendDeep([], oldData);
70719 var newLayout = extendDeep({}, oldLayout, cloneLayoutOverride(options.tileClass));
70720 var context = graphObj._context || {};
70721
70722 if(options.width) newLayout.width = options.width;
70723 if(options.height) newLayout.height = options.height;
70724
70725 if(options.tileClass === 'thumbnail' || options.tileClass === 'themes__thumb') {
70726 // kill annotations
70727 newLayout.annotations = [];
70728 var keys = Object.keys(newLayout);
70729
70730 for(i = 0; i < keys.length; i++) {
70731 if(keyIsAxis(keys[i])) {
70732 newLayout[keys[i]].title = {text: ''};
70733 }
70734 }
70735
70736 // kill colorbar and pie labels
70737 for(i = 0; i < newData.length; i++) {
70738 var trace = newData[i];
70739 trace.showscale = false;
70740 if(trace.marker) trace.marker.showscale = false;
70741 if(Registry.traceIs(trace, 'pie-like')) trace.textposition = 'none';
70742 }
70743 }
70744
70745 if(Array.isArray(options.annotations)) {
70746 for(i = 0; i < options.annotations.length; i++) {
70747 newLayout.annotations.push(options.annotations[i]);
70748 }
70749 }
70750
70751 // TODO: does this scene modification really belong here?
70752 // If we still need it, can it move into the gl3d module?
70753 var sceneIds = Object.keys(newLayout).filter(function(key) {
70754 return key.match(/^scene\d*$/);
70755 });
70756 if(sceneIds.length) {
70757 var axesImageOverride = {};
70758 if(options.tileClass === 'thumbnail') {
70759 axesImageOverride = {
70760 title: {text: ''},
70761 showaxeslabels: false,
70762 showticklabels: false,
70763 linetickenable: false
70764 };
70765 }
70766 for(i = 0; i < sceneIds.length; i++) {
70767 var scene = newLayout[sceneIds[i]];
70768
70769 if(!scene.xaxis) {
70770 scene.xaxis = {};
70771 }
70772
70773 if(!scene.yaxis) {
70774 scene.yaxis = {};
70775 }
70776
70777 if(!scene.zaxis) {
70778 scene.zaxis = {};
70779 }
70780
70781 extendFlat(scene.xaxis, axesImageOverride);
70782 extendFlat(scene.yaxis, axesImageOverride);
70783 extendFlat(scene.zaxis, axesImageOverride);
70784
70785 // TODO what does this do?
70786 scene._scene = null;
70787 }
70788 }
70789
70790 var gd = document.createElement('div');
70791 if(options.tileClass) gd.className = options.tileClass;
70792
70793 var plotTile = {
70794 gd: gd,
70795 td: gd, // for external (image server) compatibility
70796 layout: newLayout,
70797 data: newData,
70798 config: {
70799 staticPlot: (options.staticPlot === undefined) ?
70800 true :
70801 options.staticPlot,
70802 plotGlPixelRatio: (options.plotGlPixelRatio === undefined) ?
70803 2 :
70804 options.plotGlPixelRatio,
70805 displaylogo: options.displaylogo || false,
70806 showLink: options.showLink || false,
70807 showTips: options.showTips || false,
70808 mapboxAccessToken: context.mapboxAccessToken
70809 }
70810 };
70811
70812 if(options.setBackground !== 'transparent') {
70813 plotTile.config.setBackground = options.setBackground || 'opaque';
70814 }
70815
70816 // attaching the default Layout the gd, so you can grab it later
70817 plotTile.gd.defaultLayout = cloneLayoutOverride(options.tileClass);
70818
70819 return plotTile;
70820};
70821
70822},{"../lib":178,"../registry":269}],271:[function(_dereq_,module,exports){
70823/**
70824* Copyright 2012-2020, Plotly, Inc.
70825* All rights reserved.
70826*
70827* This source code is licensed under the MIT license found in the
70828* LICENSE file in the root directory of this source tree.
70829*/
70830
70831'use strict';
70832
70833var Lib = _dereq_('../lib');
70834
70835var toImage = _dereq_('../plot_api/to_image');
70836
70837var fileSaver = _dereq_('./filesaver');
70838var helpers = _dereq_('./helpers');
70839
70840/**
70841 * Plotly.downloadImage
70842 *
70843 * @param {object | string | HTML div} gd
70844 * can either be a data/layout/config object
70845 * or an existing graph <div>
70846 * or an id to an existing graph <div>
70847 * @param {object} opts (see Plotly.toImage in ../plot_api/to_image)
70848 * @return {promise}
70849 */
70850function downloadImage(gd, opts) {
70851 var _gd;
70852 if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd);
70853
70854 opts = opts || {};
70855 opts.format = opts.format || 'png';
70856 opts.imageDataOnly = true;
70857
70858 return new Promise(function(resolve, reject) {
70859 if(_gd && _gd._snapshotInProgress) {
70860 reject(new Error('Snapshotting already in progress.'));
70861 }
70862
70863 // see comments within svgtoimg for additional
70864 // discussion of problems with IE
70865 // can now draw to canvas, but CORS tainted canvas
70866 // does not allow toDataURL
70867 // svg format will work though
70868 if(Lib.isIE() && opts.format !== 'svg') {
70869 reject(new Error(helpers.MSG_IE_BAD_FORMAT));
70870 }
70871
70872 if(_gd) _gd._snapshotInProgress = true;
70873 var promise = toImage(gd, opts);
70874
70875 var filename = opts.filename || gd.fn || 'newplot';
70876 filename += '.' + opts.format.replace('-', '.');
70877
70878 promise.then(function(result) {
70879 if(_gd) _gd._snapshotInProgress = false;
70880 return fileSaver(result, filename, opts.format);
70881 }).then(function(name) {
70882 resolve(name);
70883 }).catch(function(err) {
70884 if(_gd) _gd._snapshotInProgress = false;
70885 reject(err);
70886 });
70887 });
70888}
70889
70890module.exports = downloadImage;
70891
70892},{"../lib":178,"../plot_api/to_image":215,"./filesaver":272,"./helpers":273}],272:[function(_dereq_,module,exports){
70893/**
70894* Copyright 2012-2020, Plotly, Inc.
70895* All rights reserved.
70896*
70897* This source code is licensed under the MIT license found in the
70898* LICENSE file in the root directory of this source tree.
70899*/
70900
70901'use strict';
70902
70903var Lib = _dereq_('../lib');
70904var helpers = _dereq_('./helpers');
70905
70906/*
70907* substantial portions of this code from FileSaver.js
70908* https://github.com/eligrey/FileSaver.js
70909* License: https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
70910* FileSaver.js
70911* A saveAs() FileSaver implementation.
70912* 1.1.20160328
70913*
70914* By Eli Grey, http://eligrey.com
70915* License: MIT
70916* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
70917*/
70918function fileSaver(url, name, format) {
70919 var saveLink = document.createElement('a');
70920 var canUseSaveLink = 'download' in saveLink;
70921
70922 var promise = new Promise(function(resolve, reject) {
70923 var blob;
70924 var objectUrl;
70925
70926 if(Lib.isIE9orBelow()) {
70927 reject(new Error('IE < 10 unsupported'));
70928 }
70929
70930 // Safari doesn't allow downloading of blob urls
70931 if(Lib.isSafari()) {
70932 var prefix = format === 'svg' ? ',' : ';base64,';
70933 helpers.octetStream(prefix + encodeURIComponent(url));
70934 return resolve(name);
70935 }
70936
70937 // IE 10+ (native saveAs)
70938 if(Lib.isIE()) {
70939 // At this point we are only dealing with a decoded SVG as
70940 // a data URL (since IE only supports SVG)
70941 blob = helpers.createBlob(url, 'svg');
70942 window.navigator.msSaveBlob(blob, name);
70943 blob = null;
70944 return resolve(name);
70945 }
70946
70947 if(canUseSaveLink) {
70948 blob = helpers.createBlob(url, format);
70949 objectUrl = helpers.createObjectURL(blob);
70950
70951 saveLink.href = objectUrl;
70952 saveLink.download = name;
70953 document.body.appendChild(saveLink);
70954 saveLink.click();
70955
70956 document.body.removeChild(saveLink);
70957 helpers.revokeObjectURL(objectUrl);
70958 blob = null;
70959
70960 return resolve(name);
70961 }
70962
70963 reject(new Error('download error'));
70964 });
70965
70966 return promise;
70967}
70968
70969
70970module.exports = fileSaver;
70971
70972},{"../lib":178,"./helpers":273}],273:[function(_dereq_,module,exports){
70973/**
70974* Copyright 2012-2020, Plotly, Inc.
70975* All rights reserved.
70976*
70977* This source code is licensed under the MIT license found in the
70978* LICENSE file in the root directory of this source tree.
70979*/
70980
70981'use strict';
70982
70983var Registry = _dereq_('../registry');
70984
70985exports.getDelay = function(fullLayout) {
70986 if(!fullLayout._has) return 0;
70987
70988 return (
70989 fullLayout._has('gl3d') ||
70990 fullLayout._has('gl2d') ||
70991 fullLayout._has('mapbox')
70992 ) ? 500 : 0;
70993};
70994
70995exports.getRedrawFunc = function(gd) {
70996 return function() {
70997 var fullLayout = gd._fullLayout || {};
70998 var hasPolar = fullLayout._has && fullLayout._has('polar');
70999 var hasLegacyPolar = !hasPolar && gd.data && gd.data[0] && gd.data[0].r;
71000
71001 if(!hasLegacyPolar) {
71002 Registry.getComponentMethod('colorbar', 'draw')(gd);
71003 }
71004 };
71005};
71006
71007exports.encodeSVG = function(svg) {
71008 return 'data:image/svg+xml,' + encodeURIComponent(svg);
71009};
71010
71011exports.encodeJSON = function(json) {
71012 return 'data:application/json,' + encodeURIComponent(json);
71013};
71014
71015var DOM_URL = window.URL || window.webkitURL;
71016
71017exports.createObjectURL = function(blob) {
71018 return DOM_URL.createObjectURL(blob);
71019};
71020
71021exports.revokeObjectURL = function(url) {
71022 return DOM_URL.revokeObjectURL(url);
71023};
71024
71025exports.createBlob = function(url, format) {
71026 if(format === 'svg') {
71027 return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'});
71028 } else if(format === 'full-json') {
71029 return new window.Blob([url], {type: 'application/json;charset=utf-8'});
71030 } else {
71031 var binary = fixBinary(window.atob(url));
71032 return new window.Blob([binary], {type: 'image/' + format});
71033 }
71034};
71035
71036exports.octetStream = function(s) {
71037 document.location.href = 'data:application/octet-stream' + s;
71038};
71039
71040// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752
71041function fixBinary(b) {
71042 var len = b.length;
71043 var buf = new ArrayBuffer(len);
71044 var arr = new Uint8Array(buf);
71045 for(var i = 0; i < len; i++) {
71046 arr[i] = b.charCodeAt(i);
71047 }
71048 return buf;
71049}
71050
71051exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/;
71052
71053exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.';
71054
71055},{"../registry":269}],274:[function(_dereq_,module,exports){
71056/**
71057* Copyright 2012-2020, Plotly, Inc.
71058* All rights reserved.
71059*
71060* This source code is licensed under the MIT license found in the
71061* LICENSE file in the root directory of this source tree.
71062*/
71063
71064
71065'use strict';
71066
71067var helpers = _dereq_('./helpers');
71068
71069var Snapshot = {
71070 getDelay: helpers.getDelay,
71071 getRedrawFunc: helpers.getRedrawFunc,
71072 clone: _dereq_('./cloneplot'),
71073 toSVG: _dereq_('./tosvg'),
71074 svgToImg: _dereq_('./svgtoimg'),
71075 toImage: _dereq_('./toimage'),
71076 downloadImage: _dereq_('./download')
71077};
71078
71079module.exports = Snapshot;
71080
71081},{"./cloneplot":270,"./download":271,"./helpers":273,"./svgtoimg":275,"./toimage":276,"./tosvg":277}],275:[function(_dereq_,module,exports){
71082/**
71083* Copyright 2012-2020, Plotly, Inc.
71084* All rights reserved.
71085*
71086* This source code is licensed under the MIT license found in the
71087* LICENSE file in the root directory of this source tree.
71088*/
71089
71090'use strict';
71091
71092var Lib = _dereq_('../lib');
71093var EventEmitter = _dereq_('events').EventEmitter;
71094
71095var helpers = _dereq_('./helpers');
71096
71097function svgToImg(opts) {
71098 var ev = opts.emitter || new EventEmitter();
71099
71100 var promise = new Promise(function(resolve, reject) {
71101 var Image = window.Image;
71102 var svg = opts.svg;
71103 var format = opts.format || 'png';
71104
71105 // IE only support svg
71106 if(Lib.isIE() && format !== 'svg') {
71107 var ieSvgError = new Error(helpers.MSG_IE_BAD_FORMAT);
71108 reject(ieSvgError);
71109 // eventually remove the ev
71110 // in favor of promises
71111 if(!opts.promise) {
71112 return ev.emit('error', ieSvgError);
71113 } else {
71114 return promise;
71115 }
71116 }
71117
71118 var canvas = opts.canvas;
71119 var scale = opts.scale || 1;
71120 var w0 = opts.width || 300;
71121 var h0 = opts.height || 150;
71122 var w1 = scale * w0;
71123 var h1 = scale * h0;
71124
71125 var ctx = canvas.getContext('2d');
71126 var img = new Image();
71127 var svgBlob, url;
71128
71129 if(format === 'svg' || Lib.isIE9orBelow() || Lib.isSafari()) {
71130 url = helpers.encodeSVG(svg);
71131 } else {
71132 svgBlob = helpers.createBlob(svg, 'svg');
71133 url = helpers.createObjectURL(svgBlob);
71134 }
71135
71136 canvas.width = w1;
71137 canvas.height = h1;
71138
71139 img.onload = function() {
71140 var imgData;
71141
71142 svgBlob = null;
71143 helpers.revokeObjectURL(url);
71144
71145 // don't need to draw to canvas if svg
71146 // save some time and also avoid failure on IE
71147 if(format !== 'svg') {
71148 ctx.drawImage(img, 0, 0, w1, h1);
71149 }
71150
71151 switch(format) {
71152 case 'jpeg':
71153 imgData = canvas.toDataURL('image/jpeg');
71154 break;
71155 case 'png':
71156 imgData = canvas.toDataURL('image/png');
71157 break;
71158 case 'webp':
71159 imgData = canvas.toDataURL('image/webp');
71160 break;
71161 case 'svg':
71162 imgData = url;
71163 break;
71164 default:
71165 var errorMsg = 'Image format is not jpeg, png, svg or webp.';
71166 reject(new Error(errorMsg));
71167 // eventually remove the ev
71168 // in favor of promises
71169 if(!opts.promise) {
71170 return ev.emit('error', errorMsg);
71171 }
71172 }
71173 resolve(imgData);
71174 // eventually remove the ev
71175 // in favor of promises
71176 if(!opts.promise) {
71177 ev.emit('success', imgData);
71178 }
71179 };
71180
71181 img.onerror = function(err) {
71182 svgBlob = null;
71183 helpers.revokeObjectURL(url);
71184
71185 reject(err);
71186 // eventually remove the ev
71187 // in favor of promises
71188 if(!opts.promise) {
71189 return ev.emit('error', err);
71190 }
71191 };
71192
71193 img.src = url;
71194 });
71195
71196 // temporary for backward compatibility
71197 // move to only Promise in 2.0.0
71198 // and eliminate the EventEmitter
71199 if(opts.promise) {
71200 return promise;
71201 }
71202
71203 return ev;
71204}
71205
71206module.exports = svgToImg;
71207
71208},{"../lib":178,"./helpers":273,"events":15}],276:[function(_dereq_,module,exports){
71209/**
71210* Copyright 2012-2020, Plotly, Inc.
71211* All rights reserved.
71212*
71213* This source code is licensed under the MIT license found in the
71214* LICENSE file in the root directory of this source tree.
71215*/
71216
71217'use strict';
71218
71219var EventEmitter = _dereq_('events').EventEmitter;
71220
71221var Registry = _dereq_('../registry');
71222var Lib = _dereq_('../lib');
71223
71224var helpers = _dereq_('./helpers');
71225var clonePlot = _dereq_('./cloneplot');
71226var toSVG = _dereq_('./tosvg');
71227var svgToImg = _dereq_('./svgtoimg');
71228
71229/**
71230 * @param {object} gd figure Object
71231 * @param {object} opts option object
71232 * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg'
71233 */
71234function toImage(gd, opts) {
71235 // first clone the GD so we can operate in a clean environment
71236 var ev = new EventEmitter();
71237
71238 var clone = clonePlot(gd, {format: 'png'});
71239 var clonedGd = clone.gd;
71240
71241 // put the cloned div somewhere off screen before attaching to DOM
71242 clonedGd.style.position = 'absolute';
71243 clonedGd.style.left = '-5000px';
71244 document.body.appendChild(clonedGd);
71245
71246 function wait() {
71247 var delay = helpers.getDelay(clonedGd._fullLayout);
71248
71249 setTimeout(function() {
71250 var svg = toSVG(clonedGd);
71251
71252 var canvas = document.createElement('canvas');
71253 canvas.id = Lib.randstr();
71254
71255 ev = svgToImg({
71256 format: opts.format,
71257 width: clonedGd._fullLayout.width,
71258 height: clonedGd._fullLayout.height,
71259 canvas: canvas,
71260 emitter: ev,
71261 svg: svg
71262 });
71263
71264 ev.clean = function() {
71265 if(clonedGd) document.body.removeChild(clonedGd);
71266 };
71267 }, delay);
71268 }
71269
71270 var redrawFunc = helpers.getRedrawFunc(clonedGd);
71271
71272 Registry.call('plot', clonedGd, clone.data, clone.layout, clone.config)
71273 .then(redrawFunc)
71274 .then(wait)
71275 .catch(function(err) {
71276 ev.emit('error', err);
71277 });
71278
71279
71280 return ev;
71281}
71282
71283module.exports = toImage;
71284
71285},{"../lib":178,"../registry":269,"./cloneplot":270,"./helpers":273,"./svgtoimg":275,"./tosvg":277,"events":15}],277:[function(_dereq_,module,exports){
71286/**
71287* Copyright 2012-2020, Plotly, Inc.
71288* All rights reserved.
71289*
71290* This source code is licensed under the MIT license found in the
71291* LICENSE file in the root directory of this source tree.
71292*/
71293
71294
71295'use strict';
71296
71297var d3 = _dereq_('d3');
71298
71299var Lib = _dereq_('../lib');
71300var Drawing = _dereq_('../components/drawing');
71301var Color = _dereq_('../components/color');
71302
71303var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
71304var DOUBLEQUOTE_REGEX = /"/g;
71305var DUMMY_SUB = 'TOBESTRIPPED';
71306var DUMMY_REGEX = new RegExp('("' + DUMMY_SUB + ')|(' + DUMMY_SUB + '")', 'g');
71307
71308function htmlEntityDecode(s) {
71309 var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html('');
71310 var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
71311 if(d === '&lt;') { return '&#60;'; } // special handling for brackets
71312 if(d === '&rt;') { return '&#62;'; }
71313 if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
71314 return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
71315 });
71316 hiddenDiv.remove();
71317 return replaced;
71318}
71319
71320function xmlEntityEncode(str) {
71321 return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&amp;');
71322}
71323
71324module.exports = function toSVG(gd, format, scale) {
71325 var fullLayout = gd._fullLayout;
71326 var svg = fullLayout._paper;
71327 var toppaper = fullLayout._toppaper;
71328 var width = fullLayout.width;
71329 var height = fullLayout.height;
71330 var i;
71331
71332 // make background color a rect in the svg, then revert after scraping
71333 // all other alterations have been dealt with by properly preparing the svg
71334 // in the first place... like setting cursors with css classes so we don't
71335 // have to remove them, and providing the right namespaces in the svg to
71336 // begin with
71337 svg.insert('rect', ':first-child')
71338 .call(Drawing.setRect, 0, 0, width, height)
71339 .call(Color.fill, fullLayout.paper_bgcolor);
71340
71341 // subplot-specific to-SVG methods
71342 // which notably add the contents of the gl-container
71343 // into the main svg node
71344 var basePlotModules = fullLayout._basePlotModules || [];
71345 for(i = 0; i < basePlotModules.length; i++) {
71346 var _module = basePlotModules[i];
71347
71348 if(_module.toSVG) _module.toSVG(gd);
71349 }
71350
71351 // add top items above them assumes everything in toppaper is either
71352 // a group or a defs, and if it's empty (like hoverlayer) we can ignore it.
71353 if(toppaper) {
71354 var nodes = toppaper.node().childNodes;
71355
71356 // make copy of nodes as childNodes prop gets mutated in loop below
71357 var topGroups = Array.prototype.slice.call(nodes);
71358
71359 for(i = 0; i < topGroups.length; i++) {
71360 var topGroup = topGroups[i];
71361
71362 if(topGroup.childNodes.length) svg.node().appendChild(topGroup);
71363 }
71364 }
71365
71366 // remove draglayer for Adobe Illustrator compatibility
71367 if(fullLayout._draggers) {
71368 fullLayout._draggers.remove();
71369 }
71370
71371 // in case the svg element had an explicit background color, remove this
71372 // we want the rect to get the color so it's the right size; svg bg will
71373 // fill whatever container it's displayed in regardless of plot size.
71374 svg.node().style.background = '';
71375
71376 svg.selectAll('text')
71377 .attr({'data-unformatted': null, 'data-math': null})
71378 .each(function() {
71379 var txt = d3.select(this);
71380
71381 // hidden text is pre-formatting mathjax, the browser ignores it
71382 // but in a static plot it's useless and it can confuse batik
71383 // we've tried to standardize on display:none but make sure we still
71384 // catch visibility:hidden if it ever arises
71385 if(this.style.visibility === 'hidden' || this.style.display === 'none') {
71386 txt.remove();
71387 return;
71388 } else {
71389 // clear other visibility/display values to default
71390 // to not potentially confuse non-browser SVG implementations
71391 txt.style({visibility: null, display: null});
71392 }
71393
71394 // Font family styles break things because of quotation marks,
71395 // so we must remove them *after* the SVG DOM has been serialized
71396 // to a string (browsers convert singles back)
71397 var ff = this.style.fontFamily;
71398 if(ff && ff.indexOf('"') !== -1) {
71399 txt.style('font-family', ff.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71400 }
71401 });
71402
71403
71404 if(fullLayout._gradientUrlQueryParts) {
71405 var queryParts = [];
71406 for(var k in fullLayout._gradientUrlQueryParts) queryParts.push(k);
71407
71408 if(queryParts.length) {
71409 svg.selectAll(queryParts.join(',')).each(function() {
71410 var pt = d3.select(this);
71411
71412 // similar to font family styles above,
71413 // we must remove " after the SVG DOM has been serialized
71414 var fill = this.style.fill;
71415 if(fill && fill.indexOf('url(') !== -1) {
71416 pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71417 }
71418
71419 var stroke = this.style.stroke;
71420 if(stroke && stroke.indexOf('url(') !== -1) {
71421 pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
71422 }
71423 });
71424 }
71425 }
71426
71427 if(format === 'pdf' || format === 'eps') {
71428 // these formats make the extra line MathJax adds around symbols look super thick in some cases
71429 // it looks better if this is removed entirely.
71430 svg.selectAll('#MathJax_SVG_glyphs path')
71431 .attr('stroke-width', 0);
71432 }
71433
71434 // fix for IE namespacing quirk?
71435 // http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie
71436 svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg);
71437 svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink);
71438
71439 if(format === 'svg' && scale) {
71440 svg.attr('width', scale * width);
71441 svg.attr('height', scale * height);
71442 svg.attr('viewBox', '0 0 ' + width + ' ' + height);
71443 }
71444
71445 var s = new window.XMLSerializer().serializeToString(svg.node());
71446 s = htmlEntityDecode(s);
71447 s = xmlEntityEncode(s);
71448
71449 // Fix quotations around font strings and gradient URLs
71450 s = s.replace(DUMMY_REGEX, '\'');
71451
71452 // IE is very strict, so we will need to clean
71453 // svg with the following regex
71454 // yes this is messy, but do not know a better way
71455 // Even with this IE will not work due to tainted canvas
71456 // see https://github.com/kangax/fabric.js/issues/1957
71457 // http://stackoverflow.com/questions/18112047/canvas-todataurl-working-in-all-browsers-except-ie10
71458 // Leave here just in case the CORS/tainted IE issue gets resolved
71459 if(Lib.isIE()) {
71460 // replace double quote with single quote
71461 s = s.replace(/"/gi, '\'');
71462 // url in svg are single quoted
71463 // since we changed double to single
71464 // we'll need to change these to double-quoted
71465 s = s.replace(/(\('#)([^']*)('\))/gi, '(\"#$2\")');
71466 // font names with spaces will be escaped single-quoted
71467 // we'll need to change these to double-quoted
71468 s = s.replace(/(\\')/gi, '\"');
71469 }
71470
71471 return s;
71472};
71473
71474},{"../components/color":52,"../components/drawing":74,"../constants/xmlns_namespaces":159,"../lib":178,"d3":16}],278:[function(_dereq_,module,exports){
71475/**
71476* Copyright 2012-2020, Plotly, Inc.
71477* All rights reserved.
71478*
71479* This source code is licensed under the MIT license found in the
71480* LICENSE file in the root directory of this source tree.
71481*/
71482
71483'use strict';
71484
71485var Lib = _dereq_('../../lib');
71486
71487// arrayOk attributes, merge them into calcdata array
71488module.exports = function arraysToCalcdata(cd, trace) {
71489 for(var i = 0; i < cd.length; i++) cd[i].i = i;
71490
71491 Lib.mergeArray(trace.text, cd, 'tx');
71492 Lib.mergeArray(trace.hovertext, cd, 'htx');
71493
71494 var marker = trace.marker;
71495 if(marker) {
71496 Lib.mergeArray(marker.opacity, cd, 'mo', true);
71497 Lib.mergeArray(marker.color, cd, 'mc');
71498
71499 var markerLine = marker.line;
71500 if(markerLine) {
71501 Lib.mergeArray(markerLine.color, cd, 'mlc');
71502 Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
71503 }
71504 }
71505};
71506
71507},{"../../lib":178}],279:[function(_dereq_,module,exports){
71508/**
71509* Copyright 2012-2020, Plotly, Inc.
71510* All rights reserved.
71511*
71512* This source code is licensed under the MIT license found in the
71513* LICENSE file in the root directory of this source tree.
71514*/
71515
71516'use strict';
71517
71518var scatterAttrs = _dereq_('../scatter/attributes');
71519var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
71520var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
71521var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
71522var fontAttrs = _dereq_('../../plots/font_attributes');
71523var constants = _dereq_('./constants');
71524
71525var extendFlat = _dereq_('../../lib/extend').extendFlat;
71526
71527var textFontAttrs = fontAttrs({
71528 editType: 'calc',
71529 arrayOk: true,
71530 colorEditType: 'style',
71531
71532});
71533
71534var scatterMarkerAttrs = scatterAttrs.marker;
71535var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
71536
71537var markerLineWidth = extendFlat({},
71538 scatterMarkerLineAttrs.width, { dflt: 0 });
71539
71540var markerLine = extendFlat({
71541 width: markerLineWidth,
71542 editType: 'calc'
71543}, colorScaleAttrs('marker.line'));
71544
71545var marker = extendFlat({
71546 line: markerLine,
71547 editType: 'calc'
71548}, colorScaleAttrs('marker'), {
71549 opacity: {
71550 valType: 'number',
71551 arrayOk: true,
71552 dflt: 1,
71553 min: 0,
71554 max: 1,
71555
71556 editType: 'style',
71557
71558 }
71559});
71560
71561module.exports = {
71562 x: scatterAttrs.x,
71563 x0: scatterAttrs.x0,
71564 dx: scatterAttrs.dx,
71565 y: scatterAttrs.y,
71566 y0: scatterAttrs.y0,
71567 dy: scatterAttrs.dy,
71568
71569 text: scatterAttrs.text,
71570 texttemplate: texttemplateAttrs({editType: 'plot'}, {
71571 keys: constants.eventDataKeys
71572 }),
71573 hovertext: scatterAttrs.hovertext,
71574 hovertemplate: hovertemplateAttrs({}, {
71575 keys: constants.eventDataKeys
71576 }),
71577
71578 textposition: {
71579 valType: 'enumerated',
71580
71581 values: ['inside', 'outside', 'auto', 'none'],
71582 dflt: 'none',
71583 arrayOk: true,
71584 editType: 'calc',
71585
71586 },
71587
71588 insidetextanchor: {
71589 valType: 'enumerated',
71590 values: ['end', 'middle', 'start'],
71591 dflt: 'end',
71592
71593 editType: 'plot',
71594
71595 },
71596
71597 textangle: {
71598 valType: 'angle',
71599 dflt: 'auto',
71600
71601 editType: 'plot',
71602
71603 },
71604
71605 textfont: extendFlat({}, textFontAttrs, {
71606
71607 }),
71608
71609 insidetextfont: extendFlat({}, textFontAttrs, {
71610
71611 }),
71612
71613 outsidetextfont: extendFlat({}, textFontAttrs, {
71614
71615 }),
71616
71617 constraintext: {
71618 valType: 'enumerated',
71619 values: ['inside', 'outside', 'both', 'none'],
71620
71621 dflt: 'both',
71622 editType: 'calc',
71623
71624 },
71625
71626 cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {
71627
71628 }),
71629
71630 orientation: {
71631 valType: 'enumerated',
71632
71633 values: ['v', 'h'],
71634 editType: 'calc+clearAxisTypes',
71635
71636 },
71637
71638 base: {
71639 valType: 'any',
71640 dflt: null,
71641 arrayOk: true,
71642
71643 editType: 'calc',
71644
71645 },
71646
71647 offset: {
71648 valType: 'number',
71649 dflt: null,
71650 arrayOk: true,
71651
71652 editType: 'calc',
71653
71654 },
71655
71656 width: {
71657 valType: 'number',
71658 dflt: null,
71659 min: 0,
71660 arrayOk: true,
71661
71662 editType: 'calc',
71663
71664 },
71665
71666 marker: marker,
71667
71668 offsetgroup: {
71669 valType: 'string',
71670
71671 dflt: '',
71672 editType: 'calc',
71673
71674 },
71675 alignmentgroup: {
71676 valType: 'string',
71677
71678 dflt: '',
71679 editType: 'calc',
71680
71681 },
71682
71683 selected: {
71684 marker: {
71685 opacity: scatterAttrs.selected.marker.opacity,
71686 color: scatterAttrs.selected.marker.color,
71687 editType: 'style'
71688 },
71689 textfont: scatterAttrs.selected.textfont,
71690 editType: 'style'
71691 },
71692 unselected: {
71693 marker: {
71694 opacity: scatterAttrs.unselected.marker.opacity,
71695 color: scatterAttrs.unselected.marker.color,
71696 editType: 'style'
71697 },
71698 textfont: scatterAttrs.unselected.textfont,
71699 editType: 'style'
71700 },
71701
71702 r: scatterAttrs.r,
71703 t: scatterAttrs.t,
71704
71705 _deprecated: {
71706 bardir: {
71707 valType: 'enumerated',
71708
71709 editType: 'calc',
71710 values: ['v', 'h'],
71711
71712 }
71713 }
71714};
71715
71716},{"../../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){
71717/**
71718* Copyright 2012-2020, Plotly, Inc.
71719* All rights reserved.
71720*
71721* This source code is licensed under the MIT license found in the
71722* LICENSE file in the root directory of this source tree.
71723*/
71724
71725'use strict';
71726
71727var Axes = _dereq_('../../plots/cartesian/axes');
71728var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
71729var colorscaleCalc = _dereq_('../../components/colorscale/calc');
71730var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
71731var calcSelection = _dereq_('../scatter/calc_selection');
71732
71733module.exports = function calc(gd, trace) {
71734 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
71735 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
71736 var size, pos;
71737
71738 var sizeOpts = {
71739 msUTC: !!(trace.base || trace.base === 0)
71740 };
71741
71742 if(trace.orientation === 'h') {
71743 size = xa.makeCalcdata(trace, 'x', sizeOpts);
71744 pos = ya.makeCalcdata(trace, 'y');
71745 } else {
71746 size = ya.makeCalcdata(trace, 'y', sizeOpts);
71747 pos = xa.makeCalcdata(trace, 'x');
71748 }
71749
71750 // create the "calculated data" to plot
71751 var serieslen = Math.min(pos.length, size.length);
71752 var cd = new Array(serieslen);
71753
71754 // set position and size
71755 for(var i = 0; i < serieslen; i++) {
71756 cd[i] = { p: pos[i], s: size[i] };
71757
71758 if(trace.ids) {
71759 cd[i].id = String(trace.ids[i]);
71760 }
71761 }
71762
71763 // auto-z and autocolorscale if applicable
71764 if(hasColorscale(trace, 'marker')) {
71765 colorscaleCalc(gd, trace, {
71766 vals: trace.marker.color,
71767 containerStr: 'marker',
71768 cLetter: 'c'
71769 });
71770 }
71771 if(hasColorscale(trace, 'marker.line')) {
71772 colorscaleCalc(gd, trace, {
71773 vals: trace.marker.line.color,
71774 containerStr: 'marker.line',
71775 cLetter: 'c'
71776 });
71777 }
71778
71779 arraysToCalcdata(cd, trace);
71780 calcSelection(cd, trace);
71781
71782 return cd;
71783};
71784
71785},{"../../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){
71786/**
71787* Copyright 2012-2020, Plotly, Inc.
71788* All rights reserved.
71789*
71790* This source code is licensed under the MIT license found in the
71791* LICENSE file in the root directory of this source tree.
71792*/
71793
71794
71795'use strict';
71796
71797module.exports = {
71798 // padding in pixels around text
71799 TEXTPAD: 3,
71800 // 'value' and 'label' are not really necessary for bar traces,
71801 // but they were made available to `texttemplate` (maybe by accident)
71802 // via tokens `%{value}` and `%{label}` starting in 1.50.0,
71803 // so let's include them in the event data also.
71804 eventDataKeys: ['value', 'label']
71805};
71806
71807},{}],282:[function(_dereq_,module,exports){
71808/**
71809* Copyright 2012-2020, Plotly, Inc.
71810* All rights reserved.
71811*
71812* This source code is licensed under the MIT license found in the
71813* LICENSE file in the root directory of this source tree.
71814*/
71815
71816'use strict';
71817
71818var isNumeric = _dereq_('fast-isnumeric');
71819var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
71820var BADNUM = _dereq_('../../constants/numerical').BADNUM;
71821
71822var Registry = _dereq_('../../registry');
71823var Axes = _dereq_('../../plots/cartesian/axes');
71824var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
71825var Sieve = _dereq_('./sieve.js');
71826
71827/*
71828 * Bar chart stacking/grouping positioning and autoscaling calculations
71829 * for each direction separately calculate the ranges and positions
71830 * note that this handles histograms too
71831 * now doing this one subplot at a time
71832 */
71833
71834function crossTraceCalc(gd, plotinfo) {
71835 var xa = plotinfo.xaxis;
71836 var ya = plotinfo.yaxis;
71837
71838 var fullLayout = gd._fullLayout;
71839 var fullTraces = gd._fullData;
71840 var calcTraces = gd.calcdata;
71841 var calcTracesHorz = [];
71842 var calcTracesVert = [];
71843
71844 for(var i = 0; i < fullTraces.length; i++) {
71845 var fullTrace = fullTraces[i];
71846 if(
71847 fullTrace.visible === true &&
71848 Registry.traceIs(fullTrace, 'bar') &&
71849 fullTrace.xaxis === xa._id &&
71850 fullTrace.yaxis === ya._id
71851 ) {
71852 if(fullTrace.orientation === 'h') {
71853 calcTracesHorz.push(calcTraces[i]);
71854 } else {
71855 calcTracesVert.push(calcTraces[i]);
71856 }
71857
71858 if(fullTrace._computePh) {
71859 var cd = gd.calcdata[i];
71860 for(var j = 0; j < cd.length; j++) {
71861 if(typeof cd[j].ph0 === 'function') cd[j].ph0 = cd[j].ph0();
71862 if(typeof cd[j].ph1 === 'function') cd[j].ph1 = cd[j].ph1();
71863 }
71864 }
71865 }
71866 }
71867
71868 var opts = {
71869 mode: fullLayout.barmode,
71870 norm: fullLayout.barnorm,
71871 gap: fullLayout.bargap,
71872 groupgap: fullLayout.bargroupgap
71873 };
71874
71875 setGroupPositions(gd, xa, ya, calcTracesVert, opts);
71876 setGroupPositions(gd, ya, xa, calcTracesHorz, opts);
71877}
71878
71879function setGroupPositions(gd, pa, sa, calcTraces, opts) {
71880 if(!calcTraces.length) return;
71881
71882 var excluded;
71883 var included;
71884 var i, calcTrace, fullTrace;
71885
71886 initBase(sa, calcTraces);
71887
71888 switch(opts.mode) {
71889 case 'overlay':
71890 setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts);
71891 break;
71892
71893 case 'group':
71894 // exclude from the group those traces for which the user set an offset
71895 excluded = [];
71896 included = [];
71897 for(i = 0; i < calcTraces.length; i++) {
71898 calcTrace = calcTraces[i];
71899 fullTrace = calcTrace[0].trace;
71900
71901 if(fullTrace.offset === undefined) included.push(calcTrace);
71902 else excluded.push(calcTrace);
71903 }
71904
71905 if(included.length) {
71906 setGroupPositionsInGroupMode(gd, pa, sa, included, opts);
71907 }
71908 if(excluded.length) {
71909 setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
71910 }
71911 break;
71912
71913 case 'stack':
71914 case 'relative':
71915 // exclude from the stack those traces for which the user set a base
71916 excluded = [];
71917 included = [];
71918 for(i = 0; i < calcTraces.length; i++) {
71919 calcTrace = calcTraces[i];
71920 fullTrace = calcTrace[0].trace;
71921
71922 if(fullTrace.base === undefined) included.push(calcTrace);
71923 else excluded.push(calcTrace);
71924 }
71925
71926 if(included.length) {
71927 setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts);
71928 }
71929 if(excluded.length) {
71930 setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
71931 }
71932 break;
71933 }
71934
71935 collectExtents(calcTraces, pa);
71936}
71937
71938function initBase(sa, calcTraces) {
71939 var i, j;
71940
71941 for(i = 0; i < calcTraces.length; i++) {
71942 var cd = calcTraces[i];
71943 var trace = cd[0].trace;
71944 var base = (trace.type === 'funnel') ? trace._base : trace.base;
71945 var b;
71946
71947 // not sure if it really makes sense to have dates for bar size data...
71948 // ideally if we want to make gantt charts or something we'd treat
71949 // the actual size (trace.x or y) as time delta but base as absolute
71950 // time. But included here for completeness.
71951 var scalendar = trace.orientation === 'h' ? trace.xcalendar : trace.ycalendar;
71952
71953 // 'base' on categorical axes makes no sense
71954 var d2c = sa.type === 'category' || sa.type === 'multicategory' ?
71955 function() { return null; } :
71956 sa.d2c;
71957
71958 if(isArrayOrTypedArray(base)) {
71959 for(j = 0; j < Math.min(base.length, cd.length); j++) {
71960 b = d2c(base[j], 0, scalendar);
71961 if(isNumeric(b)) {
71962 cd[j].b = +b;
71963 cd[j].hasB = 1;
71964 } else cd[j].b = 0;
71965 }
71966 for(; j < cd.length; j++) {
71967 cd[j].b = 0;
71968 }
71969 } else {
71970 b = d2c(base, 0, scalendar);
71971 var hasBase = isNumeric(b);
71972 b = hasBase ? b : 0;
71973 for(j = 0; j < cd.length; j++) {
71974 cd[j].b = b;
71975 if(hasBase) cd[j].hasB = 1;
71976 }
71977 }
71978 }
71979}
71980
71981function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) {
71982 // update position axis and set bar offsets and widths
71983 for(var i = 0; i < calcTraces.length; i++) {
71984 var calcTrace = calcTraces[i];
71985
71986 var sieve = new Sieve([calcTrace], {
71987 sepNegVal: false,
71988 overlapNoMerge: !opts.norm
71989 });
71990
71991 // set bar offsets and widths, and update position axis
71992 setOffsetAndWidth(pa, sieve, opts);
71993
71994 // set bar bases and sizes, and update size axis
71995 //
71996 // (note that `setGroupPositionsInOverlayMode` handles the case barnorm
71997 // is defined, because this function is also invoked for traces that
71998 // can't be grouped or stacked)
71999 if(opts.norm) {
72000 sieveBars(sieve);
72001 normalizeBars(sa, sieve, opts);
72002 } else {
72003 setBaseAndTop(sa, sieve);
72004 }
72005 }
72006}
72007
72008function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces, opts) {
72009 var sieve = new Sieve(calcTraces, {
72010 sepNegVal: false,
72011 overlapNoMerge: !opts.norm
72012 });
72013
72014 // set bar offsets and widths, and update position axis
72015 setOffsetAndWidthInGroupMode(gd, pa, sieve, opts);
72016
72017 // relative-stack bars within the same trace that would otherwise
72018 // be hidden
72019 unhideBarsWithinTrace(sieve);
72020
72021 // set bar bases and sizes, and update size axis
72022 if(opts.norm) {
72023 sieveBars(sieve);
72024 normalizeBars(sa, sieve, opts);
72025 } else {
72026 setBaseAndTop(sa, sieve);
72027 }
72028}
72029
72030function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
72031 var sieve = new Sieve(calcTraces, {
72032 sepNegVal: opts.mode === 'relative',
72033 overlapNoMerge: !(opts.norm || opts.mode === 'stack' || opts.mode === 'relative')
72034 });
72035
72036 // set bar offsets and widths, and update position axis
72037 setOffsetAndWidth(pa, sieve, opts);
72038
72039 // set bar bases and sizes, and update size axis
72040 stackBars(sa, sieve, opts);
72041
72042 // flag the outmost bar (for text display purposes)
72043 for(var i = 0; i < calcTraces.length; i++) {
72044 var calcTrace = calcTraces[i];
72045
72046 for(var j = 0; j < calcTrace.length; j++) {
72047 var bar = calcTrace[j];
72048
72049 if(bar.s !== BADNUM) {
72050 var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s));
72051 if(isOutmostBar) bar._outmost = true;
72052 }
72053 }
72054 }
72055
72056 // Note that marking the outmost bars has to be done
72057 // before `normalizeBars` changes `bar.b` and `bar.s`.
72058 if(opts.norm) normalizeBars(sa, sieve, opts);
72059}
72060
72061function setOffsetAndWidth(pa, sieve, opts) {
72062 var minDiff = sieve.minDiff;
72063 var calcTraces = sieve.traces;
72064
72065 // set bar offsets and widths
72066 var barGroupWidth = minDiff * (1 - opts.gap);
72067 var barWidthPlusGap = barGroupWidth;
72068 var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
72069
72070 // computer bar group center and bar offset
72071 var offsetFromCenter = -barWidth / 2;
72072
72073 for(var i = 0; i < calcTraces.length; i++) {
72074 var calcTrace = calcTraces[i];
72075 var t = calcTrace[0].t;
72076
72077 // store bar width and offset for this trace
72078 t.barwidth = barWidth;
72079 t.poffset = offsetFromCenter;
72080 t.bargroupwidth = barGroupWidth;
72081 t.bardelta = minDiff;
72082 }
72083
72084 // stack bars that only differ by rounding
72085 sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
72086
72087 // if defined, apply trace offset and width
72088 applyAttributes(sieve);
72089
72090 // store the bar center in each calcdata item
72091 setBarCenterAndWidth(pa, sieve);
72092
72093 // update position axes
72094 updatePositionAxis(pa, sieve);
72095}
72096
72097function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
72098 var fullLayout = gd._fullLayout;
72099 var positions = sieve.positions;
72100 var distinctPositions = sieve.distinctPositions;
72101 var minDiff = sieve.minDiff;
72102 var calcTraces = sieve.traces;
72103 var nTraces = calcTraces.length;
72104
72105 // if there aren't any overlapping positions,
72106 // let them have full width even if mode is group
72107 var overlap = (positions.length !== distinctPositions.length);
72108 var barGroupWidth = minDiff * (1 - opts.gap);
72109
72110 var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
72111 var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
72112
72113 for(var i = 0; i < nTraces; i++) {
72114 var calcTrace = calcTraces[i];
72115 var trace = calcTrace[0].trace;
72116
72117 var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
72118 var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
72119
72120 var barWidthPlusGap;
72121 if(nOffsetGroups) {
72122 barWidthPlusGap = barGroupWidth / nOffsetGroups;
72123 } else {
72124 barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
72125 }
72126
72127 var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
72128
72129 var offsetFromCenter;
72130 if(nOffsetGroups) {
72131 offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
72132 } else {
72133 offsetFromCenter = overlap ?
72134 ((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
72135 -barWidth / 2;
72136 }
72137
72138 var t = calcTrace[0].t;
72139 t.barwidth = barWidth;
72140 t.poffset = offsetFromCenter;
72141 t.bargroupwidth = barGroupWidth;
72142 t.bardelta = minDiff;
72143 }
72144
72145 // stack bars that only differ by rounding
72146 sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
72147
72148 // if defined, apply trace width
72149 applyAttributes(sieve);
72150
72151 // store the bar center in each calcdata item
72152 setBarCenterAndWidth(pa, sieve);
72153
72154 // update position axes
72155 updatePositionAxis(pa, sieve, overlap);
72156}
72157
72158function applyAttributes(sieve) {
72159 var calcTraces = sieve.traces;
72160 var i, j;
72161
72162 for(i = 0; i < calcTraces.length; i++) {
72163 var calcTrace = calcTraces[i];
72164 var calcTrace0 = calcTrace[0];
72165 var fullTrace = calcTrace0.trace;
72166 var t = calcTrace0.t;
72167 var offset = fullTrace._offset || fullTrace.offset;
72168 var initialPoffset = t.poffset;
72169 var newPoffset;
72170
72171 if(isArrayOrTypedArray(offset)) {
72172 // if offset is an array, then clone it into t.poffset.
72173 newPoffset = Array.prototype.slice.call(offset, 0, calcTrace.length);
72174
72175 // guard against non-numeric items
72176 for(j = 0; j < newPoffset.length; j++) {
72177 if(!isNumeric(newPoffset[j])) {
72178 newPoffset[j] = initialPoffset;
72179 }
72180 }
72181
72182 // if the length of the array is too short,
72183 // then extend it with the initial value of t.poffset
72184 for(j = newPoffset.length; j < calcTrace.length; j++) {
72185 newPoffset.push(initialPoffset);
72186 }
72187
72188 t.poffset = newPoffset;
72189 } else if(offset !== undefined) {
72190 t.poffset = offset;
72191 }
72192
72193 var width = fullTrace._width || fullTrace.width;
72194 var initialBarwidth = t.barwidth;
72195
72196 if(isArrayOrTypedArray(width)) {
72197 // if width is an array, then clone it into t.barwidth.
72198 var newBarwidth = Array.prototype.slice.call(width, 0, calcTrace.length);
72199
72200 // guard against non-numeric items
72201 for(j = 0; j < newBarwidth.length; j++) {
72202 if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth;
72203 }
72204
72205 // if the length of the array is too short,
72206 // then extend it with the initial value of t.barwidth
72207 for(j = newBarwidth.length; j < calcTrace.length; j++) {
72208 newBarwidth.push(initialBarwidth);
72209 }
72210
72211 t.barwidth = newBarwidth;
72212
72213 // if user didn't set offset,
72214 // then correct t.poffset to ensure bars remain centered
72215 if(offset === undefined) {
72216 newPoffset = [];
72217 for(j = 0; j < calcTrace.length; j++) {
72218 newPoffset.push(
72219 initialPoffset + (initialBarwidth - newBarwidth[j]) / 2
72220 );
72221 }
72222 t.poffset = newPoffset;
72223 }
72224 } else if(width !== undefined) {
72225 t.barwidth = width;
72226
72227 // if user didn't set offset,
72228 // then correct t.poffset to ensure bars remain centered
72229 if(offset === undefined) {
72230 t.poffset = initialPoffset + (initialBarwidth - width) / 2;
72231 }
72232 }
72233 }
72234}
72235
72236function setBarCenterAndWidth(pa, sieve) {
72237 var calcTraces = sieve.traces;
72238 var pLetter = getAxisLetter(pa);
72239
72240 for(var i = 0; i < calcTraces.length; i++) {
72241 var calcTrace = calcTraces[i];
72242 var t = calcTrace[0].t;
72243 var poffset = t.poffset;
72244 var poffsetIsArray = Array.isArray(poffset);
72245 var barwidth = t.barwidth;
72246 var barwidthIsArray = Array.isArray(barwidth);
72247
72248 for(var j = 0; j < calcTrace.length; j++) {
72249 var calcBar = calcTrace[j];
72250
72251 // store the actual bar width and position, for use by hover
72252 var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth;
72253 calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2;
72254 }
72255 }
72256}
72257
72258function updatePositionAxis(pa, sieve, allowMinDtick) {
72259 var calcTraces = sieve.traces;
72260 var minDiff = sieve.minDiff;
72261 var vpad = minDiff / 2;
72262
72263 Axes.minDtick(pa, sieve.minDiff, sieve.distinctPositions[0], allowMinDtick);
72264
72265 for(var i = 0; i < calcTraces.length; i++) {
72266 var calcTrace = calcTraces[i];
72267 var calcTrace0 = calcTrace[0];
72268 var fullTrace = calcTrace0.trace;
72269 var pts = [];
72270 var bar, l, r, j;
72271
72272 for(j = 0; j < calcTrace.length; j++) {
72273 bar = calcTrace[j];
72274 l = bar.p - vpad;
72275 r = bar.p + vpad;
72276 pts.push(l, r);
72277 }
72278
72279 if(fullTrace.width || fullTrace.offset) {
72280 var t = calcTrace0.t;
72281 var poffset = t.poffset;
72282 var barwidth = t.barwidth;
72283 var poffsetIsArray = Array.isArray(poffset);
72284 var barwidthIsArray = Array.isArray(barwidth);
72285
72286 for(j = 0; j < calcTrace.length; j++) {
72287 bar = calcTrace[j];
72288 var calcBarOffset = poffsetIsArray ? poffset[j] : poffset;
72289 var calcBarWidth = barwidthIsArray ? barwidth[j] : barwidth;
72290 l = bar.p + calcBarOffset;
72291 r = l + calcBarWidth;
72292 pts.push(l, r);
72293 }
72294 }
72295
72296 fullTrace._extremes[pa._id] = Axes.findExtremes(pa, pts, {padded: false});
72297 }
72298}
72299
72300// store these bar bases and tops in calcdata
72301// and make sure the size axis includes zero,
72302// along with the bases and tops of each bar.
72303function setBaseAndTop(sa, sieve) {
72304 var calcTraces = sieve.traces;
72305 var sLetter = getAxisLetter(sa);
72306
72307 for(var i = 0; i < calcTraces.length; i++) {
72308 var calcTrace = calcTraces[i];
72309 var fullTrace = calcTrace[0].trace;
72310 var pts = [];
72311 var tozero = false;
72312
72313 for(var j = 0; j < calcTrace.length; j++) {
72314 var bar = calcTrace[j];
72315 var base = bar.b;
72316 var top = base + bar.s;
72317
72318 bar[sLetter] = top;
72319 pts.push(top);
72320 if(bar.hasB) pts.push(base);
72321
72322 if(!bar.hasB || !bar.b) {
72323 tozero = true;
72324 }
72325 }
72326
72327 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72328 tozero: tozero,
72329 padded: true
72330 });
72331 }
72332}
72333
72334function stackBars(sa, sieve, opts) {
72335 var sLetter = getAxisLetter(sa);
72336 var calcTraces = sieve.traces;
72337 var calcTrace;
72338 var fullTrace;
72339 var isFunnel;
72340 var i, j;
72341 var bar;
72342
72343 for(i = 0; i < calcTraces.length; i++) {
72344 calcTrace = calcTraces[i];
72345 fullTrace = calcTrace[0].trace;
72346
72347 if(fullTrace.type === 'funnel') {
72348 for(j = 0; j < calcTrace.length; j++) {
72349 bar = calcTrace[j];
72350
72351 if(bar.s !== BADNUM) {
72352 // create base of funnels
72353 sieve.put(bar.p, -0.5 * bar.s);
72354 }
72355 }
72356 }
72357 }
72358
72359 for(i = 0; i < calcTraces.length; i++) {
72360 calcTrace = calcTraces[i];
72361 fullTrace = calcTrace[0].trace;
72362
72363 isFunnel = (fullTrace.type === 'funnel');
72364
72365 var pts = [];
72366
72367 for(j = 0; j < calcTrace.length; j++) {
72368 bar = calcTrace[j];
72369
72370 if(bar.s !== BADNUM) {
72371 // stack current bar and get previous sum
72372 var value;
72373 if(isFunnel) {
72374 value = bar.s;
72375 } else {
72376 value = bar.s + bar.b;
72377 }
72378
72379 var base = sieve.put(bar.p, value);
72380
72381 var top = base + value;
72382
72383 // store the bar base and top in each calcdata item
72384 bar.b = base;
72385 bar[sLetter] = top;
72386
72387 if(!opts.norm) {
72388 pts.push(top);
72389 if(bar.hasB) {
72390 pts.push(base);
72391 }
72392 }
72393 }
72394 }
72395
72396 // if barnorm is set, let normalizeBars update the axis range
72397 if(!opts.norm) {
72398 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72399 // N.B. we don't stack base with 'base',
72400 // so set tozero:true always!
72401 tozero: true,
72402 padded: true
72403 });
72404 }
72405 }
72406}
72407
72408function sieveBars(sieve) {
72409 var calcTraces = sieve.traces;
72410
72411 for(var i = 0; i < calcTraces.length; i++) {
72412 var calcTrace = calcTraces[i];
72413
72414 for(var j = 0; j < calcTrace.length; j++) {
72415 var bar = calcTrace[j];
72416
72417 if(bar.s !== BADNUM) {
72418 sieve.put(bar.p, bar.b + bar.s);
72419 }
72420 }
72421 }
72422}
72423
72424function unhideBarsWithinTrace(sieve) {
72425 var calcTraces = sieve.traces;
72426
72427 for(var i = 0; i < calcTraces.length; i++) {
72428 var calcTrace = calcTraces[i];
72429 var fullTrace = calcTrace[0].trace;
72430
72431 if(fullTrace.base === undefined) {
72432 var inTraceSieve = new Sieve([calcTrace], {
72433 sepNegVal: true,
72434 overlapNoMerge: true
72435 });
72436
72437 for(var j = 0; j < calcTrace.length; j++) {
72438 var bar = calcTrace[j];
72439
72440 if(bar.p !== BADNUM) {
72441 // stack current bar and get previous sum
72442 var base = inTraceSieve.put(bar.p, bar.b + bar.s);
72443
72444 // if previous sum if non-zero, this means:
72445 // multiple bars have same starting point are potentially hidden,
72446 // shift them vertically so that all bars are visible by default
72447 if(base) bar.b = base;
72448 }
72449 }
72450 }
72451 }
72452}
72453
72454// Note:
72455//
72456// normalizeBars requires that either sieveBars or stackBars has been
72457// previously invoked.
72458function normalizeBars(sa, sieve, opts) {
72459 var calcTraces = sieve.traces;
72460 var sLetter = getAxisLetter(sa);
72461 var sTop = opts.norm === 'fraction' ? 1 : 100;
72462 var sTiny = sTop / 1e9; // in case of rounding error in sum
72463 var sMin = sa.l2c(sa.c2l(0));
72464 var sMax = opts.mode === 'stack' ? sTop : sMin;
72465
72466 function needsPadding(v) {
72467 return (
72468 isNumeric(sa.c2l(v)) &&
72469 ((v < sMin - sTiny) || (v > sMax + sTiny) || !isNumeric(sMin))
72470 );
72471 }
72472
72473 for(var i = 0; i < calcTraces.length; i++) {
72474 var calcTrace = calcTraces[i];
72475 var fullTrace = calcTrace[0].trace;
72476 var pts = [];
72477 var tozero = false;
72478 var padded = false;
72479
72480 for(var j = 0; j < calcTrace.length; j++) {
72481 var bar = calcTrace[j];
72482
72483 if(bar.s !== BADNUM) {
72484 var scale = Math.abs(sTop / sieve.get(bar.p, bar.s));
72485 bar.b *= scale;
72486 bar.s *= scale;
72487
72488 var base = bar.b;
72489 var top = base + bar.s;
72490
72491 bar[sLetter] = top;
72492 pts.push(top);
72493 padded = padded || needsPadding(top);
72494
72495 if(bar.hasB) {
72496 pts.push(base);
72497 padded = padded || needsPadding(base);
72498 }
72499
72500 if(!bar.hasB || !bar.b) {
72501 tozero = true;
72502 }
72503 }
72504 }
72505
72506 fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
72507 tozero: tozero,
72508 padded: padded
72509 });
72510 }
72511}
72512
72513// find the full position span of bars at each position
72514// for use by hover, to ensure labels move in if bars are
72515// narrower than the space they're in.
72516// run once per trace group (subplot & direction) and
72517// the same mapping is attached to all calcdata traces
72518function collectExtents(calcTraces, pa) {
72519 var pLetter = getAxisLetter(pa);
72520 var extents = {};
72521 var i, j, cd;
72522
72523 var pMin = Infinity;
72524 var pMax = -Infinity;
72525
72526 for(i = 0; i < calcTraces.length; i++) {
72527 cd = calcTraces[i];
72528 for(j = 0; j < cd.length; j++) {
72529 var p = cd[j].p;
72530 if(isNumeric(p)) {
72531 pMin = Math.min(pMin, p);
72532 pMax = Math.max(pMax, p);
72533 }
72534 }
72535 }
72536
72537 // this is just for positioning of hover labels, and nobody will care if
72538 // the label is 1px too far out; so round positions to 1/10K in case
72539 // position values don't exactly match from trace to trace
72540 var roundFactor = 10000 / (pMax - pMin);
72541 var round = extents.round = function(p) {
72542 return String(Math.round(roundFactor * (p - pMin)));
72543 };
72544
72545 for(i = 0; i < calcTraces.length; i++) {
72546 cd = calcTraces[i];
72547 cd[0].t.extents = extents;
72548
72549 var poffset = cd[0].t.poffset;
72550 var poffsetIsArray = Array.isArray(poffset);
72551
72552 for(j = 0; j < cd.length; j++) {
72553 var di = cd[j];
72554 var p0 = di[pLetter] - di.w / 2;
72555
72556 if(isNumeric(p0)) {
72557 var p1 = di[pLetter] + di.w / 2;
72558 var pVal = round(di.p);
72559 if(extents[pVal]) {
72560 extents[pVal] = [Math.min(p0, extents[pVal][0]), Math.max(p1, extents[pVal][1])];
72561 } else {
72562 extents[pVal] = [p0, p1];
72563 }
72564 }
72565
72566 di.p0 = di.p + (poffsetIsArray ? poffset[j] : poffset);
72567 di.p1 = di.p0 + di.w;
72568 di.s0 = di.b;
72569 di.s1 = di.s0 + di.s;
72570 }
72571 }
72572}
72573
72574function getAxisLetter(ax) {
72575 return ax._id.charAt(0);
72576}
72577
72578module.exports = {
72579 crossTraceCalc: crossTraceCalc,
72580 setGroupPositions: setGroupPositions
72581};
72582
72583},{"../../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){
72584/**
72585* Copyright 2012-2020, Plotly, Inc.
72586* All rights reserved.
72587*
72588* This source code is licensed under the MIT license found in the
72589* LICENSE file in the root directory of this source tree.
72590*/
72591
72592'use strict';
72593
72594var Lib = _dereq_('../../lib');
72595var Color = _dereq_('../../components/color');
72596var Registry = _dereq_('../../registry');
72597
72598var handleXYDefaults = _dereq_('../scatter/xy_defaults');
72599var handleStyleDefaults = _dereq_('./style_defaults');
72600var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
72601var attributes = _dereq_('./attributes');
72602
72603var coerceFont = Lib.coerceFont;
72604
72605function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
72606 function coerce(attr, dflt) {
72607 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
72608 }
72609
72610 var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
72611 if(!len) {
72612 traceOut.visible = false;
72613 return;
72614 }
72615
72616 coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
72617 coerce('base');
72618 coerce('offset');
72619 coerce('width');
72620
72621 coerce('text');
72622 coerce('hovertext');
72623 coerce('hovertemplate');
72624
72625 var textposition = coerce('textposition');
72626 handleText(traceIn, traceOut, layout, coerce, textposition, {
72627 moduleHasSelected: true,
72628 moduleHasUnselected: true,
72629 moduleHasConstrain: true,
72630 moduleHasCliponaxis: true,
72631 moduleHasTextangle: true,
72632 moduleHasInsideanchor: true
72633 });
72634
72635 handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
72636
72637 var lineColor = (traceOut.marker.line || {}).color;
72638
72639 // override defaultColor for error bars with defaultLine
72640 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
72641 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
72642 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
72643
72644 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
72645}
72646
72647function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) {
72648 var orientation = traceOut.orientation;
72649 // N.B. grouping is done across all trace types that support it
72650 var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
72651 var groupId = getAxisGroup(fullLayout, posAxId) + orientation;
72652
72653 var alignmentOpts = fullLayout._alignmentOpts || {};
72654 var alignmentgroup = coerce('alignmentgroup');
72655
72656 var alignmentGroups = alignmentOpts[groupId];
72657 if(!alignmentGroups) alignmentGroups = alignmentOpts[groupId] = {};
72658
72659 var alignmentGroupOpts = alignmentGroups[alignmentgroup];
72660
72661 if(alignmentGroupOpts) {
72662 alignmentGroupOpts.traces.push(traceOut);
72663 } else {
72664 alignmentGroupOpts = alignmentGroups[alignmentgroup] = {
72665 traces: [traceOut],
72666 alignmentIndex: Object.keys(alignmentGroups).length,
72667 offsetGroups: {}
72668 };
72669 }
72670
72671 var offsetgroup = coerce('offsetgroup');
72672 var offsetGroups = alignmentGroupOpts.offsetGroups;
72673 var offsetGroupOpts = offsetGroups[offsetgroup];
72674
72675 if(offsetgroup) {
72676 if(!offsetGroupOpts) {
72677 offsetGroupOpts = offsetGroups[offsetgroup] = {
72678 offsetIndex: Object.keys(offsetGroups).length
72679 };
72680 }
72681
72682 traceOut._offsetIndex = offsetGroupOpts.offsetIndex;
72683 }
72684}
72685
72686function crossTraceDefaults(fullData, fullLayout) {
72687 var traceIn, traceOut;
72688
72689 function coerce(attr) {
72690 return Lib.coerce(traceOut._input, traceOut, attributes, attr);
72691 }
72692
72693 if(fullLayout.barmode === 'group') {
72694 for(var i = 0; i < fullData.length; i++) {
72695 traceOut = fullData[i];
72696
72697 if(traceOut.type === 'bar') {
72698 traceIn = traceOut._input;
72699 handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
72700 }
72701 }
72702 }
72703}
72704
72705function handleText(traceIn, traceOut, layout, coerce, textposition, opts) {
72706 opts = opts || {};
72707 var moduleHasSelected = !(opts.moduleHasSelected === false);
72708 var moduleHasUnselected = !(opts.moduleHasUnselected === false);
72709 var moduleHasConstrain = !(opts.moduleHasConstrain === false);
72710 var moduleHasCliponaxis = !(opts.moduleHasCliponaxis === false);
72711 var moduleHasTextangle = !(opts.moduleHasTextangle === false);
72712 var moduleHasInsideanchor = !(opts.moduleHasInsideanchor === false);
72713 var hasPathbar = !!opts.hasPathbar;
72714
72715 var hasBoth = Array.isArray(textposition) || textposition === 'auto';
72716 var hasInside = hasBoth || textposition === 'inside';
72717 var hasOutside = hasBoth || textposition === 'outside';
72718
72719 if(hasInside || hasOutside) {
72720 var dfltFont = coerceFont(coerce, 'textfont', layout.font);
72721
72722 // Note that coercing `insidetextfont` is always needed –
72723 // even if `textposition` is `outside` for each trace – since
72724 // an outside label can become an inside one, for example because
72725 // of a bar being stacked on top of it.
72726 var insideTextFontDefault = Lib.extendFlat({}, dfltFont);
72727 var isTraceTextfontColorSet = traceIn.textfont && traceIn.textfont.color;
72728 var isColorInheritedFromLayoutFont = !isTraceTextfontColorSet;
72729 if(isColorInheritedFromLayoutFont) {
72730 delete insideTextFontDefault.color;
72731 }
72732 coerceFont(coerce, 'insidetextfont', insideTextFontDefault);
72733
72734 if(hasPathbar) {
72735 var pathbarTextFontDefault = Lib.extendFlat({}, dfltFont);
72736 if(isColorInheritedFromLayoutFont) {
72737 delete pathbarTextFontDefault.color;
72738 }
72739 coerceFont(coerce, 'pathbar.textfont', pathbarTextFontDefault);
72740 }
72741
72742 if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont);
72743
72744 if(moduleHasSelected) coerce('selected.textfont.color');
72745 if(moduleHasUnselected) coerce('unselected.textfont.color');
72746 if(moduleHasConstrain) coerce('constraintext');
72747 if(moduleHasCliponaxis) coerce('cliponaxis');
72748 if(moduleHasTextangle) coerce('textangle');
72749
72750 coerce('texttemplate');
72751 }
72752
72753 if(hasInside) {
72754 if(moduleHasInsideanchor) coerce('insidetextanchor');
72755 }
72756}
72757
72758module.exports = {
72759 supplyDefaults: supplyDefaults,
72760 crossTraceDefaults: crossTraceDefaults,
72761 handleGroupingDefaults: handleGroupingDefaults,
72762 handleText: handleText
72763};
72764
72765},{"../../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){
72766/**
72767* Copyright 2012-2020, Plotly, Inc.
72768* All rights reserved.
72769*
72770* This source code is licensed under the MIT license found in the
72771* LICENSE file in the root directory of this source tree.
72772*/
72773
72774'use strict';
72775
72776module.exports = function eventData(out, pt, trace) {
72777 // standard cartesian event data
72778 out.x = 'xVal' in pt ? pt.xVal : pt.x;
72779 out.y = 'yVal' in pt ? pt.yVal : pt.y;
72780 if(pt.xa) out.xaxis = pt.xa;
72781 if(pt.ya) out.yaxis = pt.ya;
72782
72783 if(trace.orientation === 'h') {
72784 out.label = out.y;
72785 out.value = out.x;
72786 } else {
72787 out.label = out.x;
72788 out.value = out.y;
72789 }
72790
72791 return out;
72792};
72793
72794},{}],285:[function(_dereq_,module,exports){
72795/**
72796* Copyright 2012-2020, Plotly, Inc.
72797* All rights reserved.
72798*
72799* This source code is licensed under the MIT license found in the
72800* LICENSE file in the root directory of this source tree.
72801*/
72802
72803'use strict';
72804
72805var isNumeric = _dereq_('fast-isnumeric');
72806var tinycolor = _dereq_('tinycolor2');
72807var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
72808
72809exports.coerceString = function(attributeDefinition, value, defaultValue) {
72810 if(typeof value === 'string') {
72811 if(value || !attributeDefinition.noBlank) return value;
72812 } else if(typeof value === 'number' || value === true) {
72813 if(!attributeDefinition.strict) return String(value);
72814 }
72815
72816 return (defaultValue !== undefined) ?
72817 defaultValue :
72818 attributeDefinition.dflt;
72819};
72820
72821exports.coerceNumber = function(attributeDefinition, value, defaultValue) {
72822 if(isNumeric(value)) {
72823 value = +value;
72824
72825 var min = attributeDefinition.min;
72826 var max = attributeDefinition.max;
72827 var isOutOfBounds = (min !== undefined && value < min) ||
72828 (max !== undefined && value > max);
72829
72830 if(!isOutOfBounds) return value;
72831 }
72832
72833 return (defaultValue !== undefined) ?
72834 defaultValue :
72835 attributeDefinition.dflt;
72836};
72837
72838exports.coerceColor = function(attributeDefinition, value, defaultValue) {
72839 if(tinycolor(value).isValid()) return value;
72840
72841 return (defaultValue !== undefined) ?
72842 defaultValue :
72843 attributeDefinition.dflt;
72844};
72845
72846exports.coerceEnumerated = function(attributeDefinition, value, defaultValue) {
72847 if(attributeDefinition.coerceNumber) value = +value;
72848
72849 if(attributeDefinition.values.indexOf(value) !== -1) return value;
72850
72851 return (defaultValue !== undefined) ?
72852 defaultValue :
72853 attributeDefinition.dflt;
72854};
72855
72856exports.getValue = function(arrayOrScalar, index) {
72857 var value;
72858 if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar;
72859 else if(index < arrayOrScalar.length) value = arrayOrScalar[index];
72860 return value;
72861};
72862
72863exports.getLineWidth = function(trace, di) {
72864 var w =
72865 (0 < di.mlw) ? di.mlw :
72866 !isArrayOrTypedArray(trace.marker.line.width) ? trace.marker.line.width :
72867 0;
72868
72869 return w;
72870};
72871
72872},{"../../lib":178,"fast-isnumeric":18,"tinycolor2":35}],286:[function(_dereq_,module,exports){
72873/**
72874* Copyright 2012-2020, Plotly, Inc.
72875* All rights reserved.
72876*
72877* This source code is licensed under the MIT license found in the
72878* LICENSE file in the root directory of this source tree.
72879*/
72880
72881
72882'use strict';
72883
72884var Fx = _dereq_('../../components/fx');
72885var Registry = _dereq_('../../registry');
72886var Color = _dereq_('../../components/color');
72887
72888var fillText = _dereq_('../../lib').fillText;
72889var getLineWidth = _dereq_('./helpers').getLineWidth;
72890var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
72891var BADNUM = _dereq_('../../constants/numerical').BADNUM;
72892
72893function hoverPoints(pointData, xval, yval, hovermode) {
72894 var barPointData = hoverOnBars(pointData, xval, yval, hovermode);
72895
72896 if(barPointData) {
72897 var cd = barPointData.cd;
72898 var trace = cd[0].trace;
72899 var di = cd[barPointData.index];
72900
72901 barPointData.color = getTraceColor(trace, di);
72902 Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, barPointData);
72903
72904 return [barPointData];
72905 }
72906}
72907
72908function hoverOnBars(pointData, xval, yval, hovermode) {
72909 var cd = pointData.cd;
72910 var trace = cd[0].trace;
72911 var t = cd[0].t;
72912 var isClosest = (hovermode === 'closest');
72913 var isWaterfall = (trace.type === 'waterfall');
72914 var maxHoverDistance = pointData.maxHoverDistance;
72915
72916 var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc;
72917
72918 function thisBarMinPos(di) { return di[posLetter] - di.w / 2; }
72919 function thisBarMaxPos(di) { return di[posLetter] + di.w / 2; }
72920
72921 var minPos = isClosest ?
72922 thisBarMinPos :
72923 function(di) {
72924 /*
72925 * In compare mode, accept a bar if you're on it *or* its group.
72926 * Nearly always it's the group that matters, but in case the bar
72927 * was explicitly set wider than its group we'd better accept the
72928 * whole bar.
72929 *
72930 * use `bardelta` instead of `bargroupwidth` so we accept hover
72931 * in the gap. That way hover doesn't flash on and off as you
72932 * mouse over the plot in compare modes.
72933 * In 'closest' mode though the flashing seems inevitable,
72934 * without far more complex logic
72935 */
72936 return Math.min(thisBarMinPos(di), di.p - t.bardelta / 2);
72937 };
72938
72939 var maxPos = isClosest ?
72940 thisBarMaxPos :
72941 function(di) {
72942 return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2);
72943 };
72944
72945 function _positionFn(_minPos, _maxPos) {
72946 // add a little to the pseudo-distance for wider bars, so that like scatter,
72947 // if you are over two overlapping bars, the narrower one wins.
72948 return Fx.inbox(_minPos - posVal, _maxPos - posVal,
72949 maxHoverDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1);
72950 }
72951
72952 function positionFn(di) {
72953 return _positionFn(minPos(di), maxPos(di));
72954 }
72955
72956 function thisBarPositionFn(di) {
72957 return _positionFn(thisBarMinPos(di), thisBarMaxPos(di));
72958 }
72959
72960 function sizeFn(di) {
72961 var v = sizeVal;
72962 var b = di.b;
72963 var s = di[sizeLetter];
72964
72965 if(isWaterfall) {
72966 var rawS = Math.abs(di.rawS) || 0;
72967 if(v > 0) {
72968 s += rawS;
72969 } else if(v < 0) {
72970 s -= rawS;
72971 }
72972 }
72973
72974 // add a gradient so hovering near the end of a
72975 // bar makes it a little closer match
72976 return Fx.inbox(b - v, s - v, maxHoverDistance + (s - v) / (s - b) - 1);
72977 }
72978
72979 if(trace.orientation === 'h') {
72980 posVal = yval;
72981 sizeVal = xval;
72982 posLetter = 'y';
72983 sizeLetter = 'x';
72984 dx = sizeFn;
72985 dy = positionFn;
72986 } else {
72987 posVal = xval;
72988 sizeVal = yval;
72989 posLetter = 'x';
72990 sizeLetter = 'y';
72991 dy = sizeFn;
72992 dx = positionFn;
72993 }
72994
72995 var pa = pointData[posLetter + 'a'];
72996 var sa = pointData[sizeLetter + 'a'];
72997
72998 pRangeCalc = Math.abs(pa.r2c(pa.range[1]) - pa.r2c(pa.range[0]));
72999
73000 function dxy(di) { return (dx(di) + dy(di)) / 2; }
73001 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
73002 Fx.getClosest(cd, distfn, pointData);
73003
73004 // skip the rest (for this trace) if we didn't find a close point
73005 if(pointData.index === false) return;
73006
73007 // skip points inside axis rangebreaks
73008 if(cd[pointData.index].p === BADNUM) return;
73009
73010 // if we get here and we're not in 'closest' mode, push min/max pos back
73011 // onto the group - even though that means occasionally the mouse will be
73012 // over the hover label.
73013 if(!isClosest) {
73014 minPos = function(di) {
73015 return Math.min(thisBarMinPos(di), di.p - t.bargroupwidth / 2);
73016 };
73017 maxPos = function(di) {
73018 return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2);
73019 };
73020 }
73021
73022 // the closest data point
73023 var index = pointData.index;
73024 var di = cd[index];
73025
73026 var size = (trace.base) ? di.b + di.s : di.s;
73027 pointData[sizeLetter + '0'] = pointData[sizeLetter + '1'] = sa.c2p(di[sizeLetter], true);
73028 pointData[sizeLetter + 'LabelVal'] = size;
73029
73030 var extent = t.extents[t.extents.round(di.p)];
73031 pointData[posLetter + '0'] = pa.c2p(isClosest ? minPos(di) : extent[0], true);
73032 pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true);
73033 pointData[posLetter + 'LabelVal'] = di.p;
73034
73035 pointData.labelLabel = hoverLabelText(pa, pointData[posLetter + 'LabelVal']);
73036 pointData.valueLabel = hoverLabelText(sa, pointData[sizeLetter + 'LabelVal']);
73037
73038 // spikelines always want "closest" distance regardless of hovermode
73039 pointData.spikeDistance = (sizeFn(di) + thisBarPositionFn(di)) / 2 - maxHoverDistance;
73040 // they also want to point to the data value, regardless of where the label goes
73041 // in case of bars shifted within groups
73042 pointData[posLetter + 'Spike'] = pa.c2p(di.p, true);
73043
73044 fillText(di, trace, pointData);
73045 pointData.hovertemplate = trace.hovertemplate;
73046
73047 return pointData;
73048}
73049
73050function getTraceColor(trace, di) {
73051 var mc = di.mcc || trace.marker.color;
73052 var mlc = di.mlcc || trace.marker.line.color;
73053 var mlw = getLineWidth(trace, di);
73054
73055 if(Color.opacity(mc)) return mc;
73056 else if(Color.opacity(mlc) && mlw) return mlc;
73057}
73058
73059module.exports = {
73060 hoverPoints: hoverPoints,
73061 hoverOnBars: hoverOnBars,
73062 getTraceColor: getTraceColor
73063};
73064
73065},{"../../components/color":52,"../../components/fx":92,"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"./helpers":285}],287:[function(_dereq_,module,exports){
73066/**
73067* Copyright 2012-2020, Plotly, Inc.
73068* All rights reserved.
73069*
73070* This source code is licensed under the MIT license found in the
73071* LICENSE file in the root directory of this source tree.
73072*/
73073
73074'use strict';
73075
73076module.exports = {
73077 attributes: _dereq_('./attributes'),
73078 layoutAttributes: _dereq_('./layout_attributes'),
73079 supplyDefaults: _dereq_('./defaults').supplyDefaults,
73080 crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
73081 supplyLayoutDefaults: _dereq_('./layout_defaults'),
73082 calc: _dereq_('./calc'),
73083 crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
73084 colorbar: _dereq_('../scatter/marker_colorbar'),
73085 arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
73086 plot: _dereq_('./plot').plot,
73087 style: _dereq_('./style').style,
73088 styleOnSelect: _dereq_('./style').styleOnSelect,
73089 hoverPoints: _dereq_('./hover').hoverPoints,
73090 eventData: _dereq_('./event_data'),
73091 selectPoints: _dereq_('./select'),
73092
73093 moduleType: 'trace',
73094 name: 'bar',
73095 basePlotModule: _dereq_('../../plots/cartesian'),
73096 categories: ['bar-like', 'cartesian', 'svg', 'bar', 'oriented', 'errorBarsOK', 'showLegend', 'zoomScale'],
73097 animatable: true,
73098 meta: {
73099
73100 }
73101};
73102
73103},{"../../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){
73104/**
73105* Copyright 2012-2020, Plotly, Inc.
73106* All rights reserved.
73107*
73108* This source code is licensed under the MIT license found in the
73109* LICENSE file in the root directory of this source tree.
73110*/
73111
73112'use strict';
73113
73114
73115module.exports = {
73116 barmode: {
73117 valType: 'enumerated',
73118 values: ['stack', 'group', 'overlay', 'relative'],
73119 dflt: 'group',
73120
73121 editType: 'calc',
73122
73123 },
73124 barnorm: {
73125 valType: 'enumerated',
73126 values: ['', 'fraction', 'percent'],
73127 dflt: '',
73128
73129 editType: 'calc',
73130
73131 },
73132 bargap: {
73133 valType: 'number',
73134 min: 0,
73135 max: 1,
73136
73137 editType: 'calc',
73138
73139 },
73140 bargroupgap: {
73141 valType: 'number',
73142 min: 0,
73143 max: 1,
73144 dflt: 0,
73145
73146 editType: 'calc',
73147
73148 }
73149};
73150
73151},{}],289:[function(_dereq_,module,exports){
73152/**
73153* Copyright 2012-2020, Plotly, Inc.
73154* All rights reserved.
73155*
73156* This source code is licensed under the MIT license found in the
73157* LICENSE file in the root directory of this source tree.
73158*/
73159
73160'use strict';
73161
73162var Registry = _dereq_('../../registry');
73163var Axes = _dereq_('../../plots/cartesian/axes');
73164var Lib = _dereq_('../../lib');
73165
73166var layoutAttributes = _dereq_('./layout_attributes');
73167
73168module.exports = function(layoutIn, layoutOut, fullData) {
73169 function coerce(attr, dflt) {
73170 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
73171 }
73172
73173 var hasBars = false;
73174 var shouldBeGapless = false;
73175 var gappedAnyway = false;
73176 var usedSubplots = {};
73177
73178 var mode = coerce('barmode');
73179
73180 for(var i = 0; i < fullData.length; i++) {
73181 var trace = fullData[i];
73182 if(Registry.traceIs(trace, 'bar') && trace.visible) hasBars = true;
73183 else continue;
73184
73185 // if we have at least 2 grouped bar traces on the same subplot,
73186 // we should default to a gap anyway, even if the data is histograms
73187 if(mode === 'group') {
73188 var subploti = trace.xaxis + trace.yaxis;
73189 if(usedSubplots[subploti]) gappedAnyway = true;
73190 usedSubplots[subploti] = true;
73191 }
73192
73193 if(trace.visible && trace.type === 'histogram') {
73194 var pa = Axes.getFromId({_fullLayout: layoutOut},
73195 trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']);
73196 if(pa.type !== 'category') shouldBeGapless = true;
73197 }
73198 }
73199
73200 if(!hasBars) {
73201 delete layoutOut.barmode;
73202 return;
73203 }
73204
73205 if(mode !== 'overlay') coerce('barnorm');
73206
73207 coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2);
73208 coerce('bargroupgap');
73209};
73210
73211},{"../../lib":178,"../../plots/cartesian/axes":222,"../../registry":269,"./layout_attributes":288}],290:[function(_dereq_,module,exports){
73212/**
73213* Copyright 2012-2020, Plotly, Inc.
73214* All rights reserved.
73215*
73216* This source code is licensed under the MIT license found in the
73217* LICENSE file in the root directory of this source tree.
73218*/
73219
73220'use strict';
73221
73222var d3 = _dereq_('d3');
73223var isNumeric = _dereq_('fast-isnumeric');
73224
73225var Lib = _dereq_('../../lib');
73226var svgTextUtils = _dereq_('../../lib/svg_text_utils');
73227
73228var Color = _dereq_('../../components/color');
73229var Drawing = _dereq_('../../components/drawing');
73230var Registry = _dereq_('../../registry');
73231var tickText = _dereq_('../../plots/cartesian/axes').tickText;
73232
73233var uniformText = _dereq_('./uniform_text');
73234var recordMinTextSize = uniformText.recordMinTextSize;
73235var clearMinTextSize = uniformText.clearMinTextSize;
73236
73237var style = _dereq_('./style');
73238var helpers = _dereq_('./helpers');
73239var constants = _dereq_('./constants');
73240var attributes = _dereq_('./attributes');
73241
73242var attributeText = attributes.text;
73243var attributeTextPosition = attributes.textposition;
73244
73245var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
73246
73247var TEXTPAD = constants.TEXTPAD;
73248
73249function keyFunc(d) {return d.id;}
73250function getKeyFunc(trace) {
73251 if(trace.ids) {
73252 return keyFunc;
73253 }
73254}
73255
73256function dirSign(a, b) {
73257 return (a < b) ? 1 : -1;
73258}
73259
73260function getXY(di, xa, ya, isHorizontal) {
73261 var s = [];
73262 var p = [];
73263
73264 var sAxis = isHorizontal ? xa : ya;
73265 var pAxis = isHorizontal ? ya : xa;
73266
73267 s[0] = sAxis.c2p(di.s0, true);
73268 p[0] = pAxis.c2p(di.p0, true);
73269
73270 s[1] = sAxis.c2p(di.s1, true);
73271 p[1] = pAxis.c2p(di.p1, true);
73272
73273 return isHorizontal ? [s, p] : [p, s];
73274}
73275
73276function transition(selection, fullLayout, opts, makeOnCompleteCallback) {
73277 if(!fullLayout.uniformtext.mode && hasTransition(opts)) {
73278 var onComplete;
73279 if(makeOnCompleteCallback) {
73280 onComplete = makeOnCompleteCallback();
73281 }
73282 return selection
73283 .transition()
73284 .duration(opts.duration)
73285 .ease(opts.easing)
73286 .each('end', function() { onComplete && onComplete(); })
73287 .each('interrupt', function() { onComplete && onComplete(); });
73288 } else {
73289 return selection;
73290 }
73291}
73292
73293function hasTransition(transitionOpts) {
73294 return transitionOpts && transitionOpts.duration > 0;
73295}
73296
73297function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) {
73298 var xa = plotinfo.xaxis;
73299 var ya = plotinfo.yaxis;
73300 var fullLayout = gd._fullLayout;
73301
73302 if(!opts) {
73303 opts = {
73304 mode: fullLayout.barmode,
73305 norm: fullLayout.barmode,
73306 gap: fullLayout.bargap,
73307 groupgap: fullLayout.bargroupgap
73308 };
73309
73310 // don't clear bar when this is called from waterfall or funnel
73311 clearMinTextSize('bar', fullLayout);
73312 }
73313
73314 var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
73315 var plotGroup = d3.select(this);
73316 var trace = cd[0].trace;
73317 var isWaterfall = (trace.type === 'waterfall');
73318 var isFunnel = (trace.type === 'funnel');
73319 var isBar = (trace.type === 'bar');
73320 var shouldDisplayZeros = (isBar || isFunnel);
73321
73322 var adjustPixel = 0;
73323 if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') {
73324 adjustPixel = trace.connector.line.width / 2;
73325 }
73326
73327 var isHorizontal = (trace.orientation === 'h');
73328 var withTransition = hasTransition(opts);
73329
73330 var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points');
73331
73332 var keyFunc = getKeyFunc(trace);
73333 var bars = pointGroup.selectAll('g.point').data(Lib.identity, keyFunc);
73334
73335 bars.enter().append('g')
73336 .classed('point', true);
73337
73338 bars.exit().remove();
73339
73340 bars.each(function(di, i) {
73341 var bar = d3.select(this);
73342
73343 // now display the bar
73344 // clipped xf/yf (2nd arg true): non-positive
73345 // log values go off-screen by plotwidth
73346 // so you see them continue if you drag the plot
73347 var xy = getXY(di, xa, ya, isHorizontal);
73348
73349 var x0 = xy[0][0];
73350 var x1 = xy[0][1];
73351 var y0 = xy[1][0];
73352 var y1 = xy[1][1];
73353
73354 // empty bars
73355 var isBlank = (isHorizontal ? x1 - x0 : y1 - y0) === 0;
73356
73357 // display zeros if line.width > 0
73358 if(isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di)) {
73359 isBlank = false;
73360 }
73361
73362 // skip nulls
73363 if(!isBlank) {
73364 isBlank = (
73365 !isNumeric(x0) ||
73366 !isNumeric(x1) ||
73367 !isNumeric(y0) ||
73368 !isNumeric(y1)
73369 );
73370 }
73371
73372 // record isBlank
73373 di.isBlank = isBlank;
73374
73375 // for blank bars, ensure start and end positions are equal - important for smooth transitions
73376 if(isBlank) {
73377 if(isHorizontal) {
73378 x1 = x0;
73379 } else {
73380 y1 = y0;
73381 }
73382 }
73383
73384 // in waterfall mode `between` we need to adjust bar end points to match the connector width
73385 if(adjustPixel && !isBlank) {
73386 if(isHorizontal) {
73387 x0 -= dirSign(x0, x1) * adjustPixel;
73388 x1 += dirSign(x0, x1) * adjustPixel;
73389 } else {
73390 y0 -= dirSign(y0, y1) * adjustPixel;
73391 y1 += dirSign(y0, y1) * adjustPixel;
73392 }
73393 }
73394
73395 var lw;
73396 var mc;
73397
73398 if(trace.type === 'waterfall') {
73399 if(!isBlank) {
73400 var cont = trace[di.dir].marker;
73401 lw = cont.line.width;
73402 mc = cont.color;
73403 }
73404 } else {
73405 lw = helpers.getLineWidth(trace, di);
73406 mc = di.mc || trace.marker.color;
73407 }
73408
73409 function roundWithLine(v) {
73410 var offset = d3.round((lw / 2) % 1, 2);
73411
73412 // if there are explicit gaps, don't round,
73413 // it can make the gaps look crappy
73414 return (opts.gap === 0 && opts.groupgap === 0) ?
73415 d3.round(Math.round(v) - offset, 2) : v;
73416 }
73417
73418 function expandToVisible(v, vc, hideZeroSpan) {
73419 if(hideZeroSpan && v === vc) {
73420 // should not expand zero span bars
73421 // when start and end positions are identical
73422 // i.e. for vertical when y0 === y1
73423 // and for horizontal when x0 === x1
73424 return v;
73425 }
73426
73427 // if it's not in danger of disappearing entirely,
73428 // round more precisely
73429 return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
73430 // but if it's very thin, expand it so it's
73431 // necessarily visible, even if it might overlap
73432 // its neighbor
73433 (v > vc ? Math.ceil(v) : Math.floor(v));
73434 }
73435
73436 if(!gd._context.staticPlot) {
73437 // if bars are not fully opaque or they have a line
73438 // around them, round to integer pixels, mainly for
73439 // safari so we prevent overlaps from its expansive
73440 // pixelation. if the bars ARE fully opaque and have
73441 // no line, expand to a full pixel to make sure we
73442 // can see them
73443
73444 var op = Color.opacity(mc);
73445 var fixpx = (op < 1 || lw > 0.01) ? roundWithLine : expandToVisible;
73446
73447 x0 = fixpx(x0, x1, isHorizontal);
73448 x1 = fixpx(x1, x0, isHorizontal);
73449 y0 = fixpx(y0, y1, !isHorizontal);
73450 y1 = fixpx(y1, y0, !isHorizontal);
73451 }
73452
73453 var sel = transition(Lib.ensureSingle(bar, 'path'), fullLayout, opts, makeOnCompleteCallback);
73454 sel
73455 .style('vector-effect', 'non-scaling-stroke')
73456 .attr('d', isNaN((x1 - x0) * (y1 - y0)) ? 'M0,0Z' : 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
73457 .call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
73458
73459 if(!fullLayout.uniformtext.mode && withTransition) {
73460 var styleFns = Drawing.makePointStyleFns(trace);
73461 Drawing.singlePointStyle(di, sel, trace, styleFns, gd);
73462 }
73463
73464 appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback);
73465
73466 if(plotinfo.layerClipId) {
73467 Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
73468 }
73469 });
73470
73471 // lastly, clip points groups of `cliponaxis !== false` traces
73472 // on `plotinfo._hasClipOnAxisFalse === true` subplots
73473 var hasClipOnAxisFalse = trace.cliponaxis === false;
73474 Drawing.setClipUrl(plotGroup, hasClipOnAxisFalse ? null : plotinfo.layerClipId, gd);
73475 });
73476
73477 // error bars are on the top
73478 Registry.getComponentMethod('errorbars', 'plot')(gd, bartraces, plotinfo, opts);
73479}
73480
73481function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) {
73482 var xa = plotinfo.xaxis;
73483 var ya = plotinfo.yaxis;
73484
73485 var fullLayout = gd._fullLayout;
73486 var textPosition;
73487
73488 function appendTextNode(bar, text, font) {
73489 var textSelection = Lib.ensureSingle(bar, 'text')
73490 .text(text)
73491 .attr({
73492 'class': 'bartext bartext-' + textPosition,
73493 'text-anchor': 'middle',
73494 // prohibit tex interpretation until we can handle
73495 // tex and regular text together
73496 'data-notex': 1
73497 })
73498 .call(Drawing.font, font)
73499 .call(svgTextUtils.convertToTspans, gd);
73500
73501 return textSelection;
73502 }
73503
73504 // get trace attributes
73505 var trace = cd[0].trace;
73506 var isHorizontal = (trace.orientation === 'h');
73507
73508 var text = getText(fullLayout, cd, i, xa, ya);
73509 textPosition = getTextPosition(trace, i);
73510
73511 // compute text position
73512 var inStackOrRelativeMode =
73513 opts.mode === 'stack' ||
73514 opts.mode === 'relative';
73515
73516 var calcBar = cd[i];
73517 var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost;
73518
73519 if(!text ||
73520 textPosition === 'none' ||
73521 ((calcBar.isBlank || x0 === x1 || y0 === y1) && (
73522 textPosition === 'auto' ||
73523 textPosition === 'inside'))) {
73524 bar.select('text').remove();
73525 return;
73526 }
73527
73528 var layoutFont = fullLayout.font;
73529 var barColor = style.getBarColor(cd[i], trace);
73530 var insideTextFont = style.getInsideTextFont(trace, i, layoutFont, barColor);
73531 var outsideTextFont = style.getOutsideTextFont(trace, i, layoutFont);
73532
73533 // Special case: don't use the c2p(v, true) value on log size axes,
73534 // so that we can get correctly inside text scaling
73535 var di = bar.datum();
73536 if(isHorizontal) {
73537 if(xa.type === 'log' && di.s0 <= 0) {
73538 if(xa.range[0] < xa.range[1]) {
73539 x0 = 0;
73540 } else {
73541 x0 = xa._length;
73542 }
73543 }
73544 } else {
73545 if(ya.type === 'log' && di.s0 <= 0) {
73546 if(ya.range[0] < ya.range[1]) {
73547 y0 = ya._length;
73548 } else {
73549 y0 = 0;
73550 }
73551 }
73552 }
73553
73554 // padding excluded
73555 var barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD;
73556 var barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD;
73557
73558 var textSelection;
73559 var textBB;
73560 var textWidth;
73561 var textHeight;
73562 var font;
73563
73564 if(textPosition === 'outside') {
73565 if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside';
73566 }
73567
73568 if(textPosition === 'auto') {
73569 if(isOutmostBar) {
73570 // draw text using insideTextFont and check if it fits inside bar
73571 textPosition = 'inside';
73572
73573 font = Lib.ensureUniformFontSize(gd, insideTextFont);
73574
73575 textSelection = appendTextNode(bar, text, font);
73576
73577 textBB = Drawing.bBox(textSelection.node()),
73578 textWidth = textBB.width,
73579 textHeight = textBB.height;
73580
73581 var textHasSize = (textWidth > 0 && textHeight > 0);
73582 var fitsInside = (textWidth <= barWidth && textHeight <= barHeight);
73583 var fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth);
73584 var fitsInsideIfShrunk = (isHorizontal) ?
73585 (barWidth >= textWidth * (barHeight / textHeight)) :
73586 (barHeight >= textHeight * (barWidth / textWidth));
73587
73588 if(textHasSize && (
73589 fitsInside ||
73590 fitsInsideIfRotated ||
73591 fitsInsideIfShrunk)
73592 ) {
73593 textPosition = 'inside';
73594 } else {
73595 textPosition = 'outside';
73596 textSelection.remove();
73597 textSelection = null;
73598 }
73599 } else {
73600 textPosition = 'inside';
73601 }
73602 }
73603
73604 if(!textSelection) {
73605 font = Lib.ensureUniformFontSize(gd, (textPosition === 'outside') ? outsideTextFont : insideTextFont);
73606
73607 textSelection = appendTextNode(bar, text, font);
73608
73609 var currentTransform = textSelection.attr('transform');
73610 textSelection.attr('transform', '');
73611 textBB = Drawing.bBox(textSelection.node()),
73612 textWidth = textBB.width,
73613 textHeight = textBB.height;
73614 textSelection.attr('transform', currentTransform);
73615
73616 if(textWidth <= 0 || textHeight <= 0) {
73617 textSelection.remove();
73618 return;
73619 }
73620 }
73621
73622 var angle = trace.textangle;
73623
73624 // compute text transform
73625 var transform, constrained;
73626 if(textPosition === 'outside') {
73627 constrained =
73628 trace.constraintext === 'both' ||
73629 trace.constraintext === 'outside';
73630
73631 transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, {
73632 isHorizontal: isHorizontal,
73633 constrained: constrained,
73634 angle: angle
73635 });
73636 } else {
73637 constrained =
73638 trace.constraintext === 'both' ||
73639 trace.constraintext === 'inside';
73640
73641 transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
73642 isHorizontal: isHorizontal,
73643 constrained: constrained,
73644 angle: angle,
73645 anchor: trace.insidetextanchor
73646 });
73647 }
73648
73649 transform.fontSize = font.size;
73650 recordMinTextSize(trace.type, transform, fullLayout);
73651 calcBar.transform = transform;
73652
73653 transition(textSelection, fullLayout, opts, makeOnCompleteCallback)
73654 .attr('transform', Lib.getTextTransform(transform));
73655}
73656
73657function getRotateFromAngle(angle) {
73658 return (angle === 'auto') ? 0 : angle;
73659}
73660
73661function getRotatedTextSize(textBB, rotate) {
73662 var a = Math.PI / 180 * rotate;
73663 var absSin = Math.abs(Math.sin(a));
73664 var absCos = Math.abs(Math.cos(a));
73665
73666 return {
73667 x: textBB.width * absCos + textBB.height * absSin,
73668 y: textBB.width * absSin + textBB.height * absCos
73669 };
73670}
73671
73672function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) {
73673 var isHorizontal = !!opts.isHorizontal;
73674 var constrained = !!opts.constrained;
73675 var angle = opts.angle || 0;
73676 var anchor = opts.anchor || 'end';
73677 var isEnd = anchor === 'end';
73678 var isStart = anchor === 'start';
73679 var leftToRight = opts.leftToRight || 0; // left: -1, center: 0, right: 1
73680 var toRight = (leftToRight + 1) / 2;
73681 var toLeft = 1 - toRight;
73682
73683 var textWidth = textBB.width;
73684 var textHeight = textBB.height;
73685 var lx = Math.abs(x1 - x0);
73686 var ly = Math.abs(y1 - y0);
73687
73688 // compute remaining space
73689 var textpad = (
73690 lx > (2 * TEXTPAD) &&
73691 ly > (2 * TEXTPAD)
73692 ) ? TEXTPAD : 0;
73693
73694 lx -= 2 * textpad;
73695 ly -= 2 * textpad;
73696
73697 var rotate = getRotateFromAngle(angle);
73698 if((angle === 'auto') &&
73699 !(textWidth <= lx && textHeight <= ly) &&
73700 (textWidth > lx || textHeight > ly) && (
73701 !(textWidth > ly || textHeight > lx) ||
73702 ((textWidth < textHeight) !== (lx < ly))
73703 )) {
73704 rotate += 90;
73705 }
73706
73707 var t = getRotatedTextSize(textBB, rotate);
73708
73709 var scale = 1;
73710 if(constrained) {
73711 scale = Math.min(
73712 1,
73713 lx / t.x,
73714 ly / t.y
73715 );
73716 }
73717
73718 // compute text and target positions
73719 var textX = (
73720 textBB.left * toLeft +
73721 textBB.right * toRight
73722 );
73723 var textY = (textBB.top + textBB.bottom) / 2;
73724 var targetX = (
73725 (x0 + TEXTPAD) * toLeft +
73726 (x1 - TEXTPAD) * toRight
73727 );
73728 var targetY = (y0 + y1) / 2;
73729 var anchorX = 0;
73730 var anchorY = 0;
73731 if(isStart || isEnd) {
73732 var extrapad = (isHorizontal ? t.x : t.y) / 2;
73733 var dir = isHorizontal ? dirSign(x0, x1) : dirSign(y0, y1);
73734
73735 if(isHorizontal) {
73736 if(isStart) {
73737 targetX = x0 + dir * textpad;
73738 anchorX = -dir * extrapad;
73739 } else {
73740 targetX = x1 - dir * textpad;
73741 anchorX = dir * extrapad;
73742 }
73743 } else {
73744 if(isStart) {
73745 targetY = y0 + dir * textpad;
73746 anchorY = -dir * extrapad;
73747 } else {
73748 targetY = y1 - dir * textpad;
73749 anchorY = dir * extrapad;
73750 }
73751 }
73752 }
73753
73754 return {
73755 textX: textX,
73756 textY: textY,
73757 targetX: targetX,
73758 targetY: targetY,
73759 anchorX: anchorX,
73760 anchorY: anchorY,
73761 scale: scale,
73762 rotate: rotate
73763 };
73764}
73765
73766function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) {
73767 var isHorizontal = !!opts.isHorizontal;
73768 var constrained = !!opts.constrained;
73769 var angle = opts.angle || 0;
73770
73771 var textWidth = textBB.width;
73772 var textHeight = textBB.height;
73773 var lx = Math.abs(x1 - x0);
73774 var ly = Math.abs(y1 - y0);
73775
73776 var textpad;
73777 // Keep the padding so the text doesn't sit right against
73778 // the bars, but don't factor it into barWidth
73779 if(isHorizontal) {
73780 textpad = (ly > 2 * TEXTPAD) ? TEXTPAD : 0;
73781 } else {
73782 textpad = (lx > 2 * TEXTPAD) ? TEXTPAD : 0;
73783 }
73784
73785 // compute rotate and scale
73786 var scale = 1;
73787 if(constrained) {
73788 scale = (isHorizontal) ?
73789 Math.min(1, ly / textHeight) :
73790 Math.min(1, lx / textWidth);
73791 }
73792
73793 var rotate = getRotateFromAngle(angle);
73794 var t = getRotatedTextSize(textBB, rotate);
73795
73796 // compute text and target positions
73797 var extrapad = (isHorizontal ? t.x : t.y) / 2;
73798 var textX = (textBB.left + textBB.right) / 2;
73799 var textY = (textBB.top + textBB.bottom) / 2;
73800 var targetX = (x0 + x1) / 2;
73801 var targetY = (y0 + y1) / 2;
73802 var anchorX = 0;
73803 var anchorY = 0;
73804
73805 var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1);
73806 if(isHorizontal) {
73807 targetX = x1 - dir * textpad;
73808 anchorX = dir * extrapad;
73809 } else {
73810 targetY = y1 + dir * textpad;
73811 anchorY = -dir * extrapad;
73812 }
73813
73814 return {
73815 textX: textX,
73816 textY: textY,
73817 targetX: targetX,
73818 targetY: targetY,
73819 anchorX: anchorX,
73820 anchorY: anchorY,
73821 scale: scale,
73822 rotate: rotate
73823 };
73824}
73825
73826function getText(fullLayout, cd, index, xa, ya) {
73827 var trace = cd[0].trace;
73828 var texttemplate = trace.texttemplate;
73829
73830 var value;
73831 if(texttemplate) {
73832 value = calcTexttemplate(fullLayout, cd, index, xa, ya);
73833 } else if(trace.textinfo) {
73834 value = calcTextinfo(cd, index, xa, ya);
73835 } else {
73836 value = helpers.getValue(trace.text, index);
73837 }
73838
73839 return helpers.coerceString(attributeText, value);
73840}
73841
73842function getTextPosition(trace, index) {
73843 var value = helpers.getValue(trace.textposition, index);
73844 return helpers.coerceEnumerated(attributeTextPosition, value);
73845}
73846
73847function calcTexttemplate(fullLayout, cd, index, xa, ya) {
73848 var trace = cd[0].trace;
73849 var texttemplate = Lib.castOption(trace, index, 'texttemplate');
73850 if(!texttemplate) return '';
73851 var isWaterfall = (trace.type === 'waterfall');
73852 var isFunnel = (trace.type === 'funnel');
73853
73854 var pLetter, pAxis;
73855 var vLetter, vAxis;
73856 if(trace.orientation === 'h') {
73857 pLetter = 'y';
73858 pAxis = ya;
73859 vLetter = 'x';
73860 vAxis = xa;
73861 } else {
73862 pLetter = 'x';
73863 pAxis = xa;
73864 vLetter = 'y';
73865 vAxis = ya;
73866 }
73867
73868 function formatLabel(u) {
73869 return tickText(pAxis, u, true).text;
73870 }
73871
73872 function formatNumber(v) {
73873 return tickText(vAxis, +v, true).text;
73874 }
73875
73876 var cdi = cd[index];
73877 var obj = {};
73878
73879 obj.label = cdi.p;
73880 obj.labelLabel = obj[pLetter + 'Label'] = formatLabel(cdi.p);
73881
73882 var tx = Lib.castOption(trace, cdi.i, 'text');
73883 if(tx === 0 || tx) obj.text = tx;
73884
73885 obj.value = cdi.s;
73886 obj.valueLabel = obj[vLetter + 'Label'] = formatNumber(cdi.s);
73887
73888 var pt = {};
73889 appendArrayPointValue(pt, trace, cdi.i);
73890
73891 if(isWaterfall) {
73892 obj.delta = +cdi.rawS || cdi.s;
73893 obj.deltaLabel = formatNumber(obj.delta);
73894 obj.final = cdi.v;
73895 obj.finalLabel = formatNumber(obj.final);
73896 obj.initial = obj.final - obj.delta;
73897 obj.initialLabel = formatNumber(obj.initial);
73898 }
73899
73900 if(isFunnel) {
73901 obj.value = cdi.s;
73902 obj.valueLabel = formatNumber(obj.value);
73903
73904 obj.percentInitial = cdi.begR;
73905 obj.percentInitialLabel = Lib.formatPercent(cdi.begR);
73906 obj.percentPrevious = cdi.difR;
73907 obj.percentPreviousLabel = Lib.formatPercent(cdi.difR);
73908 obj.percentTotal = cdi.sumR;
73909 obj.percenTotalLabel = Lib.formatPercent(cdi.sumR);
73910 }
73911
73912 var customdata = Lib.castOption(trace, cdi.i, 'customdata');
73913 if(customdata) obj.customdata = customdata;
73914 return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {});
73915}
73916
73917function calcTextinfo(cd, index, xa, ya) {
73918 var trace = cd[0].trace;
73919 var isHorizontal = (trace.orientation === 'h');
73920 var isWaterfall = (trace.type === 'waterfall');
73921 var isFunnel = (trace.type === 'funnel');
73922
73923 function formatLabel(u) {
73924 var pAxis = isHorizontal ? ya : xa;
73925 return tickText(pAxis, u, true).text;
73926 }
73927
73928 function formatNumber(v) {
73929 var sAxis = isHorizontal ? xa : ya;
73930 return tickText(sAxis, +v, true).text;
73931 }
73932
73933 var textinfo = trace.textinfo;
73934 var cdi = cd[index];
73935
73936 var parts = textinfo.split('+');
73937 var text = [];
73938 var tx;
73939
73940 var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
73941
73942 if(hasFlag('label')) {
73943 text.push(formatLabel(cd[index].p));
73944 }
73945
73946 if(hasFlag('text')) {
73947 tx = Lib.castOption(trace, cdi.i, 'text');
73948 if(tx === 0 || tx) text.push(tx);
73949 }
73950
73951 if(isWaterfall) {
73952 var delta = +cdi.rawS || cdi.s;
73953 var final = cdi.v;
73954 var initial = final - delta;
73955
73956 if(hasFlag('initial')) text.push(formatNumber(initial));
73957 if(hasFlag('delta')) text.push(formatNumber(delta));
73958 if(hasFlag('final')) text.push(formatNumber(final));
73959 }
73960
73961 if(isFunnel) {
73962 if(hasFlag('value')) text.push(formatNumber(cdi.s));
73963
73964 var nPercent = 0;
73965 if(hasFlag('percent initial')) nPercent++;
73966 if(hasFlag('percent previous')) nPercent++;
73967 if(hasFlag('percent total')) nPercent++;
73968
73969 var hasMultiplePercents = nPercent > 1;
73970
73971 if(hasFlag('percent initial')) {
73972 tx = Lib.formatPercent(cdi.begR);
73973 if(hasMultiplePercents) tx += ' of initial';
73974 text.push(tx);
73975 }
73976 if(hasFlag('percent previous')) {
73977 tx = Lib.formatPercent(cdi.difR);
73978 if(hasMultiplePercents) tx += ' of previous';
73979 text.push(tx);
73980 }
73981 if(hasFlag('percent total')) {
73982 tx = Lib.formatPercent(cdi.sumR);
73983 if(hasMultiplePercents) tx += ' of total';
73984 text.push(tx);
73985 }
73986 }
73987
73988 return text.join('<br>');
73989}
73990
73991module.exports = {
73992 plot: plot,
73993 toMoveInsideBar: toMoveInsideBar
73994};
73995
73996},{"../../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){
73997/**
73998* Copyright 2012-2020, Plotly, Inc.
73999* All rights reserved.
74000*
74001* This source code is licensed under the MIT license found in the
74002* LICENSE file in the root directory of this source tree.
74003*/
74004
74005'use strict';
74006
74007module.exports = function selectPoints(searchInfo, selectionTester) {
74008 var cd = searchInfo.cd;
74009 var xa = searchInfo.xaxis;
74010 var ya = searchInfo.yaxis;
74011 var trace = cd[0].trace;
74012 var isFunnel = (trace.type === 'funnel');
74013 var isHorizontal = (trace.orientation === 'h');
74014 var selection = [];
74015 var i;
74016
74017 if(selectionTester === false) {
74018 // clear selection
74019 for(i = 0; i < cd.length; i++) {
74020 cd[i].selected = 0;
74021 }
74022 } else {
74023 for(i = 0; i < cd.length; i++) {
74024 var di = cd[i];
74025 var ct = 'ct' in di ? di.ct : getCentroid(di, xa, ya, isHorizontal, isFunnel);
74026
74027 if(selectionTester.contains(ct, false, i, searchInfo)) {
74028 selection.push({
74029 pointNumber: i,
74030 x: xa.c2d(di.x),
74031 y: ya.c2d(di.y)
74032 });
74033 di.selected = 1;
74034 } else {
74035 di.selected = 0;
74036 }
74037 }
74038 }
74039
74040 return selection;
74041};
74042
74043function getCentroid(d, xa, ya, isHorizontal, isFunnel) {
74044 var x0 = xa.c2p(isHorizontal ? d.s0 : d.p0, true);
74045 var x1 = xa.c2p(isHorizontal ? d.s1 : d.p1, true);
74046 var y0 = ya.c2p(isHorizontal ? d.p0 : d.s0, true);
74047 var y1 = ya.c2p(isHorizontal ? d.p1 : d.s1, true);
74048
74049 if(isFunnel) {
74050 return [(x0 + x1) / 2, (y0 + y1) / 2];
74051 } else {
74052 if(isHorizontal) {
74053 return [x1, (y0 + y1) / 2];
74054 } else {
74055 return [(x0 + x1) / 2, y1];
74056 }
74057 }
74058}
74059
74060},{}],292:[function(_dereq_,module,exports){
74061/**
74062* Copyright 2012-2020, Plotly, Inc.
74063* All rights reserved.
74064*
74065* This source code is licensed under the MIT license found in the
74066* LICENSE file in the root directory of this source tree.
74067*/
74068
74069'use strict';
74070
74071module.exports = Sieve;
74072
74073var distinctVals = _dereq_('../../lib').distinctVals;
74074var BADNUM = _dereq_('../../constants/numerical').BADNUM;
74075
74076/**
74077 * Helper class to sieve data from traces into bins
74078 *
74079 * @class
74080 *
74081 * @param {Array} traces
74082* Array of calculated traces
74083 * @param {object} opts
74084 * - @param {boolean} [sepNegVal]
74085 * If true, then split data at the same position into a bar
74086 * for positive values and another for negative values
74087 * - @param {boolean} [overlapNoMerge]
74088 * If true, then don't merge overlapping bars into a single bar
74089 */
74090function Sieve(traces, opts) {
74091 this.traces = traces;
74092 this.sepNegVal = opts.sepNegVal;
74093 this.overlapNoMerge = opts.overlapNoMerge;
74094
74095 // for single-bin histograms - see histogram/calc
74096 var width1 = Infinity;
74097
74098 var positions = [];
74099 for(var i = 0; i < traces.length; i++) {
74100 var trace = traces[i];
74101 for(var j = 0; j < trace.length; j++) {
74102 var bar = trace[j];
74103 if(bar.p !== BADNUM) positions.push(bar.p);
74104 }
74105 if(trace[0] && trace[0].width1) {
74106 width1 = Math.min(trace[0].width1, width1);
74107 }
74108 }
74109 this.positions = positions;
74110
74111 var dv = distinctVals(positions);
74112 this.distinctPositions = dv.vals;
74113 if(dv.vals.length === 1 && width1 !== Infinity) this.minDiff = width1;
74114 else this.minDiff = Math.min(dv.minDiff, width1);
74115
74116 this.binWidth = this.minDiff;
74117
74118 this.bins = {};
74119}
74120
74121/**
74122 * Sieve datum
74123 *
74124 * @method
74125 * @param {number} position
74126 * @param {number} value
74127 * @returns {number} Previous bin value
74128 */
74129Sieve.prototype.put = function put(position, value) {
74130 var label = this.getLabel(position, value);
74131 var oldValue = this.bins[label] || 0;
74132
74133 this.bins[label] = oldValue + value;
74134
74135 return oldValue;
74136};
74137
74138/**
74139 * Get current bin value for a given datum
74140 *
74141 * @method
74142 * @param {number} position Position of datum
74143 * @param {number} [value] Value of datum
74144 * (required if this.sepNegVal is true)
74145 * @returns {number} Current bin value
74146 */
74147Sieve.prototype.get = function get(position, value) {
74148 var label = this.getLabel(position, value);
74149 return this.bins[label] || 0;
74150};
74151
74152/**
74153 * Get bin label for a given datum
74154 *
74155 * @method
74156 * @param {number} position Position of datum
74157 * @param {number} [value] Value of datum
74158 * (required if this.sepNegVal is true)
74159 * @returns {string} Bin label
74160 * (prefixed with a 'v' if value is negative and this.sepNegVal is
74161 * true; otherwise prefixed with '^')
74162 */
74163Sieve.prototype.getLabel = function getLabel(position, value) {
74164 var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^';
74165 var label = (this.overlapNoMerge) ?
74166 position :
74167 Math.round(position / this.binWidth);
74168 return prefix + label;
74169};
74170
74171},{"../../constants/numerical":158,"../../lib":178}],293:[function(_dereq_,module,exports){
74172/**
74173* Copyright 2012-2020, Plotly, Inc.
74174* All rights reserved.
74175*
74176* This source code is licensed under the MIT license found in the
74177* LICENSE file in the root directory of this source tree.
74178*/
74179
74180'use strict';
74181
74182var d3 = _dereq_('d3');
74183var Color = _dereq_('../../components/color');
74184var Drawing = _dereq_('../../components/drawing');
74185var Lib = _dereq_('../../lib');
74186var Registry = _dereq_('../../registry');
74187
74188var resizeText = _dereq_('./uniform_text').resizeText;
74189var attributes = _dereq_('./attributes');
74190var attributeTextFont = attributes.textfont;
74191var attributeInsideTextFont = attributes.insidetextfont;
74192var attributeOutsideTextFont = attributes.outsidetextfont;
74193var helpers = _dereq_('./helpers');
74194
74195function style(gd) {
74196 var s = d3.select(gd).selectAll('g.barlayer').selectAll('g.trace');
74197 resizeText(gd, s, 'bar');
74198
74199 var barcount = s.size();
74200 var fullLayout = gd._fullLayout;
74201
74202 // trace styling
74203 s.style('opacity', function(d) { return d[0].trace.opacity; })
74204
74205 // for gapless (either stacked or neighboring grouped) bars use
74206 // crispEdges to turn off antialiasing so an artificial gap
74207 // isn't introduced.
74208 .each(function(d) {
74209 if((fullLayout.barmode === 'stack' && barcount > 1) ||
74210 (fullLayout.bargap === 0 &&
74211 fullLayout.bargroupgap === 0 &&
74212 !d[0].trace.marker.line.width)) {
74213 d3.select(this).attr('shape-rendering', 'crispEdges');
74214 }
74215 });
74216
74217 s.selectAll('g.points').each(function(d) {
74218 var sel = d3.select(this);
74219 var trace = d[0].trace;
74220 stylePoints(sel, trace, gd);
74221 });
74222
74223 Registry.getComponentMethod('errorbars', 'style')(s);
74224}
74225
74226function stylePoints(sel, trace, gd) {
74227 Drawing.pointStyle(sel.selectAll('path'), trace, gd);
74228 styleTextPoints(sel, trace, gd);
74229}
74230
74231function styleTextPoints(sel, trace, gd) {
74232 sel.selectAll('text').each(function(d) {
74233 var tx = d3.select(this);
74234 var font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
74235
74236 Drawing.font(tx, font);
74237 });
74238}
74239
74240function styleOnSelect(gd, cd, sel) {
74241 var trace = cd[0].trace;
74242
74243 if(trace.selectedpoints) {
74244 stylePointsInSelectionMode(sel, trace, gd);
74245 } else {
74246 stylePoints(sel, trace, gd);
74247 Registry.getComponentMethod('errorbars', 'style')(sel);
74248 }
74249}
74250
74251function stylePointsInSelectionMode(s, trace, gd) {
74252 Drawing.selectedPointStyle(s.selectAll('path'), trace);
74253 styleTextInSelectionMode(s.selectAll('text'), trace, gd);
74254}
74255
74256function styleTextInSelectionMode(txs, trace, gd) {
74257 txs.each(function(d) {
74258 var tx = d3.select(this);
74259 var font;
74260
74261 if(d.selected) {
74262 font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
74263
74264 var selectedFontColor = trace.selected.textfont && trace.selected.textfont.color;
74265 if(selectedFontColor) {
74266 font.color = selectedFontColor;
74267 }
74268
74269 Drawing.font(tx, font);
74270 } else {
74271 Drawing.selectedTextStyle(tx, trace);
74272 }
74273 });
74274}
74275
74276function determineFont(tx, d, trace, gd) {
74277 var layoutFont = gd._fullLayout.font;
74278 var textFont = trace.textfont;
74279
74280 if(tx.classed('bartext-inside')) {
74281 var barColor = getBarColor(d, trace);
74282 textFont = getInsideTextFont(trace, d.i, layoutFont, barColor);
74283 } else if(tx.classed('bartext-outside')) {
74284 textFont = getOutsideTextFont(trace, d.i, layoutFont);
74285 }
74286
74287 return textFont;
74288}
74289
74290function getTextFont(trace, index, defaultValue) {
74291 return getFontValue(
74292 attributeTextFont, trace.textfont, index, defaultValue);
74293}
74294
74295function getInsideTextFont(trace, index, layoutFont, barColor) {
74296 var defaultFont = getTextFont(trace, index, layoutFont);
74297
74298 var wouldFallBackToLayoutFont =
74299 (trace._input.textfont === undefined || trace._input.textfont.color === undefined) ||
74300 (Array.isArray(trace.textfont.color) && trace.textfont.color[index] === undefined);
74301 if(wouldFallBackToLayoutFont) {
74302 defaultFont = {
74303 color: Color.contrast(barColor),
74304 family: defaultFont.family,
74305 size: defaultFont.size
74306 };
74307 }
74308
74309 return getFontValue(
74310 attributeInsideTextFont, trace.insidetextfont, index, defaultFont);
74311}
74312
74313function getOutsideTextFont(trace, index, layoutFont) {
74314 var defaultFont = getTextFont(trace, index, layoutFont);
74315 return getFontValue(
74316 attributeOutsideTextFont, trace.outsidetextfont, index, defaultFont);
74317}
74318
74319function getFontValue(attributeDefinition, attributeValue, index, defaultValue) {
74320 attributeValue = attributeValue || {};
74321
74322 var familyValue = helpers.getValue(attributeValue.family, index);
74323 var sizeValue = helpers.getValue(attributeValue.size, index);
74324 var colorValue = helpers.getValue(attributeValue.color, index);
74325
74326 return {
74327 family: helpers.coerceString(
74328 attributeDefinition.family, familyValue, defaultValue.family),
74329 size: helpers.coerceNumber(
74330 attributeDefinition.size, sizeValue, defaultValue.size),
74331 color: helpers.coerceColor(
74332 attributeDefinition.color, colorValue, defaultValue.color)
74333 };
74334}
74335
74336function getBarColor(cd, trace) {
74337 if(trace.type === 'waterfall') {
74338 return trace[cd.dir].marker.color;
74339 }
74340 return cd.mc || trace.marker.color;
74341}
74342
74343module.exports = {
74344 style: style,
74345 styleTextPoints: styleTextPoints,
74346 styleOnSelect: styleOnSelect,
74347 getInsideTextFont: getInsideTextFont,
74348 getOutsideTextFont: getOutsideTextFont,
74349 getBarColor: getBarColor,
74350 resizeText: resizeText
74351};
74352
74353},{"../../components/color":52,"../../components/drawing":74,"../../lib":178,"../../registry":269,"./attributes":279,"./helpers":285,"./uniform_text":295,"d3":16}],294:[function(_dereq_,module,exports){
74354/**
74355* Copyright 2012-2020, Plotly, Inc.
74356* All rights reserved.
74357*
74358* This source code is licensed under the MIT license found in the
74359* LICENSE file in the root directory of this source tree.
74360*/
74361
74362'use strict';
74363
74364var Color = _dereq_('../../components/color');
74365var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
74366var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
74367
74368module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) {
74369 coerce('marker.color', defaultColor);
74370
74371 if(hasColorscale(traceIn, 'marker')) {
74372 colorscaleDefaults(
74373 traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}
74374 );
74375 }
74376
74377 coerce('marker.line.color', Color.defaultLine);
74378
74379 if(hasColorscale(traceIn, 'marker.line')) {
74380 colorscaleDefaults(
74381 traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}
74382 );
74383 }
74384
74385 coerce('marker.line.width');
74386 coerce('marker.opacity');
74387 coerce('selected.marker.color');
74388 coerce('unselected.marker.color');
74389};
74390
74391},{"../../components/color":52,"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63}],295:[function(_dereq_,module,exports){
74392/**
74393* Copyright 2012-2020, Plotly, Inc.
74394* All rights reserved.
74395*
74396* This source code is licensed under the MIT license found in the
74397* LICENSE file in the root directory of this source tree.
74398*/
74399
74400'use strict';
74401
74402var d3 = _dereq_('d3');
74403var Lib = _dereq_('../../lib');
74404
74405function resizeText(gd, gTrace, traceType) {
74406 var fullLayout = gd._fullLayout;
74407 var minSize = fullLayout['_' + traceType + 'Text_minsize'];
74408 if(minSize) {
74409 var shouldHide = fullLayout.uniformtext.mode === 'hide';
74410
74411 var selector;
74412 switch(traceType) {
74413 case 'funnelarea' :
74414 case 'pie' :
74415 case 'sunburst' :
74416 selector = 'g.slice';
74417 break;
74418 case 'treemap' :
74419 selector = 'g.slice, g.pathbar';
74420 break;
74421 default :
74422 selector = 'g.points > g.point';
74423 }
74424
74425 gTrace.selectAll(selector).each(function(d) {
74426 var transform = d.transform;
74427 if(transform) {
74428 transform.scale = (shouldHide && transform.hide) ? 0 : minSize / transform.fontSize;
74429
74430 var el = d3.select(this).select('text');
74431 el.attr('transform', Lib.getTextTransform(transform));
74432 }
74433 });
74434 }
74435}
74436
74437function recordMinTextSize(
74438 traceType, // in
74439 transform, // inout
74440 fullLayout // inout
74441) {
74442 if(fullLayout.uniformtext.mode) {
74443 var minKey = getMinKey(traceType);
74444 var minSize = fullLayout.uniformtext.minsize;
74445 var size = transform.scale * transform.fontSize;
74446
74447 transform.hide = size < minSize;
74448
74449 fullLayout[minKey] = fullLayout[minKey] || Infinity;
74450 if(!transform.hide) {
74451 fullLayout[minKey] = Math.min(
74452 fullLayout[minKey],
74453 Math.max(size, minSize)
74454 );
74455 }
74456 }
74457}
74458
74459function clearMinTextSize(
74460 traceType, // in
74461 fullLayout // inout
74462) {
74463 var minKey = getMinKey(traceType);
74464 fullLayout[minKey] = undefined;
74465}
74466
74467function getMinKey(traceType) {
74468 return '_' + traceType + 'Text_minsize';
74469}
74470
74471module.exports = {
74472 recordMinTextSize: recordMinTextSize,
74473 clearMinTextSize: clearMinTextSize,
74474 resizeText: resizeText
74475};
74476
74477},{"../../lib":178,"d3":16}],296:[function(_dereq_,module,exports){
74478/**
74479* Copyright 2012-2020, Plotly, Inc.
74480* All rights reserved.
74481*
74482* This source code is licensed under the MIT license found in the
74483* LICENSE file in the root directory of this source tree.
74484*/
74485
74486'use strict';
74487
74488var scatterAttrs = _dereq_('../scatter/attributes');
74489var barAttrs = _dereq_('../bar/attributes');
74490var colorAttrs = _dereq_('../../components/color/attributes');
74491var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
74492var extendFlat = _dereq_('../../lib/extend').extendFlat;
74493
74494var scatterMarkerAttrs = scatterAttrs.marker;
74495var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
74496
74497module.exports = {
74498 y: {
74499 valType: 'data_array',
74500 editType: 'calc+clearAxisTypes',
74501
74502 },
74503 x: {
74504 valType: 'data_array',
74505 editType: 'calc+clearAxisTypes',
74506
74507 },
74508 x0: {
74509 valType: 'any',
74510
74511 editType: 'calc+clearAxisTypes',
74512
74513 },
74514 y0: {
74515 valType: 'any',
74516
74517 editType: 'calc+clearAxisTypes',
74518
74519 },
74520
74521 dx: {
74522 valType: 'number',
74523
74524 editType: 'calc',
74525
74526 },
74527 dy: {
74528 valType: 'number',
74529
74530 editType: 'calc',
74531
74532 },
74533
74534 name: {
74535 valType: 'string',
74536
74537 editType: 'calc+clearAxisTypes',
74538
74539 },
74540
74541 q1: {
74542 valType: 'data_array',
74543
74544 editType: 'calc+clearAxisTypes',
74545
74546 },
74547 median: {
74548 valType: 'data_array',
74549
74550 editType: 'calc+clearAxisTypes',
74551
74552 },
74553 q3: {
74554 valType: 'data_array',
74555
74556 editType: 'calc+clearAxisTypes',
74557
74558 },
74559 lowerfence: {
74560 valType: 'data_array',
74561
74562 editType: 'calc',
74563
74564 },
74565 upperfence: {
74566 valType: 'data_array',
74567
74568 editType: 'calc',
74569
74570 },
74571
74572 notched: {
74573 valType: 'boolean',
74574
74575 editType: 'calc',
74576
74577 },
74578 notchwidth: {
74579 valType: 'number',
74580 min: 0,
74581 max: 0.5,
74582 dflt: 0.25,
74583
74584 editType: 'calc',
74585
74586 },
74587 notchspan: {
74588 valType: 'data_array',
74589
74590 editType: 'calc',
74591
74592 },
74593
74594 // TODO
74595 // maybe add
74596 // - loweroutlierbound / upperoutlierbound
74597 // - lowersuspectedoutlierbound / uppersuspectedoutlierbound
74598
74599 boxpoints: {
74600 valType: 'enumerated',
74601 values: ['all', 'outliers', 'suspectedoutliers', false],
74602
74603 editType: 'calc',
74604
74605 },
74606 jitter: {
74607 valType: 'number',
74608 min: 0,
74609 max: 1,
74610
74611 editType: 'calc',
74612
74613 },
74614 pointpos: {
74615 valType: 'number',
74616 min: -2,
74617 max: 2,
74618
74619 editType: 'calc',
74620
74621 },
74622
74623 boxmean: {
74624 valType: 'enumerated',
74625 values: [true, 'sd', false],
74626
74627 editType: 'calc',
74628
74629 },
74630 mean: {
74631 valType: 'data_array',
74632
74633 editType: 'calc',
74634
74635 },
74636 sd: {
74637 valType: 'data_array',
74638
74639 editType: 'calc',
74640
74641 },
74642
74643 orientation: {
74644 valType: 'enumerated',
74645 values: ['v', 'h'],
74646
74647 editType: 'calc+clearAxisTypes',
74648
74649 },
74650
74651 quartilemethod: {
74652 valType: 'enumerated',
74653 values: ['linear', 'exclusive', 'inclusive'],
74654 dflt: 'linear',
74655
74656 editType: 'calc',
74657
74658 },
74659
74660 width: {
74661 valType: 'number',
74662 min: 0,
74663
74664 dflt: 0,
74665 editType: 'calc',
74666
74667 },
74668
74669 marker: {
74670 outliercolor: {
74671 valType: 'color',
74672 dflt: 'rgba(0, 0, 0, 0)',
74673
74674 editType: 'style',
74675
74676 },
74677 symbol: extendFlat({}, scatterMarkerAttrs.symbol,
74678 {arrayOk: false, editType: 'plot'}),
74679 opacity: extendFlat({}, scatterMarkerAttrs.opacity,
74680 {arrayOk: false, dflt: 1, editType: 'style'}),
74681 size: extendFlat({}, scatterMarkerAttrs.size,
74682 {arrayOk: false, editType: 'calc'}),
74683 color: extendFlat({}, scatterMarkerAttrs.color,
74684 {arrayOk: false, editType: 'style'}),
74685 line: {
74686 color: extendFlat({}, scatterMarkerLineAttrs.color,
74687 {arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'}
74688 ),
74689 width: extendFlat({}, scatterMarkerLineAttrs.width,
74690 {arrayOk: false, dflt: 0, editType: 'style'}
74691 ),
74692 outliercolor: {
74693 valType: 'color',
74694
74695 editType: 'style',
74696
74697 },
74698 outlierwidth: {
74699 valType: 'number',
74700 min: 0,
74701 dflt: 1,
74702
74703 editType: 'style',
74704
74705 },
74706 editType: 'style'
74707 },
74708 editType: 'plot'
74709 },
74710
74711 line: {
74712 color: {
74713 valType: 'color',
74714
74715 editType: 'style',
74716
74717 },
74718 width: {
74719 valType: 'number',
74720
74721 min: 0,
74722 dflt: 2,
74723 editType: 'style',
74724
74725 },
74726 editType: 'plot'
74727 },
74728
74729 fillcolor: scatterAttrs.fillcolor,
74730
74731 whiskerwidth: {
74732 valType: 'number',
74733 min: 0,
74734 max: 1,
74735 dflt: 0.5,
74736
74737 editType: 'calc',
74738
74739 },
74740
74741 offsetgroup: barAttrs.offsetgroup,
74742 alignmentgroup: barAttrs.alignmentgroup,
74743
74744 selected: {
74745 marker: scatterAttrs.selected.marker,
74746 editType: 'style'
74747 },
74748 unselected: {
74749 marker: scatterAttrs.unselected.marker,
74750 editType: 'style'
74751 },
74752
74753 text: extendFlat({}, scatterAttrs.text, {
74754
74755 }),
74756 hovertext: extendFlat({}, scatterAttrs.hovertext, {
74757
74758 }),
74759 hovertemplate: hovertemplateAttrs({
74760
74761 }),
74762
74763 hoveron: {
74764 valType: 'flaglist',
74765 flags: ['boxes', 'points'],
74766 dflt: 'boxes+points',
74767
74768 editType: 'style',
74769
74770 }
74771};
74772
74773},{"../../components/color/attributes":51,"../../lib/extend":173,"../../plots/template_attributes":264,"../bar/attributes":279,"../scatter/attributes":389}],297:[function(_dereq_,module,exports){
74774/**
74775* Copyright 2012-2020, Plotly, Inc.
74776* All rights reserved.
74777*
74778* This source code is licensed under the MIT license found in the
74779* LICENSE file in the root directory of this source tree.
74780*/
74781
74782'use strict';
74783
74784var isNumeric = _dereq_('fast-isnumeric');
74785
74786var Axes = _dereq_('../../plots/cartesian/axes');
74787var Lib = _dereq_('../../lib');
74788
74789var BADNUM = _dereq_('../../constants/numerical').BADNUM;
74790var _ = Lib._;
74791
74792module.exports = function calc(gd, trace) {
74793 var fullLayout = gd._fullLayout;
74794 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
74795 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
74796 var cd = [];
74797
74798 // N.B. violin reuses same Box.calc
74799 var numKey = trace.type === 'violin' ? '_numViolins' : '_numBoxes';
74800
74801 var i, j;
74802 var valAxis, valLetter;
74803 var posAxis, posLetter;
74804
74805 if(trace.orientation === 'h') {
74806 valAxis = xa;
74807 valLetter = 'x';
74808 posAxis = ya;
74809 posLetter = 'y';
74810 } else {
74811 valAxis = ya;
74812 valLetter = 'y';
74813 posAxis = xa;
74814 posLetter = 'x';
74815 }
74816
74817 var posArray = getPos(trace, posLetter, posAxis, fullLayout[numKey]);
74818 var dv = Lib.distinctVals(posArray);
74819 var posDistinct = dv.vals;
74820 var dPos = dv.minDiff / 2;
74821
74822 // item in trace calcdata
74823 var cdi;
74824 // array of {v: v, i, i} sample pts
74825 var pts;
74826 // values of the `pts` array of objects
74827 var boxVals;
74828 // length of sample
74829 var N;
74830 // single sample point
74831 var pt;
74832 // single sample value
74833 var v;
74834
74835 // filter function for outlier pts
74836 // outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
74837 var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ?
74838 Lib.identity :
74839 function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); };
74840
74841 if(trace._hasPreCompStats) {
74842 var valArrayRaw = trace[valLetter];
74843 var d2c = function(k) { return valAxis.d2c((trace[k] || [])[i]); };
74844 var minVal = Infinity;
74845 var maxVal = -Infinity;
74846
74847 for(i = 0; i < trace._length; i++) {
74848 var posi = posArray[i];
74849 if(!isNumeric(posi)) continue;
74850
74851 cdi = {};
74852 cdi.pos = cdi[posLetter] = posi;
74853
74854 cdi.q1 = d2c('q1');
74855 cdi.med = d2c('median');
74856 cdi.q3 = d2c('q3');
74857
74858 pts = [];
74859 if(valArrayRaw && Lib.isArrayOrTypedArray(valArrayRaw[i])) {
74860 for(j = 0; j < valArrayRaw[i].length; j++) {
74861 v = valAxis.d2c(valArrayRaw[i][j]);
74862 if(v !== BADNUM) {
74863 pt = {v: v, i: [i, j]};
74864 arraysToCalcdata(pt, trace, [i, j]);
74865 pts.push(pt);
74866 }
74867 }
74868 }
74869 cdi.pts = pts.sort(sortByVal);
74870 boxVals = cdi[valLetter] = pts.map(extractVal);
74871 N = boxVals.length;
74872
74873 if(cdi.med !== BADNUM && cdi.q1 !== BADNUM && cdi.q3 !== BADNUM &&
74874 cdi.med >= cdi.q1 && cdi.q3 >= cdi.med
74875 ) {
74876 var lf = d2c('lowerfence');
74877 cdi.lf = (lf !== BADNUM && lf <= cdi.q1) ?
74878 lf :
74879 computeLowerFence(cdi, boxVals, N);
74880
74881 var uf = d2c('upperfence');
74882 cdi.uf = (uf !== BADNUM && uf >= cdi.q3) ?
74883 uf :
74884 computeUpperFence(cdi, boxVals, N);
74885
74886 var mean = d2c('mean');
74887 cdi.mean = (mean !== BADNUM) ?
74888 mean :
74889 (N ? Lib.mean(boxVals, N) : (cdi.q1 + cdi.q3) / 2);
74890
74891 var sd = d2c('sd');
74892 cdi.sd = (mean !== BADNUM && sd >= 0) ?
74893 sd :
74894 (N ? Lib.stdev(boxVals, N, cdi.mean) : (cdi.q3 - cdi.q1));
74895
74896 cdi.lo = computeLowerOutlierBound(cdi);
74897 cdi.uo = computeUpperOutlierBound(cdi);
74898
74899 var ns = d2c('notchspan');
74900 ns = (ns !== BADNUM && ns > 0) ? ns : computeNotchSpan(cdi, N);
74901 cdi.ln = cdi.med - ns;
74902 cdi.un = cdi.med + ns;
74903
74904 var imin = cdi.lf;
74905 var imax = cdi.uf;
74906 if(trace.boxpoints && boxVals.length) {
74907 imin = Math.min(imin, boxVals[0]);
74908 imax = Math.max(imax, boxVals[N - 1]);
74909 }
74910 if(trace.notched) {
74911 imin = Math.min(imin, cdi.ln);
74912 imax = Math.max(imax, cdi.un);
74913 }
74914 cdi.min = imin;
74915 cdi.max = imax;
74916 } else {
74917 Lib.warn([
74918 'Invalid input - make sure that q1 <= median <= q3',
74919 'q1 = ' + cdi.q1,
74920 'median = ' + cdi.med,
74921 'q3 = ' + cdi.q3
74922 ].join('\n'));
74923
74924 var v0;
74925 if(cdi.med !== BADNUM) {
74926 v0 = cdi.med;
74927 } else if(cdi.q1 !== BADNUM) {
74928 if(cdi.q3 !== BADNUM) v0 = (cdi.q1 + cdi.q3) / 2;
74929 else v0 = cdi.q1;
74930 } else if(cdi.q3 !== BADNUM) {
74931 v0 = cdi.q3;
74932 } else {
74933 v0 = 0;
74934 }
74935
74936 // draw box as line segment
74937 cdi.med = v0;
74938 cdi.q1 = cdi.q3 = v0;
74939 cdi.lf = cdi.uf = v0;
74940 cdi.mean = cdi.sd = v0;
74941 cdi.ln = cdi.un = v0;
74942 cdi.min = cdi.max = v0;
74943 }
74944
74945 minVal = Math.min(minVal, cdi.min);
74946 maxVal = Math.max(maxVal, cdi.max);
74947
74948 cdi.pts2 = pts.filter(ptFilterFn);
74949
74950 cd.push(cdi);
74951 }
74952
74953 trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
74954 [minVal, maxVal],
74955 {padded: true}
74956 );
74957 } else {
74958 var valArray = valAxis.makeCalcdata(trace, valLetter);
74959 var posBins = makeBins(posDistinct, dPos);
74960 var pLen = posDistinct.length;
74961 var ptsPerBin = initNestedArray(pLen);
74962
74963 // bin pts info per position bins
74964 for(i = 0; i < trace._length; i++) {
74965 v = valArray[i];
74966 if(!isNumeric(v)) continue;
74967
74968 var n = Lib.findBin(posArray[i], posBins);
74969 if(n >= 0 && n < pLen) {
74970 pt = {v: v, i: i};
74971 arraysToCalcdata(pt, trace, i);
74972 ptsPerBin[n].push(pt);
74973 }
74974 }
74975
74976 var minLowerNotch = Infinity;
74977 var maxUpperNotch = -Infinity;
74978
74979 var quartilemethod = trace.quartilemethod;
74980 var usesExclusive = quartilemethod === 'exclusive';
74981 var usesInclusive = quartilemethod === 'inclusive';
74982
74983 // build calcdata trace items, one item per distinct position
74984 for(i = 0; i < pLen; i++) {
74985 if(ptsPerBin[i].length > 0) {
74986 cdi = {};
74987 cdi.pos = cdi[posLetter] = posDistinct[i];
74988
74989 pts = cdi.pts = ptsPerBin[i].sort(sortByVal);
74990 boxVals = cdi[valLetter] = pts.map(extractVal);
74991 N = boxVals.length;
74992
74993 cdi.min = boxVals[0];
74994 cdi.max = boxVals[N - 1];
74995 cdi.mean = Lib.mean(boxVals, N);
74996 cdi.sd = Lib.stdev(boxVals, N, cdi.mean);
74997 cdi.med = Lib.interp(boxVals, 0.5);
74998
74999 if((N % 2) && (usesExclusive || usesInclusive)) {
75000 var lower;
75001 var upper;
75002
75003 if(usesExclusive) {
75004 // do NOT include the median in either half
75005 lower = boxVals.slice(0, N / 2);
75006 upper = boxVals.slice(N / 2 + 1);
75007 } else if(usesInclusive) {
75008 // include the median in either half
75009 lower = boxVals.slice(0, N / 2 + 1);
75010 upper = boxVals.slice(N / 2);
75011 }
75012
75013 cdi.q1 = Lib.interp(lower, 0.5);
75014 cdi.q3 = Lib.interp(upper, 0.5);
75015 } else {
75016 cdi.q1 = Lib.interp(boxVals, 0.25);
75017 cdi.q3 = Lib.interp(boxVals, 0.75);
75018 }
75019
75020 // lower and upper fences
75021 cdi.lf = computeLowerFence(cdi, boxVals, N);
75022 cdi.uf = computeUpperFence(cdi, boxVals, N);
75023
75024 // lower and upper outliers bounds
75025 cdi.lo = computeLowerOutlierBound(cdi);
75026 cdi.uo = computeUpperOutlierBound(cdi);
75027
75028 // lower and upper notches
75029 var mci = computeNotchSpan(cdi, N);
75030 cdi.ln = cdi.med - mci;
75031 cdi.un = cdi.med + mci;
75032 minLowerNotch = Math.min(minLowerNotch, cdi.ln);
75033 maxUpperNotch = Math.max(maxUpperNotch, cdi.un);
75034
75035 cdi.pts2 = pts.filter(ptFilterFn);
75036
75037 cd.push(cdi);
75038 }
75039 }
75040
75041 trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
75042 trace.notched ? valArray.concat([minLowerNotch, maxUpperNotch]) : valArray,
75043 {padded: true}
75044 );
75045 }
75046
75047 calcSelection(cd, trace);
75048
75049 if(cd.length > 0) {
75050 cd[0].t = {
75051 num: fullLayout[numKey],
75052 dPos: dPos,
75053 posLetter: posLetter,
75054 valLetter: valLetter,
75055 labels: {
75056 med: _(gd, 'median:'),
75057 min: _(gd, 'min:'),
75058 q1: _(gd, 'q1:'),
75059 q3: _(gd, 'q3:'),
75060 max: _(gd, 'max:'),
75061 mean: trace.boxmean === 'sd' ? _(gd, 'mean ± σ:') : _(gd, 'mean:'),
75062 lf: _(gd, 'lower fence:'),
75063 uf: _(gd, 'upper fence:')
75064 }
75065 };
75066
75067 fullLayout[numKey]++;
75068 return cd;
75069 } else {
75070 return [{t: {empty: true}}];
75071 }
75072};
75073
75074// In vertical (horizontal) box plots:
75075// if no x (y) data, use x0 (y0), or name
75076// so if you want one box
75077// per trace, set x0 (y0) to the x (y) value or category for this trace
75078// (or set x (y) to a constant array matching y (x))
75079function getPos(trace, posLetter, posAxis, num) {
75080 var hasPosArray = posLetter in trace;
75081 var hasPos0 = posLetter + '0' in trace;
75082 var hasPosStep = 'd' + posLetter in trace;
75083
75084 if(hasPosArray || (hasPos0 && hasPosStep)) {
75085 return posAxis.makeCalcdata(trace, posLetter);
75086 }
75087
75088 var pos0;
75089 if(hasPos0) {
75090 pos0 = trace[posLetter + '0'];
75091 } else if('name' in trace && (
75092 posAxis.type === 'category' || (
75093 isNumeric(trace.name) &&
75094 ['linear', 'log'].indexOf(posAxis.type) !== -1
75095 ) || (
75096 Lib.isDateTime(trace.name) &&
75097 posAxis.type === 'date'
75098 )
75099 )) {
75100 pos0 = trace.name;
75101 } else {
75102 pos0 = num;
75103 }
75104
75105 var pos0c = posAxis.type === 'multicategory' ?
75106 posAxis.r2c_just_indices(pos0) :
75107 posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']);
75108
75109 var len = trace._length;
75110 var out = new Array(len);
75111 for(var i = 0; i < len; i++) out[i] = pos0c;
75112
75113 return out;
75114}
75115
75116function makeBins(x, dx) {
75117 var len = x.length;
75118 var bins = new Array(len + 1);
75119
75120 for(var i = 0; i < len; i++) {
75121 bins[i] = x[i] - dx;
75122 }
75123 bins[len] = x[len - 1] + dx;
75124
75125 return bins;
75126}
75127
75128function initNestedArray(len) {
75129 var arr = new Array(len);
75130 for(var i = 0; i < len; i++) {
75131 arr[i] = [];
75132 }
75133 return arr;
75134}
75135
75136var TRACE_TO_CALC = {
75137 text: 'tx',
75138 hovertext: 'htx'
75139};
75140
75141function arraysToCalcdata(pt, trace, ptNumber) {
75142 for(var k in TRACE_TO_CALC) {
75143 if(Lib.isArrayOrTypedArray(trace[k])) {
75144 if(Array.isArray(ptNumber)) {
75145 if(Lib.isArrayOrTypedArray(trace[k][ptNumber[0]])) {
75146 pt[TRACE_TO_CALC[k]] = trace[k][ptNumber[0]][ptNumber[1]];
75147 }
75148 } else {
75149 pt[TRACE_TO_CALC[k]] = trace[k][ptNumber];
75150 }
75151 }
75152 }
75153}
75154
75155function calcSelection(cd, trace) {
75156 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
75157 for(var i = 0; i < cd.length; i++) {
75158 var pts = cd[i].pts || [];
75159 var ptNumber2cdIndex = {};
75160
75161 for(var j = 0; j < pts.length; j++) {
75162 ptNumber2cdIndex[pts[j].i] = j;
75163 }
75164
75165 Lib.tagSelected(pts, trace, ptNumber2cdIndex);
75166 }
75167 }
75168}
75169
75170function sortByVal(a, b) { return a.v - b.v; }
75171
75172function extractVal(o) { return o.v; }
75173
75174// last point below 1.5 * IQR
75175function computeLowerFence(cdi, boxVals, N) {
75176 if(N === 0) return cdi.q1;
75177 return Math.min(
75178 cdi.q1,
75179 boxVals[Math.min(
75180 Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, boxVals, true) + 1,
75181 N - 1
75182 )]
75183 );
75184}
75185
75186// last point above 1.5 * IQR
75187function computeUpperFence(cdi, boxVals, N) {
75188 if(N === 0) return cdi.q3;
75189 return Math.max(
75190 cdi.q3,
75191 boxVals[Math.max(
75192 Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, boxVals),
75193 0
75194 )]
75195 );
75196}
75197
75198// 3 IQR below (don't clip to max/min,
75199// this is only for discriminating suspected & far outliers)
75200function computeLowerOutlierBound(cdi) {
75201 return 4 * cdi.q1 - 3 * cdi.q3;
75202}
75203
75204// 3 IQR above (don't clip to max/min,
75205// this is only for discriminating suspected & far outliers)
75206function computeUpperOutlierBound(cdi) {
75207 return 4 * cdi.q3 - 3 * cdi.q1;
75208}
75209
75210// 95% confidence intervals for median
75211function computeNotchSpan(cdi, N) {
75212 if(N === 0) return 0;
75213 return 1.57 * (cdi.q3 - cdi.q1) / Math.sqrt(N);
75214}
75215
75216},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"fast-isnumeric":18}],298:[function(_dereq_,module,exports){
75217/**
75218* Copyright 2012-2020, Plotly, Inc.
75219* All rights reserved.
75220*
75221* This source code is licensed under the MIT license found in the
75222* LICENSE file in the root directory of this source tree.
75223*/
75224
75225'use strict';
75226
75227var Axes = _dereq_('../../plots/cartesian/axes');
75228var Lib = _dereq_('../../lib');
75229var getAxisGroup = _dereq_('../../plots/cartesian/axis_ids').getAxisGroup;
75230
75231var orientations = ['v', 'h'];
75232
75233function crossTraceCalc(gd, plotinfo) {
75234 var calcdata = gd.calcdata;
75235 var xa = plotinfo.xaxis;
75236 var ya = plotinfo.yaxis;
75237
75238 for(var i = 0; i < orientations.length; i++) {
75239 var orientation = orientations[i];
75240 var posAxis = orientation === 'h' ? ya : xa;
75241 var boxList = [];
75242
75243 // make list of boxes / candlesticks
75244 // For backward compatibility, candlesticks are treated as if they *are* box traces here
75245 for(var j = 0; j < calcdata.length; j++) {
75246 var cd = calcdata[j];
75247 var t = cd[0].t;
75248 var trace = cd[0].trace;
75249
75250 if(trace.visible === true &&
75251 (trace.type === 'box' || trace.type === 'candlestick') &&
75252 !t.empty &&
75253 (trace.orientation || 'v') === orientation &&
75254 trace.xaxis === xa._id &&
75255 trace.yaxis === ya._id
75256 ) {
75257 boxList.push(j);
75258 }
75259 }
75260
75261 setPositionOffset('box', gd, boxList, posAxis);
75262 }
75263}
75264
75265function setPositionOffset(traceType, gd, boxList, posAxis) {
75266 var calcdata = gd.calcdata;
75267 var fullLayout = gd._fullLayout;
75268 var axId = posAxis._id;
75269 var axLetter = axId.charAt(0);
75270
75271 var i, j, calcTrace;
75272 var pointList = [];
75273 var shownPts = 0;
75274
75275 // make list of box points
75276 for(i = 0; i < boxList.length; i++) {
75277 calcTrace = calcdata[boxList[i]];
75278 for(j = 0; j < calcTrace.length; j++) {
75279 pointList.push(posAxis.c2l(calcTrace[j].pos, true));
75280 shownPts += (calcTrace[j].pts2 || []).length;
75281 }
75282 }
75283
75284 if(!pointList.length) return;
75285
75286 // box plots - update dPos based on multiple traces
75287 var boxdv = Lib.distinctVals(pointList);
75288 var dPos0 = boxdv.minDiff / 2;
75289
75290 // check for forced minimum dtick
75291 Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);
75292
75293 var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
75294 var numTotal = fullLayout[numKey];
75295 var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1;
75296 var groupFraction = 1 - fullLayout[traceType + 'gap'];
75297 var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];
75298
75299 for(i = 0; i < boxList.length; i++) {
75300 calcTrace = calcdata[boxList[i]];
75301
75302 var trace = calcTrace[0].trace;
75303 var t = calcTrace[0].t;
75304 var width = trace.width;
75305 var side = trace.side;
75306
75307 // position coordinate delta
75308 var dPos;
75309 // box half width;
75310 var bdPos;
75311 // box center offset
75312 var bPos;
75313 // half-width within which to accept hover for this box/violin
75314 // always split the distance to the closest box/violin
75315 var wHover;
75316
75317 if(width) {
75318 dPos = bdPos = wHover = width / 2;
75319 bPos = 0;
75320 } else {
75321 dPos = dPos0;
75322
75323 if(group) {
75324 var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation;
75325 var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
75326 var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
75327 var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
75328 var num = nOffsetGroups || numTotal;
75329 var shift = nOffsetGroups ? trace._offsetIndex : t.num;
75330
75331 bdPos = dPos * groupFraction * groupGapFraction / num;
75332 bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction;
75333 wHover = dPos * groupFraction / num;
75334 } else {
75335 bdPos = dPos * groupFraction * groupGapFraction;
75336 bPos = 0;
75337 wHover = dPos;
75338 }
75339 }
75340 t.dPos = dPos;
75341 t.bPos = bPos;
75342 t.bdPos = bdPos;
75343 t.wHover = wHover;
75344
75345 // box/violin-only value-space push value
75346 var pushplus;
75347 var pushminus;
75348 // edge of box/violin
75349 var edge = bPos + bdPos;
75350 var edgeplus;
75351 var edgeminus;
75352 // value-space padding
75353 var vpadplus;
75354 var vpadminus;
75355 // pixel-space padding
75356 var ppadplus;
75357 var ppadminus;
75358 // do we add 5% of both sides (more logic for points beyond box/violin below)
75359 var padded = Boolean(width);
75360 // does this trace show points?
75361 var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0);
75362
75363 if(side === 'positive') {
75364 pushplus = dPos * (width ? 1 : 0.5);
75365 edgeplus = edge;
75366 pushminus = edgeplus = bPos;
75367 } else if(side === 'negative') {
75368 pushplus = edgeplus = bPos;
75369 pushminus = dPos * (width ? 1 : 0.5);
75370 edgeminus = edge;
75371 } else {
75372 pushplus = pushminus = dPos;
75373 edgeplus = edgeminus = edge;
75374 }
75375
75376 if(hasPts) {
75377 var pointpos = trace.pointpos;
75378 var jitter = trace.jitter;
75379 var ms = trace.marker.size / 2;
75380
75381 var pp = 0;
75382 if((pointpos + jitter) >= 0) {
75383 pp = edge * (pointpos + jitter);
75384 if(pp > pushplus) {
75385 // (++) beyond plus-value, use pp
75386 padded = true;
75387 ppadplus = ms;
75388 vpadplus = pp;
75389 } else if(pp > edgeplus) {
75390 // (+), use push-value (it's bigger), but add px-pad
75391 ppadplus = ms;
75392 vpadplus = pushplus;
75393 }
75394 }
75395 if(pp <= pushplus) {
75396 // (->) fallback to push value
75397 vpadplus = pushplus;
75398 }
75399
75400 var pm = 0;
75401 if((pointpos - jitter) <= 0) {
75402 pm = -edge * (pointpos - jitter);
75403 if(pm > pushminus) {
75404 // (--) beyond plus-value, use pp
75405 padded = true;
75406 ppadminus = ms;
75407 vpadminus = pm;
75408 } else if(pm > edgeminus) {
75409 // (-), use push-value (it's bigger), but add px-pad
75410 ppadminus = ms;
75411 vpadminus = pushminus;
75412 }
75413 }
75414 if(pm <= pushminus) {
75415 // (<-) fallback to push value
75416 vpadminus = pushminus;
75417 }
75418 } else {
75419 vpadplus = pushplus;
75420 vpadminus = pushminus;
75421 }
75422
75423 var pos = new Array(calcTrace.length);
75424 for(j = 0; j < calcTrace.length; j++) {
75425 pos[j] = calcTrace[j].pos;
75426 }
75427
75428 trace._extremes[axId] = Axes.findExtremes(posAxis, pos, {
75429 padded: padded,
75430 vpadminus: vpadminus,
75431 vpadplus: vpadplus,
75432 vpadLinearized: true,
75433 // N.B. SVG px-space positive/negative
75434 ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
75435 ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
75436 });
75437 }
75438}
75439
75440module.exports = {
75441 crossTraceCalc: crossTraceCalc,
75442 setPositionOffset: setPositionOffset
75443};
75444
75445},{"../../lib":178,"../../plots/cartesian/axes":222,"../../plots/cartesian/axis_ids":225}],299:[function(_dereq_,module,exports){
75446/**
75447* Copyright 2012-2020, Plotly, Inc.
75448* All rights reserved.
75449*
75450* This source code is licensed under the MIT license found in the
75451* LICENSE file in the root directory of this source tree.
75452*/
75453
75454'use strict';
75455
75456var Lib = _dereq_('../../lib');
75457var Registry = _dereq_('../../registry');
75458var Color = _dereq_('../../components/color');
75459var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
75460var autoType = _dereq_('../../plots/cartesian/axis_autotype');
75461var attributes = _dereq_('./attributes');
75462
75463function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
75464 function coerce(attr, dflt) {
75465 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
75466 }
75467
75468 handleSampleDefaults(traceIn, traceOut, coerce, layout);
75469 if(traceOut.visible === false) return;
75470
75471 var hasPreCompStats = traceOut._hasPreCompStats;
75472
75473 if(hasPreCompStats) {
75474 coerce('lowerfence');
75475 coerce('upperfence');
75476 }
75477
75478 coerce('line.color', (traceIn.marker || {}).color || defaultColor);
75479 coerce('line.width');
75480 coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
75481
75482 var boxmeanDflt = false;
75483 if(hasPreCompStats) {
75484 var mean = coerce('mean');
75485 var sd = coerce('sd');
75486 if(mean && mean.length) {
75487 boxmeanDflt = true;
75488 if(sd && sd.length) boxmeanDflt = 'sd';
75489 }
75490 }
75491 coerce('boxmean', boxmeanDflt);
75492
75493 coerce('whiskerwidth');
75494 coerce('width');
75495 coerce('quartilemethod');
75496
75497 var notchedDflt = false;
75498 if(hasPreCompStats) {
75499 var notchspan = coerce('notchspan');
75500 if(notchspan && notchspan.length) {
75501 notchedDflt = true;
75502 }
75503 } else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) {
75504 notchedDflt = true;
75505 }
75506 var notched = coerce('notched', notchedDflt);
75507 if(notched) coerce('notchwidth');
75508
75509 handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
75510}
75511
75512function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
75513 function getDims(arr) {
75514 var dims = 0;
75515 if(arr && arr.length) {
75516 dims += 1;
75517 if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) {
75518 dims += 1;
75519 }
75520 }
75521 return dims;
75522 }
75523
75524 function valid(astr) {
75525 return Lib.validate(traceIn[astr], attributes[astr]);
75526 }
75527
75528 var y = coerce('y');
75529 var x = coerce('x');
75530
75531 var sLen;
75532 if(traceOut.type === 'box') {
75533 var q1 = coerce('q1');
75534 var median = coerce('median');
75535 var q3 = coerce('q3');
75536
75537 traceOut._hasPreCompStats = (
75538 q1 && q1.length &&
75539 median && median.length &&
75540 q3 && q3.length
75541 );
75542 sLen = Math.min(
75543 Lib.minRowLength(q1),
75544 Lib.minRowLength(median),
75545 Lib.minRowLength(q3)
75546 );
75547 }
75548
75549 var yDims = getDims(y);
75550 var xDims = getDims(x);
75551 var yLen = yDims && Lib.minRowLength(y);
75552 var xLen = xDims && Lib.minRowLength(x);
75553
75554 var defaultOrientation, len;
75555 if(traceOut._hasPreCompStats) {
75556 switch(String(xDims) + String(yDims)) {
75557 // no x / no y
75558 case '00':
75559 var setInX = valid('x0') || valid('dx');
75560 var setInY = valid('y0') || valid('dy');
75561
75562 if(setInY && !setInX) {
75563 defaultOrientation = 'h';
75564 } else {
75565 defaultOrientation = 'v';
75566 }
75567
75568 len = sLen;
75569 break;
75570 // just x
75571 case '10':
75572 defaultOrientation = 'v';
75573 len = Math.min(sLen, xLen);
75574 break;
75575 case '20':
75576 defaultOrientation = 'h';
75577 len = Math.min(sLen, x.length);
75578 break;
75579 // just y
75580 case '01':
75581 defaultOrientation = 'h';
75582 len = Math.min(sLen, yLen);
75583 break;
75584 case '02':
75585 defaultOrientation = 'v';
75586 len = Math.min(sLen, y.length);
75587 break;
75588 // both
75589 case '12':
75590 defaultOrientation = 'v';
75591 len = Math.min(sLen, xLen, y.length);
75592 break;
75593 case '21':
75594 defaultOrientation = 'h';
75595 len = Math.min(sLen, x.length, yLen);
75596 break;
75597 case '11':
75598 // this one is ill-defined
75599 len = 0;
75600 break;
75601 case '22':
75602 var hasCategories = false;
75603 var i;
75604 for(i = 0; i < x.length; i++) {
75605 if(autoType(x[i]) === 'category') {
75606 hasCategories = true;
75607 break;
75608 }
75609 }
75610
75611 if(hasCategories) {
75612 defaultOrientation = 'v';
75613 len = Math.min(sLen, xLen, y.length);
75614 } else {
75615 for(i = 0; i < y.length; i++) {
75616 if(autoType(y[i]) === 'category') {
75617 hasCategories = true;
75618 break;
75619 }
75620 }
75621
75622 if(hasCategories) {
75623 defaultOrientation = 'h';
75624 len = Math.min(sLen, x.length, yLen);
75625 } else {
75626 defaultOrientation = 'v';
75627 len = Math.min(sLen, xLen, y.length);
75628 }
75629 }
75630 break;
75631 }
75632 } else if(yDims > 0) {
75633 defaultOrientation = 'v';
75634 if(xDims > 0) {
75635 len = Math.min(xLen, yLen);
75636 } else {
75637 len = Math.min(yLen);
75638 }
75639 } else if(xDims > 0) {
75640 defaultOrientation = 'h';
75641 len = Math.min(xLen);
75642 } else {
75643 len = 0;
75644 }
75645
75646 if(!len) {
75647 traceOut.visible = false;
75648 return;
75649 }
75650 traceOut._length = len;
75651
75652 var orientation = coerce('orientation', defaultOrientation);
75653
75654 // these are just used for positioning, they never define the sample
75655 if(traceOut._hasPreCompStats) {
75656 if(orientation === 'v' && xDims === 0) {
75657 coerce('x0', 0);
75658 coerce('dx', 1);
75659 } else if(orientation === 'h' && yDims === 0) {
75660 coerce('y0', 0);
75661 coerce('dy', 1);
75662 }
75663 } else {
75664 if(orientation === 'v' && xDims === 0) {
75665 coerce('x0');
75666 } else if(orientation === 'h' && yDims === 0) {
75667 coerce('y0');
75668 }
75669 }
75670
75671 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
75672 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
75673}
75674
75675function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
75676 var prefix = opts.prefix;
75677
75678 var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor');
75679 var lineoutliercolor = coerce('marker.line.outliercolor');
75680
75681 var modeDflt = 'outliers';
75682 if(traceOut._hasPreCompStats) {
75683 modeDflt = 'all';
75684 } else if(outlierColorDflt || lineoutliercolor) {
75685 modeDflt = 'suspectedoutliers';
75686 }
75687
75688 var mode = coerce(prefix + 'points', modeDflt);
75689
75690 if(mode) {
75691 coerce('jitter', mode === 'all' ? 0.3 : 0);
75692 coerce('pointpos', mode === 'all' ? -1.5 : 0);
75693
75694 coerce('marker.symbol');
75695 coerce('marker.opacity');
75696 coerce('marker.size');
75697 coerce('marker.color', traceOut.line.color);
75698 coerce('marker.line.color');
75699 coerce('marker.line.width');
75700
75701 if(mode === 'suspectedoutliers') {
75702 coerce('marker.line.outliercolor', traceOut.marker.color);
75703 coerce('marker.line.outlierwidth');
75704 }
75705
75706 coerce('selected.marker.color');
75707 coerce('unselected.marker.color');
75708 coerce('selected.marker.size');
75709 coerce('unselected.marker.size');
75710
75711 coerce('text');
75712 coerce('hovertext');
75713 } else {
75714 delete traceOut.marker;
75715 }
75716
75717 var hoveron = coerce('hoveron');
75718 if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
75719 coerce('hovertemplate');
75720 }
75721
75722 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
75723}
75724
75725function crossTraceDefaults(fullData, fullLayout) {
75726 var traceIn, traceOut;
75727
75728 function coerce(attr) {
75729 return Lib.coerce(traceOut._input, traceOut, attributes, attr);
75730 }
75731
75732 for(var i = 0; i < fullData.length; i++) {
75733 traceOut = fullData[i];
75734 var traceType = traceOut.type;
75735
75736 if(traceType === 'box' || traceType === 'violin') {
75737 traceIn = traceOut._input;
75738 if(fullLayout[traceType + 'mode'] === 'group') {
75739 handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
75740 }
75741 }
75742 }
75743}
75744
75745module.exports = {
75746 supplyDefaults: supplyDefaults,
75747 crossTraceDefaults: crossTraceDefaults,
75748
75749 handleSampleDefaults: handleSampleDefaults,
75750 handlePointsDefaults: handlePointsDefaults
75751};
75752
75753},{"../../components/color":52,"../../lib":178,"../../plots/cartesian/axis_autotype":223,"../../registry":269,"../bar/defaults":283,"./attributes":296}],300:[function(_dereq_,module,exports){
75754/**
75755* Copyright 2012-2020, Plotly, Inc.
75756* All rights reserved.
75757*
75758* This source code is licensed under the MIT license found in the
75759* LICENSE file in the root directory of this source tree.
75760*/
75761
75762'use strict';
75763
75764module.exports = function eventData(out, pt) {
75765 // Note: hoverOnBox property is needed for click-to-select
75766 // to ignore when a box was clicked. This is the reason box
75767 // implements this custom eventData function.
75768 if(pt.hoverOnBox) out.hoverOnBox = pt.hoverOnBox;
75769
75770 if('xVal' in pt) out.x = pt.xVal;
75771 if('yVal' in pt) out.y = pt.yVal;
75772 if(pt.xa) out.xaxis = pt.xa;
75773 if(pt.ya) out.yaxis = pt.ya;
75774
75775 return out;
75776};
75777
75778},{}],301:[function(_dereq_,module,exports){
75779/**
75780* Copyright 2012-2020, Plotly, Inc.
75781* All rights reserved.
75782*
75783* This source code is licensed under the MIT license found in the
75784* LICENSE file in the root directory of this source tree.
75785*/
75786
75787'use strict';
75788
75789var Axes = _dereq_('../../plots/cartesian/axes');
75790var Lib = _dereq_('../../lib');
75791var Fx = _dereq_('../../components/fx');
75792var Color = _dereq_('../../components/color');
75793var fillText = Lib.fillText;
75794
75795function hoverPoints(pointData, xval, yval, hovermode) {
75796 var cd = pointData.cd;
75797 var trace = cd[0].trace;
75798 var hoveron = trace.hoveron;
75799 var closeBoxData = [];
75800 var closePtData;
75801
75802 if(hoveron.indexOf('boxes') !== -1) {
75803 closeBoxData = closeBoxData.concat(hoverOnBoxes(pointData, xval, yval, hovermode));
75804 }
75805
75806 if(hoveron.indexOf('points') !== -1) {
75807 closePtData = hoverOnPoints(pointData, xval, yval);
75808 }
75809
75810 // If there's a point in range and hoveron has points, show the best single point only.
75811 // If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
75812 if(hovermode === 'closest') {
75813 if(closePtData) return [closePtData];
75814 return closeBoxData;
75815 }
75816
75817 // Otherwise in compare mode, allow a point AND the box stats to be labeled
75818 // If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
75819 if(closePtData) {
75820 closeBoxData.push(closePtData);
75821 return closeBoxData;
75822 }
75823 return closeBoxData;
75824}
75825
75826function hoverOnBoxes(pointData, xval, yval, hovermode) {
75827 var cd = pointData.cd;
75828 var xa = pointData.xa;
75829 var ya = pointData.ya;
75830 var trace = cd[0].trace;
75831 var t = cd[0].t;
75832 var isViolin = trace.type === 'violin';
75833 var closeBoxData = [];
75834
75835 var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos,
75836 hoverPseudoDistance, spikePseudoDistance;
75837
75838 var boxDelta = t.bdPos;
75839 var boxDeltaPos, boxDeltaNeg;
75840 var posAcceptance = t.wHover;
75841 var shiftPos = function(di) { return pAxis.c2l(di.pos) + t.bPos - pAxis.c2l(pVal); };
75842
75843 if(isViolin && trace.side !== 'both') {
75844 if(trace.side === 'positive') {
75845 dPos = function(di) {
75846 var pos = shiftPos(di);
75847 return Fx.inbox(pos, pos + posAcceptance, hoverPseudoDistance);
75848 };
75849 boxDeltaPos = boxDelta;
75850 boxDeltaNeg = 0;
75851 }
75852 if(trace.side === 'negative') {
75853 dPos = function(di) {
75854 var pos = shiftPos(di);
75855 return Fx.inbox(pos - posAcceptance, pos, hoverPseudoDistance);
75856 };
75857 boxDeltaPos = 0;
75858 boxDeltaNeg = boxDelta;
75859 }
75860 } else {
75861 dPos = function(di) {
75862 var pos = shiftPos(di);
75863 return Fx.inbox(pos - posAcceptance, pos + posAcceptance, hoverPseudoDistance);
75864 };
75865 boxDeltaPos = boxDeltaNeg = boxDelta;
75866 }
75867
75868 var dVal;
75869
75870 if(isViolin) {
75871 dVal = function(di) {
75872 return Fx.inbox(di.span[0] - vVal, di.span[1] - vVal, hoverPseudoDistance);
75873 };
75874 } else {
75875 dVal = function(di) {
75876 return Fx.inbox(di.min - vVal, di.max - vVal, hoverPseudoDistance);
75877 };
75878 }
75879
75880 if(trace.orientation === 'h') {
75881 vVal = xval;
75882 pVal = yval;
75883 dx = dVal;
75884 dy = dPos;
75885 pLetter = 'y';
75886 pAxis = ya;
75887 vLetter = 'x';
75888 vAxis = xa;
75889 } else {
75890 vVal = yval;
75891 pVal = xval;
75892 dx = dPos;
75893 dy = dVal;
75894 pLetter = 'x';
75895 pAxis = xa;
75896 vLetter = 'y';
75897 vAxis = ya;
75898 }
75899
75900 // if two boxes are overlaying, let the narrowest one win
75901 var pseudoDistance = Math.min(1, boxDelta / Math.abs(pAxis.r2c(pAxis.range[1]) - pAxis.r2c(pAxis.range[0])));
75902 hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
75903 spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
75904
75905 function dxy(di) { return (dx(di) + dy(di)) / 2; }
75906 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
75907 Fx.getClosest(cd, distfn, pointData);
75908
75909 // skip the rest (for this trace) if we didn't find a close point
75910 // and create the item(s) in closedata for this point
75911 if(pointData.index === false) return [];
75912
75913 var di = cd[pointData.index];
75914 var lc = trace.line.color;
75915 var mc = (trace.marker || {}).color;
75916
75917 if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
75918 else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
75919 else pointData.color = trace.fillcolor;
75920
75921 pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDeltaNeg, true);
75922 pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDeltaPos, true);
75923
75924 pointData[pLetter + 'LabelVal'] = di.pos;
75925
75926 var spikePosAttr = pLetter + 'Spike';
75927 pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
75928 pointData[spikePosAttr] = pAxis.c2p(di.pos, true);
75929
75930 // box plots: each "point" gets many labels
75931 var usedVals = {};
75932 var attrs = ['med', 'q1', 'q3', 'min', 'max'];
75933
75934 if(trace.boxmean || (trace.meanline || {}).visible) {
75935 attrs.push('mean');
75936 }
75937 if(trace.boxpoints || trace.points) {
75938 attrs.push('lf', 'uf');
75939 }
75940
75941 for(var i = 0; i < attrs.length; i++) {
75942 var attr = attrs[i];
75943
75944 if(!(attr in di) || (di[attr] in usedVals)) continue;
75945 usedVals[di[attr]] = true;
75946
75947 // copy out to a new object for each value to label
75948 var val = di[attr];
75949 var valPx = vAxis.c2p(val, true);
75950 var pointData2 = Lib.extendFlat({}, pointData);
75951
75952 pointData2.attr = attr;
75953 pointData2[vLetter + '0'] = pointData2[vLetter + '1'] = valPx;
75954 pointData2[vLetter + 'LabelVal'] = val;
75955 pointData2[vLetter + 'Label'] = (t.labels ? t.labels[attr] + ' ' : '') + Axes.hoverLabelText(vAxis, val);
75956
75957 // Note: introduced to be able to distinguish a
75958 // clicked point from a box during click-to-select
75959 pointData2.hoverOnBox = true;
75960
75961 if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
75962 pointData2[vLetter + 'err'] = di.sd;
75963 }
75964
75965 // only keep name and spikes on the first item (median)
75966 pointData.name = '';
75967 pointData.spikeDistance = undefined;
75968 pointData[spikePosAttr] = undefined;
75969
75970 // no hovertemplate support yet
75971 pointData2.hovertemplate = false;
75972
75973 closeBoxData.push(pointData2);
75974 }
75975
75976 return closeBoxData;
75977}
75978
75979function hoverOnPoints(pointData, xval, yval) {
75980 var cd = pointData.cd;
75981 var xa = pointData.xa;
75982 var ya = pointData.ya;
75983 var trace = cd[0].trace;
75984 var xPx = xa.c2p(xval);
75985 var yPx = ya.c2p(yval);
75986 var closePtData;
75987
75988 var dx = function(di) {
75989 var rad = Math.max(3, di.mrc || 0);
75990 return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad);
75991 };
75992 var dy = function(di) {
75993 var rad = Math.max(3, di.mrc || 0);
75994 return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad);
75995 };
75996 var distfn = Fx.quadrature(dx, dy);
75997
75998 // show one point per trace
75999 var ijClosest = false;
76000 var di, pt;
76001
76002 for(var i = 0; i < cd.length; i++) {
76003 di = cd[i];
76004
76005 for(var j = 0; j < (di.pts || []).length; j++) {
76006 pt = di.pts[j];
76007
76008 var newDistance = distfn(pt);
76009 if(newDistance <= pointData.distance) {
76010 pointData.distance = newDistance;
76011 ijClosest = [i, j];
76012 }
76013 }
76014 }
76015
76016 if(!ijClosest) return false;
76017
76018 di = cd[ijClosest[0]];
76019 pt = di.pts[ijClosest[1]];
76020
76021 var xc = xa.c2p(pt.x, true);
76022 var yc = ya.c2p(pt.y, true);
76023 var rad = pt.mrc || 1;
76024
76025 closePtData = Lib.extendFlat({}, pointData, {
76026 // corresponds to index in x/y input data array
76027 index: pt.i,
76028 color: (trace.marker || {}).color,
76029 name: trace.name,
76030 x0: xc - rad,
76031 x1: xc + rad,
76032 y0: yc - rad,
76033 y1: yc + rad,
76034 spikeDistance: pointData.distance,
76035 hovertemplate: trace.hovertemplate
76036 });
76037
76038 var pa;
76039 if(trace.orientation === 'h') {
76040 pa = ya;
76041 closePtData.xLabelVal = pt.x;
76042 closePtData.yLabelVal = di.pos;
76043 } else {
76044 pa = xa;
76045 closePtData.xLabelVal = di.pos;
76046 closePtData.yLabelVal = pt.y;
76047 }
76048
76049 var pLetter = pa._id.charAt(0);
76050 closePtData[pLetter + 'Spike'] = pa.c2p(di.pos, true);
76051
76052 fillText(pt, trace, closePtData);
76053
76054 return closePtData;
76055}
76056
76057module.exports = {
76058 hoverPoints: hoverPoints,
76059 hoverOnBoxes: hoverOnBoxes,
76060 hoverOnPoints: hoverOnPoints
76061};
76062
76063},{"../../components/color":52,"../../components/fx":92,"../../lib":178,"../../plots/cartesian/axes":222}],302:[function(_dereq_,module,exports){
76064/**
76065* Copyright 2012-2020, Plotly, Inc.
76066* All rights reserved.
76067*
76068* This source code is licensed under the MIT license found in the
76069* LICENSE file in the root directory of this source tree.
76070*/
76071
76072'use strict';
76073
76074module.exports = {
76075 attributes: _dereq_('./attributes'),
76076 layoutAttributes: _dereq_('./layout_attributes'),
76077 supplyDefaults: _dereq_('./defaults').supplyDefaults,
76078 crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
76079 supplyLayoutDefaults: _dereq_('./layout_defaults').supplyLayoutDefaults,
76080 calc: _dereq_('./calc'),
76081 crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
76082 plot: _dereq_('./plot').plot,
76083 style: _dereq_('./style').style,
76084 styleOnSelect: _dereq_('./style').styleOnSelect,
76085 hoverPoints: _dereq_('./hover').hoverPoints,
76086 eventData: _dereq_('./event_data'),
76087 selectPoints: _dereq_('./select'),
76088
76089 moduleType: 'trace',
76090 name: 'box',
76091 basePlotModule: _dereq_('../../plots/cartesian'),
76092 categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'boxLayout', 'zoomScale'],
76093 meta: {
76094
76095 }
76096};
76097
76098},{"../../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){
76099/**
76100* Copyright 2012-2020, Plotly, Inc.
76101* All rights reserved.
76102*
76103* This source code is licensed under the MIT license found in the
76104* LICENSE file in the root directory of this source tree.
76105*/
76106
76107'use strict';
76108
76109
76110module.exports = {
76111 boxmode: {
76112 valType: 'enumerated',
76113 values: ['group', 'overlay'],
76114 dflt: 'overlay',
76115
76116 editType: 'calc',
76117
76118 },
76119 boxgap: {
76120 valType: 'number',
76121 min: 0,
76122 max: 1,
76123 dflt: 0.3,
76124
76125 editType: 'calc',
76126
76127 },
76128 boxgroupgap: {
76129 valType: 'number',
76130 min: 0,
76131 max: 1,
76132 dflt: 0.3,
76133
76134 editType: 'calc',
76135
76136 }
76137};
76138
76139},{}],304:[function(_dereq_,module,exports){
76140/**
76141* Copyright 2012-2020, Plotly, Inc.
76142* All rights reserved.
76143*
76144* This source code is licensed under the MIT license found in the
76145* LICENSE file in the root directory of this source tree.
76146*/
76147
76148'use strict';
76149
76150var Registry = _dereq_('../../registry');
76151var Lib = _dereq_('../../lib');
76152var layoutAttributes = _dereq_('./layout_attributes');
76153
76154function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
76155 var category = traceType + 'Layout';
76156 var hasTraceType = false;
76157
76158 for(var i = 0; i < fullData.length; i++) {
76159 var trace = fullData[i];
76160
76161 if(Registry.traceIs(trace, category)) {
76162 hasTraceType = true;
76163 break;
76164 }
76165 }
76166 if(!hasTraceType) return;
76167
76168 coerce(traceType + 'mode');
76169 coerce(traceType + 'gap');
76170 coerce(traceType + 'groupgap');
76171}
76172
76173function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
76174 function coerce(attr, dflt) {
76175 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
76176 }
76177 _supply(layoutIn, layoutOut, fullData, coerce, 'box');
76178}
76179
76180module.exports = {
76181 supplyLayoutDefaults: supplyLayoutDefaults,
76182 _supply: _supply
76183};
76184
76185},{"../../lib":178,"../../registry":269,"./layout_attributes":303}],305:[function(_dereq_,module,exports){
76186/**
76187* Copyright 2012-2020, Plotly, Inc.
76188* All rights reserved.
76189*
76190* This source code is licensed under the MIT license found in the
76191* LICENSE file in the root directory of this source tree.
76192*/
76193
76194'use strict';
76195
76196var d3 = _dereq_('d3');
76197
76198var Lib = _dereq_('../../lib');
76199var Drawing = _dereq_('../../components/drawing');
76200
76201// constants for dynamic jitter (ie less jitter for sparser points)
76202var JITTERCOUNT = 5; // points either side of this to include
76203var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"
76204
76205function plot(gd, plotinfo, cdbox, boxLayer) {
76206 var xa = plotinfo.xaxis;
76207 var ya = plotinfo.yaxis;
76208
76209 Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
76210 var plotGroup = d3.select(this);
76211 var cd0 = cd[0];
76212 var t = cd0.t;
76213 var trace = cd0.trace;
76214
76215 // whisker width
76216 t.wdPos = t.bdPos * trace.whiskerwidth;
76217
76218 if(trace.visible !== true || t.empty) {
76219 plotGroup.remove();
76220 return;
76221 }
76222
76223 var posAxis, valAxis;
76224
76225 if(trace.orientation === 'h') {
76226 posAxis = ya;
76227 valAxis = xa;
76228 } else {
76229 posAxis = xa;
76230 valAxis = ya;
76231 }
76232
76233 plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
76234 plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
76235 plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
76236 });
76237}
76238
76239function plotBoxAndWhiskers(sel, axes, trace, t) {
76240 var posAxis = axes.pos;
76241 var valAxis = axes.val;
76242 var bPos = t.bPos;
76243 var wdPos = t.wdPos || 0;
76244 var bPosPxOffset = t.bPosPxOffset || 0;
76245 var whiskerWidth = trace.whiskerwidth || 0;
76246 var notched = trace.notched || false;
76247 var nw = notched ? 1 - 2 * trace.notchwidth : 1;
76248
76249 // to support for one-sided box
76250 var bdPos0;
76251 var bdPos1;
76252 if(Array.isArray(t.bdPos)) {
76253 bdPos0 = t.bdPos[0];
76254 bdPos1 = t.bdPos[1];
76255 } else {
76256 bdPos0 = t.bdPos;
76257 bdPos1 = t.bdPos;
76258 }
76259
76260 var paths = sel.selectAll('path.box').data((
76261 trace.type !== 'violin' ||
76262 trace.box.visible
76263 ) ? Lib.identity : []);
76264
76265 paths.enter().append('path')
76266 .style('vector-effect', 'non-scaling-stroke')
76267 .attr('class', 'box');
76268
76269 paths.exit().remove();
76270
76271 paths.each(function(d) {
76272 if(d.empty) return 'M0,0Z';
76273
76274 var lcenter = posAxis.c2l(d.pos + bPos, true);
76275 var posc = posAxis.l2p(lcenter) + bPosPxOffset;
76276 var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
76277 var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
76278 var posw0 = posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
76279 var posw1 = posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
76280 var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
76281 var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
76282 var q1 = valAxis.c2p(d.q1, true);
76283 var q3 = valAxis.c2p(d.q3, true);
76284 // make sure median isn't identical to either of the
76285 // quartiles, so we can see it
76286 var m = Lib.constrain(
76287 valAxis.c2p(d.med, true),
76288 Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
76289 );
76290
76291 // for compatibility with box, violin, and candlestick
76292 // perhaps we should put this into cd0.t instead so it's more explicit,
76293 // but what we have now is:
76294 // - box always has d.lf, but boxpoints can be anything
76295 // - violin has d.lf and should always use it (boxpoints is undefined)
76296 // - candlestick has only min/max
76297 var useExtremes = (d.lf === undefined) || (trace.boxpoints === false);
76298 var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true);
76299 var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true);
76300 var ln = valAxis.c2p(d.ln, true);
76301 var un = valAxis.c2p(d.un, true);
76302
76303 if(trace.orientation === 'h') {
76304 d3.select(this).attr('d',
76305 'M' + m + ',' + posm0 + 'V' + posm1 + // median line
76306 'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
76307 (notched ? 'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 : '') + // top notched edge
76308 'H' + q3 + // end of the top edge
76309 'V' + pos0 + // right edge
76310 (notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
76311 'Z' + // end of the box
76312 'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
76313 ((whiskerWidth === 0) ? '' : // whisker caps
76314 'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1));
76315 } else {
76316 d3.select(this).attr('d',
76317 'M' + posm0 + ',' + m + 'H' + posm1 + // median line
76318 'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
76319 (notched ? 'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un : '') + // notched right edge
76320 'V' + q3 + // end of the right edge
76321 'H' + pos0 + // bottom of the box
76322 (notched ? 'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln : '') + // notched left edge
76323 'Z' + // end of the box
76324 'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
76325 ((whiskerWidth === 0) ? '' : // whisker caps
76326 'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1));
76327 }
76328 });
76329}
76330
76331function plotPoints(sel, axes, trace, t) {
76332 var xa = axes.x;
76333 var ya = axes.y;
76334 var bdPos = t.bdPos;
76335 var bPos = t.bPos;
76336
76337 // to support violin points
76338 var mode = trace.boxpoints || trace.points;
76339
76340 // repeatable pseudo-random number generator
76341 Lib.seedPseudoRandom();
76342
76343 // since box plot points get an extra level of nesting, each
76344 // box needs the trace styling info
76345 var fn = function(d) {
76346 d.forEach(function(v) {
76347 v.t = t;
76348 v.trace = trace;
76349 });
76350 return d;
76351 };
76352
76353 var gPoints = sel.selectAll('g.points')
76354 .data(mode ? fn : []);
76355
76356 gPoints.enter().append('g')
76357 .attr('class', 'points');
76358
76359 gPoints.exit().remove();
76360
76361 var paths = gPoints.selectAll('path')
76362 .data(function(d) {
76363 var i;
76364 var pts = d.pts2;
76365
76366 // normally use IQR, but if this is 0 or too small, use max-min
76367 var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);
76368 var minSpread = typicalSpread * 1e-9;
76369 var spreadLimit = typicalSpread * JITTERSPREAD;
76370 var jitterFactors = [];
76371 var maxJitterFactor = 0;
76372 var newJitter;
76373
76374 // dynamic jitter
76375 if(trace.jitter) {
76376 if(typicalSpread === 0) {
76377 // edge case of no spread at all: fall back to max jitter
76378 maxJitterFactor = 1;
76379 jitterFactors = new Array(pts.length);
76380 for(i = 0; i < pts.length; i++) {
76381 jitterFactors[i] = 1;
76382 }
76383 } else {
76384 for(i = 0; i < pts.length; i++) {
76385 var i0 = Math.max(0, i - JITTERCOUNT);
76386 var pmin = pts[i0].v;
76387 var i1 = Math.min(pts.length - 1, i + JITTERCOUNT);
76388 var pmax = pts[i1].v;
76389
76390 if(mode !== 'all') {
76391 if(pts[i].v < d.lf) pmax = Math.min(pmax, d.lf);
76392 else pmin = Math.max(pmin, d.uf);
76393 }
76394
76395 var jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0;
76396 jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1);
76397
76398 jitterFactors.push(jitterFactor);
76399 maxJitterFactor = Math.max(jitterFactor, maxJitterFactor);
76400 }
76401 }
76402 newJitter = trace.jitter * 2 / (maxJitterFactor || 1);
76403 }
76404
76405 // fills in 'x' and 'y' in calcdata 'pts' item
76406 for(i = 0; i < pts.length; i++) {
76407 var pt = pts[i];
76408 var v = pt.v;
76409
76410 var jitterOffset = trace.jitter ?
76411 (newJitter * jitterFactors[i] * (Lib.pseudoRandom() - 0.5)) :
76412 0;
76413
76414 var posPx = d.pos + bPos + bdPos * (trace.pointpos + jitterOffset);
76415
76416 if(trace.orientation === 'h') {
76417 pt.y = posPx;
76418 pt.x = v;
76419 } else {
76420 pt.x = posPx;
76421 pt.y = v;
76422 }
76423
76424 // tag suspected outliers
76425 if(mode === 'suspectedoutliers' && v < d.uo && v > d.lo) {
76426 pt.so = true;
76427 }
76428 }
76429
76430 return pts;
76431 });
76432
76433 paths.enter().append('path')
76434 .classed('point', true);
76435
76436 paths.exit().remove();
76437
76438 paths.call(Drawing.translatePoints, xa, ya);
76439}
76440
76441function plotBoxMean(sel, axes, trace, t) {
76442 var posAxis = axes.pos;
76443 var valAxis = axes.val;
76444 var bPos = t.bPos;
76445 var bPosPxOffset = t.bPosPxOffset || 0;
76446
76447 // to support violin mean lines
76448 var mode = trace.boxmean || (trace.meanline || {}).visible;
76449
76450 // to support for one-sided box
76451 var bdPos0;
76452 var bdPos1;
76453 if(Array.isArray(t.bdPos)) {
76454 bdPos0 = t.bdPos[0];
76455 bdPos1 = t.bdPos[1];
76456 } else {
76457 bdPos0 = t.bdPos;
76458 bdPos1 = t.bdPos;
76459 }
76460
76461 var paths = sel.selectAll('path.mean').data((
76462 (trace.type === 'box' && trace.boxmean) ||
76463 (trace.type === 'violin' && trace.box.visible && trace.meanline.visible)
76464 ) ? Lib.identity : []);
76465
76466 paths.enter().append('path')
76467 .attr('class', 'mean')
76468 .style({
76469 fill: 'none',
76470 'vector-effect': 'non-scaling-stroke'
76471 });
76472
76473 paths.exit().remove();
76474
76475 paths.each(function(d) {
76476 var lcenter = posAxis.c2l(d.pos + bPos, true);
76477 var posc = posAxis.l2p(lcenter) + bPosPxOffset;
76478 var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
76479 var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
76480 var m = valAxis.c2p(d.mean, true);
76481 var sl = valAxis.c2p(d.mean - d.sd, true);
76482 var sh = valAxis.c2p(d.mean + d.sd, true);
76483
76484 if(trace.orientation === 'h') {
76485 d3.select(this).attr('d',
76486 'M' + m + ',' + pos0 + 'V' + pos1 +
76487 (mode === 'sd' ?
76488 'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' :
76489 '')
76490 );
76491 } else {
76492 d3.select(this).attr('d',
76493 'M' + pos0 + ',' + m + 'H' + pos1 +
76494 (mode === 'sd' ?
76495 'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' :
76496 '')
76497 );
76498 }
76499 });
76500}
76501
76502module.exports = {
76503 plot: plot,
76504 plotBoxAndWhiskers: plotBoxAndWhiskers,
76505 plotPoints: plotPoints,
76506 plotBoxMean: plotBoxMean
76507};
76508
76509},{"../../components/drawing":74,"../../lib":178,"d3":16}],306:[function(_dereq_,module,exports){
76510/**
76511* Copyright 2012-2020, Plotly, Inc.
76512* All rights reserved.
76513*
76514* This source code is licensed under the MIT license found in the
76515* LICENSE file in the root directory of this source tree.
76516*/
76517
76518'use strict';
76519
76520module.exports = function selectPoints(searchInfo, selectionTester) {
76521 var cd = searchInfo.cd;
76522 var xa = searchInfo.xaxis;
76523 var ya = searchInfo.yaxis;
76524 var selection = [];
76525 var i, j;
76526
76527 if(selectionTester === false) {
76528 for(i = 0; i < cd.length; i++) {
76529 for(j = 0; j < (cd[i].pts || []).length; j++) {
76530 // clear selection
76531 cd[i].pts[j].selected = 0;
76532 }
76533 }
76534 } else {
76535 for(i = 0; i < cd.length; i++) {
76536 for(j = 0; j < (cd[i].pts || []).length; j++) {
76537 var pt = cd[i].pts[j];
76538 var x = xa.c2p(pt.x);
76539 var y = ya.c2p(pt.y);
76540
76541 if(selectionTester.contains([x, y], null, pt.i, searchInfo)) {
76542 selection.push({
76543 pointNumber: pt.i,
76544 x: xa.c2d(pt.x),
76545 y: ya.c2d(pt.y)
76546 });
76547 pt.selected = 1;
76548 } else {
76549 pt.selected = 0;
76550 }
76551 }
76552 }
76553 }
76554
76555 return selection;
76556};
76557
76558},{}],307:[function(_dereq_,module,exports){
76559/**
76560* Copyright 2012-2020, Plotly, Inc.
76561* All rights reserved.
76562*
76563* This source code is licensed under the MIT license found in the
76564* LICENSE file in the root directory of this source tree.
76565*/
76566
76567'use strict';
76568
76569var d3 = _dereq_('d3');
76570var Color = _dereq_('../../components/color');
76571var Drawing = _dereq_('../../components/drawing');
76572
76573function style(gd, cd, sel) {
76574 var s = sel ? sel : d3.select(gd).selectAll('g.trace.boxes');
76575
76576 s.style('opacity', function(d) { return d[0].trace.opacity; });
76577
76578 s.each(function(d) {
76579 var el = d3.select(this);
76580 var trace = d[0].trace;
76581 var lineWidth = trace.line.width;
76582
76583 function styleBox(boxSel, lineWidth, lineColor, fillColor) {
76584 boxSel.style('stroke-width', lineWidth + 'px')
76585 .call(Color.stroke, lineColor)
76586 .call(Color.fill, fillColor);
76587 }
76588
76589 var allBoxes = el.selectAll('path.box');
76590
76591 if(trace.type === 'candlestick') {
76592 allBoxes.each(function(boxData) {
76593 if(boxData.empty) return;
76594
76595 var thisBox = d3.select(this);
76596 var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
76597 styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
76598 // TODO: custom selection style for candlesticks
76599 thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);
76600 });
76601 } else {
76602 styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor);
76603 el.selectAll('path.mean')
76604 .style({
76605 'stroke-width': lineWidth,
76606 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px'
76607 })
76608 .call(Color.stroke, trace.line.color);
76609
76610 var pts = el.selectAll('path.point');
76611 Drawing.pointStyle(pts, trace, gd);
76612 }
76613 });
76614}
76615
76616function styleOnSelect(gd, cd, sel) {
76617 var trace = cd[0].trace;
76618 var pts = sel.selectAll('path.point');
76619
76620 if(trace.selectedpoints) {
76621 Drawing.selectedPointStyle(pts, trace);
76622 } else {
76623 Drawing.pointStyle(pts, trace, gd);
76624 }
76625}
76626
76627module.exports = {
76628 style: style,
76629 styleOnSelect: styleOnSelect
76630};
76631
76632},{"../../components/color":52,"../../components/drawing":74,"d3":16}],308:[function(_dereq_,module,exports){
76633/**
76634* Copyright 2012-2020, Plotly, Inc.
76635* All rights reserved.
76636*
76637* This source code is licensed under the MIT license found in the
76638* LICENSE file in the root directory of this source tree.
76639*/
76640
76641'use strict';
76642
76643var heatmapAttrs = _dereq_('../heatmap/attributes');
76644var scatterAttrs = _dereq_('../scatter/attributes');
76645var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
76646var dash = _dereq_('../../components/drawing/attributes').dash;
76647var fontAttrs = _dereq_('../../plots/font_attributes');
76648var extendFlat = _dereq_('../../lib/extend').extendFlat;
76649
76650var filterOps = _dereq_('../../constants/filter_ops');
76651var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
76652var INTERVAL_OPS = filterOps.INTERVAL_OPS;
76653
76654var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
76655
76656var scatterLineAttrs = scatterAttrs.line;
76657
76658module.exports = extendFlat({
76659 z: heatmapAttrs.z,
76660 x: heatmapAttrs.x,
76661 x0: heatmapAttrs.x0,
76662 dx: heatmapAttrs.dx,
76663 y: heatmapAttrs.y,
76664 y0: heatmapAttrs.y0,
76665 dy: heatmapAttrs.dy,
76666 text: heatmapAttrs.text,
76667 hovertext: heatmapAttrs.hovertext,
76668 transpose: heatmapAttrs.transpose,
76669 xtype: heatmapAttrs.xtype,
76670 ytype: heatmapAttrs.ytype,
76671 zhoverformat: heatmapAttrs.zhoverformat,
76672 hovertemplate: heatmapAttrs.hovertemplate,
76673 hoverongaps: heatmapAttrs.hoverongaps,
76674 connectgaps: extendFlat({}, heatmapAttrs.connectgaps, {
76675
76676 }),
76677
76678 fillcolor: {
76679 valType: 'color',
76680
76681 editType: 'calc',
76682
76683 },
76684
76685 autocontour: {
76686 valType: 'boolean',
76687 dflt: true,
76688
76689 editType: 'calc',
76690 impliedEdits: {
76691 'contours.start': undefined,
76692 'contours.end': undefined,
76693 'contours.size': undefined
76694 },
76695
76696 },
76697 ncontours: {
76698 valType: 'integer',
76699 dflt: 15,
76700 min: 1,
76701
76702 editType: 'calc',
76703
76704 },
76705
76706 contours: {
76707 type: {
76708 valType: 'enumerated',
76709 values: ['levels', 'constraint'],
76710 dflt: 'levels',
76711
76712 editType: 'calc',
76713
76714 },
76715 start: {
76716 valType: 'number',
76717 dflt: null,
76718
76719 editType: 'plot',
76720 impliedEdits: {'^autocontour': false},
76721
76722 },
76723 end: {
76724 valType: 'number',
76725 dflt: null,
76726
76727 editType: 'plot',
76728 impliedEdits: {'^autocontour': false},
76729
76730 },
76731 size: {
76732 valType: 'number',
76733 dflt: null,
76734 min: 0,
76735
76736 editType: 'plot',
76737 impliedEdits: {'^autocontour': false},
76738
76739 },
76740 coloring: {
76741 valType: 'enumerated',
76742 values: ['fill', 'heatmap', 'lines', 'none'],
76743 dflt: 'fill',
76744
76745 editType: 'calc',
76746
76747 },
76748 showlines: {
76749 valType: 'boolean',
76750 dflt: true,
76751
76752 editType: 'plot',
76753
76754 },
76755 showlabels: {
76756 valType: 'boolean',
76757 dflt: false,
76758
76759 editType: 'plot',
76760
76761 },
76762 labelfont: fontAttrs({
76763 editType: 'plot',
76764 colorEditType: 'style',
76765
76766 }),
76767 labelformat: {
76768 valType: 'string',
76769 dflt: '',
76770
76771 editType: 'plot',
76772
76773 },
76774 operation: {
76775 valType: 'enumerated',
76776 values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS),
76777
76778 dflt: '=',
76779 editType: 'calc',
76780
76781 },
76782 value: {
76783 valType: 'any',
76784 dflt: 0,
76785
76786 editType: 'calc',
76787
76788 },
76789 editType: 'calc',
76790 impliedEdits: {'autocontour': false}
76791 },
76792
76793 line: {
76794 color: extendFlat({}, scatterLineAttrs.color, {
76795 editType: 'style+colorbars',
76796
76797 }),
76798 width: {
76799 valType: 'number',
76800 min: 0,
76801
76802 editType: 'style+colorbars',
76803
76804 },
76805 dash: dash,
76806 smoothing: extendFlat({}, scatterLineAttrs.smoothing, {
76807
76808 }),
76809 editType: 'plot'
76810 }
76811},
76812 colorScaleAttrs('', {
76813 cLetter: 'z',
76814 autoColorDflt: false,
76815 editTypeOverride: 'calc'
76816 })
76817);
76818
76819},{"../../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){
76820/**
76821* Copyright 2012-2020, Plotly, Inc.
76822* All rights reserved.
76823*
76824* This source code is licensed under the MIT license found in the
76825* LICENSE file in the root directory of this source tree.
76826*/
76827
76828'use strict';
76829
76830var Colorscale = _dereq_('../../components/colorscale');
76831
76832var heatmapCalc = _dereq_('../heatmap/calc');
76833var setContours = _dereq_('./set_contours');
76834var endPlus = _dereq_('./end_plus');
76835
76836// most is the same as heatmap calc, then adjust it
76837// though a few things inside heatmap calc still look for
76838// contour maps, because the makeBoundArray calls are too entangled
76839module.exports = function calc(gd, trace) {
76840 var cd = heatmapCalc(gd, trace);
76841
76842 var zOut = cd[0].z;
76843 setContours(trace, zOut);
76844
76845 var contours = trace.contours;
76846 var cOpts = Colorscale.extractOpts(trace);
76847 var cVals;
76848
76849 if(contours.coloring === 'heatmap' && cOpts.auto && trace.autocontour === false) {
76850 var start = contours.start;
76851 var end = endPlus(contours);
76852 var cs = contours.size || 1;
76853 var nc = Math.floor((end - start) / cs) + 1;
76854
76855 if(!isFinite(cs)) {
76856 cs = 1;
76857 nc = 1;
76858 }
76859
76860 var min0 = start - cs / 2;
76861 var max0 = min0 + nc * cs;
76862 cVals = [min0, max0];
76863 } else {
76864 cVals = zOut;
76865 }
76866
76867 Colorscale.calc(gd, trace, {vals: cVals, cLetter: 'z'});
76868
76869 return cd;
76870};
76871
76872},{"../../components/colorscale":64,"../heatmap/calc":331,"./end_plus":319,"./set_contours":327}],310:[function(_dereq_,module,exports){
76873/**
76874* Copyright 2012-2020, Plotly, Inc.
76875* All rights reserved.
76876*
76877* This source code is licensed under the MIT license found in the
76878* LICENSE file in the root directory of this source tree.
76879*/
76880
76881'use strict';
76882
76883module.exports = function(pathinfo, contours) {
76884 var pi0 = pathinfo[0];
76885 var z = pi0.z;
76886 var i;
76887
76888 switch(contours.type) {
76889 case 'levels':
76890 // Why (just) use z[0][0] and z[0][1]?
76891 //
76892 // N.B. using boundaryMin instead of edgeVal2 here makes the
76893 // `contour_scatter` mock fail
76894 var edgeVal2 = Math.min(z[0][0], z[0][1]);
76895
76896 for(i = 0; i < pathinfo.length; i++) {
76897 var pi = pathinfo[i];
76898 pi.prefixBoundary = !pi.edgepaths.length &&
76899 (edgeVal2 > pi.level || pi.starts.length && edgeVal2 === pi.level);
76900 }
76901 break;
76902 case 'constraint':
76903 // after convertToConstraints, pathinfo has length=0
76904 pi0.prefixBoundary = false;
76905
76906 // joinAllPaths does enough already when edgepaths are present
76907 if(pi0.edgepaths.length) return;
76908
76909 var na = pi0.x.length;
76910 var nb = pi0.y.length;
76911 var boundaryMax = -Infinity;
76912 var boundaryMin = Infinity;
76913
76914 for(i = 0; i < nb; i++) {
76915 boundaryMin = Math.min(boundaryMin, z[i][0]);
76916 boundaryMin = Math.min(boundaryMin, z[i][na - 1]);
76917 boundaryMax = Math.max(boundaryMax, z[i][0]);
76918 boundaryMax = Math.max(boundaryMax, z[i][na - 1]);
76919 }
76920 for(i = 1; i < na - 1; i++) {
76921 boundaryMin = Math.min(boundaryMin, z[0][i]);
76922 boundaryMin = Math.min(boundaryMin, z[nb - 1][i]);
76923 boundaryMax = Math.max(boundaryMax, z[0][i]);
76924 boundaryMax = Math.max(boundaryMax, z[nb - 1][i]);
76925 }
76926
76927 var contoursValue = contours.value;
76928 var v1, v2;
76929
76930 switch(contours._operation) {
76931 case '>':
76932 if(contoursValue > boundaryMax) {
76933 pi0.prefixBoundary = true;
76934 }
76935 break;
76936 case '<':
76937 if(contoursValue < boundaryMin ||
76938 (pi0.starts.length && contoursValue === boundaryMin)) {
76939 pi0.prefixBoundary = true;
76940 }
76941 break;
76942 case '[]':
76943 v1 = Math.min(contoursValue[0], contoursValue[1]);
76944 v2 = Math.max(contoursValue[0], contoursValue[1]);
76945 if(v2 < boundaryMin || v1 > boundaryMax ||
76946 (pi0.starts.length && v2 === boundaryMin)) {
76947 pi0.prefixBoundary = true;
76948 }
76949 break;
76950 case '][':
76951 v1 = Math.min(contoursValue[0], contoursValue[1]);
76952 v2 = Math.max(contoursValue[0], contoursValue[1]);
76953 if(v1 < boundaryMin && v2 > boundaryMax) {
76954 pi0.prefixBoundary = true;
76955 }
76956 break;
76957 }
76958 break;
76959 }
76960};
76961
76962},{}],311:[function(_dereq_,module,exports){
76963/**
76964* Copyright 2012-2020, Plotly, Inc.
76965* All rights reserved.
76966*
76967* This source code is licensed under the MIT license found in the
76968* LICENSE file in the root directory of this source tree.
76969*/
76970
76971'use strict';
76972
76973var Colorscale = _dereq_('../../components/colorscale');
76974var makeColorMap = _dereq_('./make_color_map');
76975var endPlus = _dereq_('./end_plus');
76976
76977function calc(gd, trace, opts) {
76978 var contours = trace.contours;
76979 var line = trace.line;
76980 var cs = contours.size || 1;
76981 var coloring = contours.coloring;
76982 var colorMap = makeColorMap(trace, {isColorbar: true});
76983
76984 if(coloring === 'heatmap') {
76985 var cOpts = Colorscale.extractOpts(trace);
76986 opts._fillgradient = cOpts.reversescale ?
76987 Colorscale.flipScale(cOpts.colorscale) :
76988 cOpts.colorscale;
76989 opts._zrange = [cOpts.min, cOpts.max];
76990 } else if(coloring === 'fill') {
76991 opts._fillcolor = colorMap;
76992 }
76993
76994 opts._line = {
76995 color: coloring === 'lines' ? colorMap : line.color,
76996 width: contours.showlines !== false ? line.width : 0,
76997 dash: line.dash
76998 };
76999
77000 opts._levels = {
77001 start: contours.start,
77002 end: endPlus(contours),
77003 size: cs
77004 };
77005}
77006
77007module.exports = {
77008 min: 'zmin',
77009 max: 'zmax',
77010 calc: calc
77011};
77012
77013},{"../../components/colorscale":64,"./end_plus":319,"./make_color_map":324}],312:[function(_dereq_,module,exports){
77014/**
77015* Copyright 2012-2020, Plotly, Inc.
77016* All rights reserved.
77017*
77018* This source code is licensed under the MIT license found in the
77019* LICENSE file in the root directory of this source tree.
77020*/
77021
77022'use strict';
77023module.exports = {
77024 // some constants to help with marching squares algorithm
77025 // where does the path start for each index?
77026 BOTTOMSTART: [1, 9, 13, 104, 713],
77027 TOPSTART: [4, 6, 7, 104, 713],
77028 LEFTSTART: [8, 12, 14, 208, 1114],
77029 RIGHTSTART: [2, 3, 11, 208, 1114],
77030
77031 // which way [dx,dy] do we leave a given index?
77032 // saddles are already disambiguated
77033 NEWDELTA: [
77034 null, [-1, 0], [0, -1], [-1, 0],
77035 [1, 0], null, [0, -1], [-1, 0],
77036 [0, 1], [0, 1], null, [0, 1],
77037 [1, 0], [1, 0], [0, -1]
77038 ],
77039
77040 // for each saddle, the first index here is used
77041 // for dx||dy<0, the second for dx||dy>0
77042 CHOOSESADDLE: {
77043 104: [4, 1],
77044 208: [2, 8],
77045 713: [7, 13],
77046 1114: [11, 14]
77047 },
77048
77049 // after one index has been used for a saddle, which do we
77050 // substitute to be used up later?
77051 SADDLEREMAINDER: {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11},
77052
77053 // length of a contour, as a multiple of the plot area diagonal, per label
77054 LABELDISTANCE: 2,
77055
77056 // number of contour levels after which we start increasing the number of
77057 // labels we draw. Many contours means they will generally be close
77058 // together, so it will be harder to follow a long way to find a label
77059 LABELINCREASE: 10,
77060
77061 // minimum length of a contour line, as a multiple of the label length,
77062 // at which we draw *any* labels
77063 LABELMIN: 3,
77064
77065 // max number of labels to draw on a single contour path, no matter how long
77066 LABELMAX: 10,
77067
77068 // constants for the label position cost function
77069 LABELOPTIMIZER: {
77070 // weight given to edge proximity
77071 EDGECOST: 1,
77072 // weight given to the angle off horizontal
77073 ANGLECOST: 1,
77074 // weight given to distance from already-placed labels
77075 NEIGHBORCOST: 5,
77076 // cost multiplier for labels on the same level
77077 SAMELEVELFACTOR: 10,
77078 // minimum distance (as a multiple of the label length)
77079 // for labels on the same level
77080 SAMELEVELDISTANCE: 5,
77081 // maximum cost before we won't even place the label
77082 MAXCOST: 100,
77083 // number of evenly spaced points to look at in the first
77084 // iteration of the search
77085 INITIALSEARCHPOINTS: 10,
77086 // number of binary search iterations after the initial wide search
77087 ITERATIONS: 5
77088 }
77089};
77090
77091},{}],313:[function(_dereq_,module,exports){
77092/**
77093* Copyright 2012-2020, Plotly, Inc.
77094* All rights reserved.
77095*
77096* This source code is licensed under the MIT license found in the
77097* LICENSE file in the root directory of this source tree.
77098*/
77099
77100
77101'use strict';
77102var isNumeric = _dereq_('fast-isnumeric');
77103
77104var handleLabelDefaults = _dereq_('./label_defaults');
77105
77106var Color = _dereq_('../../components/color');
77107var addOpacity = Color.addOpacity;
77108var opacity = Color.opacity;
77109
77110var filterOps = _dereq_('../../constants/filter_ops');
77111var CONSTRAINT_REDUCTION = filterOps.CONSTRAINT_REDUCTION;
77112var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
77113
77114module.exports = function handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, opts) {
77115 var contours = traceOut.contours;
77116 var showLines, lineColor, fillColor;
77117
77118 var operation = coerce('contours.operation');
77119 contours._operation = CONSTRAINT_REDUCTION[operation];
77120
77121 handleConstraintValueDefaults(coerce, contours);
77122
77123 if(operation === '=') {
77124 showLines = contours.showlines = true;
77125 } else {
77126 showLines = coerce('contours.showlines');
77127 fillColor = coerce('fillcolor', addOpacity(
77128 (traceIn.line || {}).color || defaultColor, 0.5
77129 ));
77130 }
77131
77132 if(showLines) {
77133 var lineDfltColor = fillColor && opacity(fillColor) ?
77134 addOpacity(traceOut.fillcolor, 1) :
77135 defaultColor;
77136 lineColor = coerce('line.color', lineDfltColor);
77137 coerce('line.width', 2);
77138 coerce('line.dash');
77139 }
77140
77141 coerce('line.smoothing');
77142
77143 handleLabelDefaults(coerce, layout, lineColor, opts);
77144};
77145
77146function handleConstraintValueDefaults(coerce, contours) {
77147 var zvalue;
77148
77149 if(COMPARISON_OPS2.indexOf(contours.operation) === -1) {
77150 // Requires an array of two numbers:
77151 coerce('contours.value', [0, 1]);
77152
77153 if(!Array.isArray(contours.value)) {
77154 if(isNumeric(contours.value)) {
77155 zvalue = parseFloat(contours.value);
77156 contours.value = [zvalue, zvalue + 1];
77157 }
77158 } else if(contours.value.length > 2) {
77159 contours.value = contours.value.slice(2);
77160 } else if(contours.length === 0) {
77161 contours.value = [0, 1];
77162 } else if(contours.length < 2) {
77163 zvalue = parseFloat(contours.value[0]);
77164 contours.value = [zvalue, zvalue + 1];
77165 } else {
77166 contours.value = [
77167 parseFloat(contours.value[0]),
77168 parseFloat(contours.value[1])
77169 ];
77170 }
77171 } else {
77172 // Requires a single scalar:
77173 coerce('contours.value', 0);
77174
77175 if(!isNumeric(contours.value)) {
77176 if(Array.isArray(contours.value)) {
77177 contours.value = parseFloat(contours.value[0]);
77178 } else {
77179 contours.value = 0;
77180 }
77181 }
77182 }
77183}
77184
77185},{"../../components/color":52,"../../constants/filter_ops":156,"./label_defaults":323,"fast-isnumeric":18}],314:[function(_dereq_,module,exports){
77186/**
77187* Copyright 2012-2020, Plotly, Inc.
77188* All rights reserved.
77189*
77190* This source code is licensed under the MIT license found in the
77191* LICENSE file in the root directory of this source tree.
77192*/
77193
77194'use strict';
77195
77196var filterOps = _dereq_('../../constants/filter_ops');
77197var isNumeric = _dereq_('fast-isnumeric');
77198
77199// This syntax conforms to the existing filter transform syntax, but we don't care
77200// about open vs. closed intervals for simply drawing contours constraints:
77201module.exports = {
77202 '[]': makeRangeSettings('[]'),
77203 '][': makeRangeSettings(']['),
77204 '>': makeInequalitySettings('>'),
77205 '<': makeInequalitySettings('<'),
77206 '=': makeInequalitySettings('=')
77207};
77208
77209// This does not in any way shape or form support calendars. It's adapted from
77210// transforms/filter.js.
77211function coerceValue(operation, value) {
77212 var hasArrayValue = Array.isArray(value);
77213
77214 var coercedValue;
77215
77216 function coerce(value) {
77217 return isNumeric(value) ? (+value) : null;
77218 }
77219
77220 if(filterOps.COMPARISON_OPS2.indexOf(operation) !== -1) {
77221 coercedValue = hasArrayValue ? coerce(value[0]) : coerce(value);
77222 } else if(filterOps.INTERVAL_OPS.indexOf(operation) !== -1) {
77223 coercedValue = hasArrayValue ?
77224 [coerce(value[0]), coerce(value[1])] :
77225 [coerce(value), coerce(value)];
77226 } else if(filterOps.SET_OPS.indexOf(operation) !== -1) {
77227 coercedValue = hasArrayValue ? value.map(coerce) : [coerce(value)];
77228 }
77229
77230 return coercedValue;
77231}
77232
77233// Returns a parabola scaled so that the min/max is either +/- 1 and zero at the two values
77234// provided. The data is mapped by this function when constructing intervals so that it's
77235// very easy to construct contours as normal.
77236function makeRangeSettings(operation) {
77237 return function(value) {
77238 value = coerceValue(operation, value);
77239
77240 // Ensure proper ordering:
77241 var min = Math.min(value[0], value[1]);
77242 var max = Math.max(value[0], value[1]);
77243
77244 return {
77245 start: min,
77246 end: max,
77247 size: max - min
77248 };
77249 };
77250}
77251
77252function makeInequalitySettings(operation) {
77253 return function(value) {
77254 value = coerceValue(operation, value);
77255
77256 return {
77257 start: value,
77258 end: Infinity,
77259 size: Infinity
77260 };
77261 };
77262}
77263
77264},{"../../constants/filter_ops":156,"fast-isnumeric":18}],315:[function(_dereq_,module,exports){
77265/**
77266* Copyright 2012-2020, Plotly, Inc.
77267* All rights reserved.
77268*
77269* This source code is licensed under the MIT license found in the
77270* LICENSE file in the root directory of this source tree.
77271*/
77272
77273'use strict';
77274
77275module.exports = function handleContourDefaults(traceIn, traceOut, coerce, coerce2) {
77276 var contourStart = coerce2('contours.start');
77277 var contourEnd = coerce2('contours.end');
77278 var missingEnd = (contourStart === false) || (contourEnd === false);
77279
77280 // normally we only need size if autocontour is off. But contour.calc
77281 // pushes its calculated contour size back to the input trace, so for
77282 // things like restyle that can call supplyDefaults without calc
77283 // after the initial draw, we can just reuse the previous calculation
77284 var contourSize = coerce('contours.size');
77285 var autoContour;
77286
77287 if(missingEnd) autoContour = traceOut.autocontour = true;
77288 else autoContour = coerce('autocontour', false);
77289
77290 if(autoContour || !contourSize) coerce('ncontours');
77291};
77292
77293},{}],316:[function(_dereq_,module,exports){
77294/**
77295* Copyright 2012-2020, Plotly, Inc.
77296* All rights reserved.
77297*
77298* This source code is licensed under the MIT license found in the
77299* LICENSE file in the root directory of this source tree.
77300*/
77301
77302'use strict';
77303
77304var Lib = _dereq_('../../lib');
77305
77306// The contour extraction is great, except it totally fails for constraints because we
77307// need weird range loops and flipped contours instead of the usual format. This function
77308// does some weird manipulation of the extracted pathinfo data such that it magically
77309// draws contours correctly *as* constraints.
77310//
77311// ** I do not know which "weird range loops" the comment above is referring to.
77312module.exports = function(pathinfo, operation) {
77313 var i, pi0, pi1;
77314
77315 var op0 = function(arr) { return arr.reverse(); };
77316 var op1 = function(arr) { return arr; };
77317
77318 switch(operation) {
77319 case '=':
77320 case '<':
77321 return pathinfo;
77322 case '>':
77323 if(pathinfo.length !== 1) {
77324 Lib.warn('Contour data invalid for the specified inequality operation.');
77325 }
77326
77327 // In this case there should be exactly one contour levels in pathinfo.
77328 // We flip all of the data. This will draw the contour as closed.
77329 pi0 = pathinfo[0];
77330
77331 for(i = 0; i < pi0.edgepaths.length; i++) {
77332 pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
77333 }
77334 for(i = 0; i < pi0.paths.length; i++) {
77335 pi0.paths[i] = op0(pi0.paths[i]);
77336 }
77337 for(i = 0; i < pi0.starts.length; i++) {
77338 pi0.starts[i] = op0(pi0.starts[i]);
77339 }
77340
77341 return pathinfo;
77342 case '][':
77343 var tmp = op0;
77344 op0 = op1;
77345 op1 = tmp;
77346 // It's a nice rule, except this definitely *is* what's intended here.
77347 /* eslint-disable: no-fallthrough */
77348 case '[]':
77349 /* eslint-enable: no-fallthrough */
77350 if(pathinfo.length !== 2) {
77351 Lib.warn('Contour data invalid for the specified inequality range operation.');
77352 }
77353
77354 // In this case there should be exactly two contour levels in pathinfo.
77355 // - We concatenate the info into one pathinfo.
77356 // - We must also flip all of the data in the `[]` case.
77357 // This will draw the contours as closed.
77358 pi0 = copyPathinfo(pathinfo[0]);
77359 pi1 = copyPathinfo(pathinfo[1]);
77360
77361 for(i = 0; i < pi0.edgepaths.length; i++) {
77362 pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
77363 }
77364 for(i = 0; i < pi0.paths.length; i++) {
77365 pi0.paths[i] = op0(pi0.paths[i]);
77366 }
77367 for(i = 0; i < pi0.starts.length; i++) {
77368 pi0.starts[i] = op0(pi0.starts[i]);
77369 }
77370
77371 while(pi1.edgepaths.length) {
77372 pi0.edgepaths.push(op1(pi1.edgepaths.shift()));
77373 }
77374 while(pi1.paths.length) {
77375 pi0.paths.push(op1(pi1.paths.shift()));
77376 }
77377 while(pi1.starts.length) {
77378 pi0.starts.push(op1(pi1.starts.shift()));
77379 }
77380
77381 return [pi0];
77382 }
77383};
77384
77385function copyPathinfo(pi) {
77386 return Lib.extendFlat({}, pi, {
77387 edgepaths: Lib.extendDeep([], pi.edgepaths),
77388 paths: Lib.extendDeep([], pi.paths),
77389 starts: Lib.extendDeep([], pi.starts)
77390 });
77391}
77392
77393},{"../../lib":178}],317:[function(_dereq_,module,exports){
77394/**
77395* Copyright 2012-2020, Plotly, Inc.
77396* All rights reserved.
77397*
77398* This source code is licensed under the MIT license found in the
77399* LICENSE file in the root directory of this source tree.
77400*/
77401
77402'use strict';
77403
77404var Lib = _dereq_('../../lib');
77405
77406var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults');
77407var handleConstraintDefaults = _dereq_('./constraint_defaults');
77408var handleContoursDefaults = _dereq_('./contours_defaults');
77409var handleStyleDefaults = _dereq_('./style_defaults');
77410var attributes = _dereq_('./attributes');
77411
77412
77413module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
77414 function coerce(attr, dflt) {
77415 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
77416 }
77417
77418 function coerce2(attr) {
77419 return Lib.coerce2(traceIn, traceOut, attributes, attr);
77420 }
77421
77422 var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
77423 if(!len) {
77424 traceOut.visible = false;
77425 return;
77426 }
77427
77428 coerce('text');
77429 coerce('hovertext');
77430 coerce('hovertemplate');
77431 coerce('hoverongaps');
77432
77433 var isConstraint = (coerce('contours.type') === 'constraint');
77434 coerce('connectgaps', Lib.isArray1D(traceOut.z));
77435
77436 if(isConstraint) {
77437 handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor);
77438 } else {
77439 handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
77440 handleStyleDefaults(traceIn, traceOut, coerce, layout);
77441 }
77442};
77443
77444},{"../../lib":178,"../heatmap/xyz_defaults":344,"./attributes":308,"./constraint_defaults":313,"./contours_defaults":315,"./style_defaults":329}],318:[function(_dereq_,module,exports){
77445/**
77446* Copyright 2012-2020, Plotly, Inc.
77447* All rights reserved.
77448*
77449* This source code is licensed under the MIT license found in the
77450* LICENSE file in the root directory of this source tree.
77451*/
77452
77453'use strict';
77454
77455var Lib = _dereq_('../../lib');
77456var constraintMapping = _dereq_('./constraint_mapping');
77457var endPlus = _dereq_('./end_plus');
77458
77459module.exports = function emptyPathinfo(contours, plotinfo, cd0) {
77460 var contoursFinal = (contours.type === 'constraint') ?
77461 constraintMapping[contours._operation](contours.value) :
77462 contours;
77463
77464 var cs = contoursFinal.size;
77465 var pathinfo = [];
77466 var end = endPlus(contoursFinal);
77467
77468 var carpet = cd0.trace._carpetTrace;
77469
77470 var basePathinfo = carpet ? {
77471 // store axes so we can convert to px
77472 xaxis: carpet.aaxis,
77473 yaxis: carpet.baxis,
77474 // full data arrays to use for interpolation
77475 x: cd0.a,
77476 y: cd0.b
77477 } : {
77478 xaxis: plotinfo.xaxis,
77479 yaxis: plotinfo.yaxis,
77480 x: cd0.x,
77481 y: cd0.y
77482 };
77483
77484 for(var ci = contoursFinal.start; ci < end; ci += cs) {
77485 pathinfo.push(Lib.extendFlat({
77486 level: ci,
77487 // all the cells with nontrivial marching index
77488 crossings: {},
77489 // starting points on the edges of the lattice for each contour
77490 starts: [],
77491 // all unclosed paths (may have less items than starts,
77492 // if a path is closed by rounding)
77493 edgepaths: [],
77494 // all closed paths
77495 paths: [],
77496 z: cd0.z,
77497 smoothing: cd0.trace.line.smoothing
77498 }, basePathinfo));
77499
77500 if(pathinfo.length > 1000) {
77501 Lib.warn('Too many contours, clipping at 1000', contours);
77502 break;
77503 }
77504 }
77505 return pathinfo;
77506};
77507
77508},{"../../lib":178,"./constraint_mapping":314,"./end_plus":319}],319:[function(_dereq_,module,exports){
77509/**
77510* Copyright 2012-2020, Plotly, Inc.
77511* All rights reserved.
77512*
77513* This source code is licensed under the MIT license found in the
77514* LICENSE file in the root directory of this source tree.
77515*/
77516
77517
77518'use strict';
77519
77520/*
77521 * tiny helper to move the end of the contours a little to prevent
77522 * losing the last contour to rounding errors
77523 */
77524module.exports = function endPlus(contours) {
77525 return contours.end + contours.size / 1e6;
77526};
77527
77528},{}],320:[function(_dereq_,module,exports){
77529/**
77530* Copyright 2012-2020, Plotly, Inc.
77531* All rights reserved.
77532*
77533* This source code is licensed under the MIT license found in the
77534* LICENSE file in the root directory of this source tree.
77535*/
77536
77537'use strict';
77538
77539var Lib = _dereq_('../../lib');
77540var constants = _dereq_('./constants');
77541
77542module.exports = function findAllPaths(pathinfo, xtol, ytol) {
77543 var cnt,
77544 startLoc,
77545 i,
77546 pi,
77547 j;
77548
77549 // Default just passes these values through as they were before:
77550 xtol = xtol || 0.01;
77551 ytol = ytol || 0.01;
77552
77553 for(i = 0; i < pathinfo.length; i++) {
77554 pi = pathinfo[i];
77555
77556 for(j = 0; j < pi.starts.length; j++) {
77557 startLoc = pi.starts[j];
77558 makePath(pi, startLoc, 'edge', xtol, ytol);
77559 }
77560
77561 cnt = 0;
77562 while(Object.keys(pi.crossings).length && cnt < 10000) {
77563 cnt++;
77564 startLoc = Object.keys(pi.crossings)[0].split(',').map(Number);
77565 makePath(pi, startLoc, undefined, xtol, ytol);
77566 }
77567 if(cnt === 10000) Lib.log('Infinite loop in contour?');
77568 }
77569};
77570
77571function equalPts(pt1, pt2, xtol, ytol) {
77572 return Math.abs(pt1[0] - pt2[0]) < xtol &&
77573 Math.abs(pt1[1] - pt2[1]) < ytol;
77574}
77575
77576// distance in index units - uses the 3rd and 4th items in points
77577function ptDist(pt1, pt2) {
77578 var dx = pt1[2] - pt2[2];
77579 var dy = pt1[3] - pt2[3];
77580 return Math.sqrt(dx * dx + dy * dy);
77581}
77582
77583function makePath(pi, loc, edgeflag, xtol, ytol) {
77584 var locStr = loc.join(',');
77585 var mi = pi.crossings[locStr];
77586 var marchStep = getStartStep(mi, edgeflag, loc);
77587 // start by going backward a half step and finding the crossing point
77588 var pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])];
77589 var m = pi.z.length;
77590 var n = pi.z[0].length;
77591 var startLoc = loc.slice();
77592 var startStep = marchStep.slice();
77593 var cnt;
77594
77595 // now follow the path
77596 for(cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops
77597 if(mi > 20) {
77598 mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1];
77599 pi.crossings[locStr] = constants.SADDLEREMAINDER[mi];
77600 } else {
77601 delete pi.crossings[locStr];
77602 }
77603
77604 marchStep = constants.NEWDELTA[mi];
77605 if(!marchStep) {
77606 Lib.log('Found bad marching index:', mi, loc, pi.level);
77607 break;
77608 }
77609
77610 // find the crossing a half step forward, and then take the full step
77611 pts.push(getInterpPx(pi, loc, marchStep));
77612 loc[0] += marchStep[0];
77613 loc[1] += marchStep[1];
77614 locStr = loc.join(',');
77615
77616 // don't include the same point multiple times
77617 if(equalPts(pts[pts.length - 1], pts[pts.length - 2], xtol, ytol)) pts.pop();
77618
77619 var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) ||
77620 (marchStep[1] && (loc[1] < 0 || loc[1] > m - 2));
77621
77622 var closedLoop = loc[0] === startLoc[0] && loc[1] === startLoc[1] &&
77623 marchStep[0] === startStep[0] && marchStep[1] === startStep[1];
77624
77625 // have we completed a loop, or reached an edge?
77626 if((closedLoop) || (edgeflag && atEdge)) break;
77627
77628 mi = pi.crossings[locStr];
77629 }
77630
77631 if(cnt === 10000) {
77632 Lib.log('Infinite loop in contour?');
77633 }
77634 var closedpath = equalPts(pts[0], pts[pts.length - 1], xtol, ytol);
77635 var totaldist = 0;
77636 var distThresholdFactor = 0.2 * pi.smoothing;
77637 var alldists = [];
77638 var cropstart = 0;
77639 var distgroup, cnt2, cnt3, newpt, ptcnt, ptavg, thisdist,
77640 i, j, edgepathi, edgepathj;
77641
77642 /*
77643 * Check for points that are too close together (<1/5 the average dist
77644 * *in grid index units* (important for log axes and nonuniform grids),
77645 * less if less smoothed) and just take the center (or avg of center 2).
77646 * This cuts down on funny behavior when a point is very close to a
77647 * contour level.
77648 */
77649 for(cnt = 1; cnt < pts.length; cnt++) {
77650 thisdist = ptDist(pts[cnt], pts[cnt - 1]);
77651 totaldist += thisdist;
77652 alldists.push(thisdist);
77653 }
77654
77655 var distThreshold = totaldist / alldists.length * distThresholdFactor;
77656
77657 function getpt(i) { return pts[i % pts.length]; }
77658
77659 for(cnt = pts.length - 2; cnt >= cropstart; cnt--) {
77660 distgroup = alldists[cnt];
77661 if(distgroup < distThreshold) {
77662 cnt3 = 0;
77663 for(cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) {
77664 if(distgroup + alldists[cnt2] < distThreshold) {
77665 distgroup += alldists[cnt2];
77666 } else break;
77667 }
77668
77669 // closed path with close points wrapping around the boundary?
77670 if(closedpath && cnt === pts.length - 2) {
77671 for(cnt3 = 0; cnt3 < cnt2; cnt3++) {
77672 if(distgroup + alldists[cnt3] < distThreshold) {
77673 distgroup += alldists[cnt3];
77674 } else break;
77675 }
77676 }
77677 ptcnt = cnt - cnt2 + cnt3 + 1;
77678 ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2);
77679
77680 // either endpoint included: keep the endpoint
77681 if(!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1];
77682 else if(!closedpath && cnt2 === -1) newpt = pts[0];
77683
77684 // odd # of points - just take the central one
77685 else if(ptcnt % 2) newpt = getpt(ptavg);
77686
77687 // even # of pts - average central two
77688 else {
77689 newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2,
77690 (getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2];
77691 }
77692
77693 pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt);
77694 cnt = cnt2 + 1;
77695 if(cnt3) cropstart = cnt3;
77696 if(closedpath) {
77697 if(cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1];
77698 else if(cnt === 0) pts[pts.length - 1] = pts[0];
77699 }
77700 }
77701 }
77702 pts.splice(0, cropstart);
77703
77704 // done with the index parts - remove them so path generation works right
77705 // because it depends on only having [xpx, ypx]
77706 for(cnt = 0; cnt < pts.length; cnt++) pts[cnt].length = 2;
77707
77708 // don't return single-point paths (ie all points were the same
77709 // so they got deleted?)
77710 if(pts.length < 2) return;
77711 else if(closedpath) {
77712 pts.pop();
77713 pi.paths.push(pts);
77714 } else {
77715 if(!edgeflag) {
77716 Lib.log('Unclosed interior contour?',
77717 pi.level, startLoc.join(','), pts.join('L'));
77718 }
77719
77720 // edge path - does it start where an existing edge path ends, or vice versa?
77721 var merged = false;
77722 for(i = 0; i < pi.edgepaths.length; i++) {
77723 edgepathi = pi.edgepaths[i];
77724 if(!merged && equalPts(edgepathi[0], pts[pts.length - 1], xtol, ytol)) {
77725 pts.pop();
77726 merged = true;
77727
77728 // now does it ALSO meet the end of another (or the same) path?
77729 var doublemerged = false;
77730 for(j = 0; j < pi.edgepaths.length; j++) {
77731 edgepathj = pi.edgepaths[j];
77732 if(equalPts(edgepathj[edgepathj.length - 1], pts[0], xtol, ytol)) {
77733 doublemerged = true;
77734 pts.shift();
77735 pi.edgepaths.splice(i, 1);
77736 if(j === i) {
77737 // the path is now closed
77738 pi.paths.push(pts.concat(edgepathj));
77739 } else {
77740 if(j > i) j--;
77741 pi.edgepaths[j] = edgepathj.concat(pts, edgepathi);
77742 }
77743 break;
77744 }
77745 }
77746 if(!doublemerged) {
77747 pi.edgepaths[i] = pts.concat(edgepathi);
77748 }
77749 }
77750 }
77751 for(i = 0; i < pi.edgepaths.length; i++) {
77752 if(merged) break;
77753 edgepathi = pi.edgepaths[i];
77754 if(equalPts(edgepathi[edgepathi.length - 1], pts[0], xtol, ytol)) {
77755 pts.shift();
77756 pi.edgepaths[i] = edgepathi.concat(pts);
77757 merged = true;
77758 }
77759 }
77760
77761 if(!merged) pi.edgepaths.push(pts);
77762 }
77763}
77764
77765// special function to get the marching step of the
77766// first point in the path (leading to loc)
77767function getStartStep(mi, edgeflag, loc) {
77768 var dx = 0;
77769 var dy = 0;
77770 if(mi > 20 && edgeflag) {
77771 // these saddles start at +/- x
77772 if(mi === 208 || mi === 1114) {
77773 // if we're starting at the left side, we must be going right
77774 dx = loc[0] === 0 ? 1 : -1;
77775 } else {
77776 // if we're starting at the bottom, we must be going up
77777 dy = loc[1] === 0 ? 1 : -1;
77778 }
77779 } else if(constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1;
77780 else if(constants.LEFTSTART.indexOf(mi) !== -1) dx = 1;
77781 else if(constants.TOPSTART.indexOf(mi) !== -1) dy = -1;
77782 else dx = -1;
77783 return [dx, dy];
77784}
77785
77786/*
77787 * Find the pixel coordinates of a particular crossing
77788 *
77789 * @param {object} pi: the pathinfo object at this level
77790 * @param {array} loc: the grid index [x, y] of the crossing
77791 * @param {array} step: the direction [dx, dy] we're moving on the grid
77792 *
77793 * @return {array} [xpx, ypx, xi, yi]: the first two are the pixel location,
77794 * the next two are the interpolated grid indices, which we use for
77795 * distance calculations to delete points that are too close together.
77796 * This is important when the grid is nonuniform (and most dramatically when
77797 * we're on log axes and include invalid (0 or negative) values.
77798 * It's crucial to delete these extra two before turning an array of these
77799 * points into a path, because those routines require length-2 points.
77800 */
77801function getInterpPx(pi, loc, step) {
77802 var locx = loc[0] + Math.max(step[0], 0);
77803 var locy = loc[1] + Math.max(step[1], 0);
77804 var zxy = pi.z[locy][locx];
77805 var xa = pi.xaxis;
77806 var ya = pi.yaxis;
77807
77808 if(step[1]) {
77809 var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy);
77810
77811 return [xa.c2p((1 - dx) * pi.x[locx] + dx * pi.x[locx + 1], true),
77812 ya.c2p(pi.y[locy], true),
77813 locx + dx, locy];
77814 } else {
77815 var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy);
77816 return [xa.c2p(pi.x[locx], true),
77817 ya.c2p((1 - dy) * pi.y[locy] + dy * pi.y[locy + 1], true),
77818 locx, locy + dy];
77819 }
77820}
77821
77822},{"../../lib":178,"./constants":312}],321:[function(_dereq_,module,exports){
77823/**
77824* Copyright 2012-2020, Plotly, Inc.
77825* All rights reserved.
77826*
77827* This source code is licensed under the MIT license found in the
77828* LICENSE file in the root directory of this source tree.
77829*/
77830
77831
77832'use strict';
77833
77834var Color = _dereq_('../../components/color');
77835
77836var heatmapHoverPoints = _dereq_('../heatmap/hover');
77837
77838module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) {
77839 var hoverData = heatmapHoverPoints(pointData, xval, yval, hovermode, hoverLayer, true);
77840
77841 if(hoverData) {
77842 hoverData.forEach(function(hoverPt) {
77843 var trace = hoverPt.trace;
77844 if(trace.contours.type === 'constraint') {
77845 if(trace.fillcolor && Color.opacity(trace.fillcolor)) {
77846 hoverPt.color = Color.addOpacity(trace.fillcolor, 1);
77847 } else if(trace.contours.showlines && Color.opacity(trace.line.color)) {
77848 hoverPt.color = Color.addOpacity(trace.line.color, 1);
77849 }
77850 }
77851 });
77852 }
77853
77854 return hoverData;
77855};
77856
77857},{"../../components/color":52,"../heatmap/hover":337}],322:[function(_dereq_,module,exports){
77858/**
77859* Copyright 2012-2020, Plotly, Inc.
77860* All rights reserved.
77861*
77862* This source code is licensed under the MIT license found in the
77863* LICENSE file in the root directory of this source tree.
77864*/
77865
77866'use strict';
77867
77868module.exports = {
77869 attributes: _dereq_('./attributes'),
77870 supplyDefaults: _dereq_('./defaults'),
77871 calc: _dereq_('./calc'),
77872 plot: _dereq_('./plot').plot,
77873 style: _dereq_('./style'),
77874 colorbar: _dereq_('./colorbar'),
77875 hoverPoints: _dereq_('./hover'),
77876
77877 moduleType: 'trace',
77878 name: 'contour',
77879 basePlotModule: _dereq_('../../plots/cartesian'),
77880 categories: ['cartesian', 'svg', '2dMap', 'contour', 'showLegend'],
77881 meta: {
77882
77883 }
77884};
77885
77886},{"../../plots/cartesian":235,"./attributes":308,"./calc":309,"./colorbar":311,"./defaults":317,"./hover":321,"./plot":326,"./style":328}],323:[function(_dereq_,module,exports){
77887/**
77888* Copyright 2012-2020, Plotly, Inc.
77889* All rights reserved.
77890*
77891* This source code is licensed under the MIT license found in the
77892* LICENSE file in the root directory of this source tree.
77893*/
77894
77895
77896'use strict';
77897
77898var Lib = _dereq_('../../lib');
77899
77900module.exports = function handleLabelDefaults(coerce, layout, lineColor, opts) {
77901 if(!opts) opts = {};
77902 var showLabels = coerce('contours.showlabels');
77903 if(showLabels) {
77904 var globalFont = layout.font;
77905 Lib.coerceFont(coerce, 'contours.labelfont', {
77906 family: globalFont.family,
77907 size: globalFont.size,
77908 color: lineColor
77909 });
77910 coerce('contours.labelformat');
77911 }
77912
77913 if(opts.hasHover !== false) coerce('zhoverformat');
77914};
77915
77916},{"../../lib":178}],324:[function(_dereq_,module,exports){
77917/**
77918* Copyright 2012-2020, Plotly, Inc.
77919* All rights reserved.
77920*
77921* This source code is licensed under the MIT license found in the
77922* LICENSE file in the root directory of this source tree.
77923*/
77924
77925'use strict';
77926
77927var d3 = _dereq_('d3');
77928
77929var Colorscale = _dereq_('../../components/colorscale');
77930var endPlus = _dereq_('./end_plus');
77931
77932module.exports = function makeColorMap(trace) {
77933 var contours = trace.contours;
77934 var start = contours.start;
77935 var end = endPlus(contours);
77936 var cs = contours.size || 1;
77937 var nc = Math.floor((end - start) / cs) + 1;
77938 var extra = contours.coloring === 'lines' ? 0 : 1;
77939 var cOpts = Colorscale.extractOpts(trace);
77940
77941 if(!isFinite(cs)) {
77942 cs = 1;
77943 nc = 1;
77944 }
77945
77946 var scl = cOpts.reversescale ?
77947 Colorscale.flipScale(cOpts.colorscale) :
77948 cOpts.colorscale;
77949
77950 var len = scl.length;
77951 var domain = new Array(len);
77952 var range = new Array(len);
77953
77954 var si, i;
77955
77956 if(contours.coloring === 'heatmap') {
77957 var zmin0 = cOpts.min;
77958 var zmax0 = cOpts.max;
77959
77960 for(i = 0; i < len; i++) {
77961 si = scl[i];
77962 domain[i] = si[0] * (zmax0 - zmin0) + zmin0;
77963 range[i] = si[1];
77964 }
77965
77966 // do the contours extend beyond the colorscale?
77967 // if so, extend the colorscale with constants
77968 var zRange = d3.extent([
77969 zmin0,
77970 zmax0,
77971 contours.start,
77972 contours.start + cs * (nc - 1)
77973 ]);
77974 var zmin = zRange[zmin0 < zmax0 ? 0 : 1];
77975 var zmax = zRange[zmin0 < zmax0 ? 1 : 0];
77976
77977 if(zmin !== zmin0) {
77978 domain.splice(0, 0, zmin);
77979 range.splice(0, 0, range[0]);
77980 }
77981
77982 if(zmax !== zmax0) {
77983 domain.push(zmax);
77984 range.push(range[range.length - 1]);
77985 }
77986 } else {
77987 for(i = 0; i < len; i++) {
77988 si = scl[i];
77989 domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start;
77990 range[i] = si[1];
77991 }
77992 }
77993
77994 return Colorscale.makeColorScaleFunc(
77995 {domain: domain, range: range},
77996 {noNumericCheck: true}
77997 );
77998};
77999
78000},{"../../components/colorscale":64,"./end_plus":319,"d3":16}],325:[function(_dereq_,module,exports){
78001/**
78002* Copyright 2012-2020, Plotly, Inc.
78003* All rights reserved.
78004*
78005* This source code is licensed under the MIT license found in the
78006* LICENSE file in the root directory of this source tree.
78007*/
78008
78009'use strict';
78010
78011var constants = _dereq_('./constants');
78012
78013// Calculate all the marching indices, for ALL levels at once.
78014// since we want to be exhaustive we'll check for contour crossings
78015// at every intersection, rather than just following a path
78016// TODO: shorten the inner loop to only the relevant levels
78017module.exports = function makeCrossings(pathinfo) {
78018 var z = pathinfo[0].z;
78019 var m = z.length;
78020 var n = z[0].length; // we already made sure z isn't ragged in interp2d
78021 var twoWide = m === 2 || n === 2;
78022 var xi;
78023 var yi;
78024 var startIndices;
78025 var ystartIndices;
78026 var label;
78027 var corners;
78028 var mi;
78029 var pi;
78030 var i;
78031
78032 for(yi = 0; yi < m - 1; yi++) {
78033 ystartIndices = [];
78034 if(yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART);
78035 if(yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART);
78036
78037 for(xi = 0; xi < n - 1; xi++) {
78038 startIndices = ystartIndices.slice();
78039 if(xi === 0) startIndices = startIndices.concat(constants.LEFTSTART);
78040 if(xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART);
78041
78042 label = xi + ',' + yi;
78043 corners = [[z[yi][xi], z[yi][xi + 1]],
78044 [z[yi + 1][xi], z[yi + 1][xi + 1]]];
78045 for(i = 0; i < pathinfo.length; i++) {
78046 pi = pathinfo[i];
78047 mi = getMarchingIndex(pi.level, corners);
78048 if(!mi) continue;
78049
78050 pi.crossings[label] = mi;
78051 if(startIndices.indexOf(mi) !== -1) {
78052 pi.starts.push([xi, yi]);
78053 if(twoWide && startIndices.indexOf(mi,
78054 startIndices.indexOf(mi) + 1) !== -1) {
78055 // the same square has starts from opposite sides
78056 // it's not possible to have starts on opposite edges
78057 // of a corner, only a start and an end...
78058 // but if the array is only two points wide (either way)
78059 // you can have starts on opposite sides.
78060 pi.starts.push([xi, yi]);
78061 }
78062 }
78063 }
78064 }
78065 }
78066};
78067
78068// modified marching squares algorithm,
78069// so we disambiguate the saddle points from the start
78070// and we ignore the cases with no crossings
78071// the index I'm using is based on:
78072// http://en.wikipedia.org/wiki/Marching_squares
78073// except that the saddles bifurcate and I represent them
78074// as the decimal combination of the two appropriate
78075// non-saddle indices
78076function getMarchingIndex(val, corners) {
78077 var mi = (corners[0][0] > val ? 0 : 1) +
78078 (corners[0][1] > val ? 0 : 2) +
78079 (corners[1][1] > val ? 0 : 4) +
78080 (corners[1][0] > val ? 0 : 8);
78081 if(mi === 5 || mi === 10) {
78082 var avg = (corners[0][0] + corners[0][1] +
78083 corners[1][0] + corners[1][1]) / 4;
78084 // two peaks with a big valley
78085 if(val > avg) return (mi === 5) ? 713 : 1114;
78086 // two valleys with a big ridge
78087 return (mi === 5) ? 104 : 208;
78088 }
78089 return (mi === 15) ? 0 : mi;
78090}
78091
78092},{"./constants":312}],326:[function(_dereq_,module,exports){
78093/**
78094* Copyright 2012-2020, Plotly, Inc.
78095* All rights reserved.
78096*
78097* This source code is licensed under the MIT license found in the
78098* LICENSE file in the root directory of this source tree.
78099*/
78100
78101
78102'use strict';
78103
78104var d3 = _dereq_('d3');
78105
78106var Lib = _dereq_('../../lib');
78107var Drawing = _dereq_('../../components/drawing');
78108var Colorscale = _dereq_('../../components/colorscale');
78109var svgTextUtils = _dereq_('../../lib/svg_text_utils');
78110var Axes = _dereq_('../../plots/cartesian/axes');
78111var setConvert = _dereq_('../../plots/cartesian/set_convert');
78112
78113var heatmapPlot = _dereq_('../heatmap/plot');
78114var makeCrossings = _dereq_('./make_crossings');
78115var findAllPaths = _dereq_('./find_all_paths');
78116var emptyPathinfo = _dereq_('./empty_pathinfo');
78117var convertToConstraints = _dereq_('./convert_to_constraints');
78118var closeBoundaries = _dereq_('./close_boundaries');
78119var constants = _dereq_('./constants');
78120var costConstants = constants.LABELOPTIMIZER;
78121
78122exports.plot = function plot(gd, plotinfo, cdcontours, contourLayer) {
78123 var xa = plotinfo.xaxis;
78124 var ya = plotinfo.yaxis;
78125
78126 Lib.makeTraceGroups(contourLayer, cdcontours, 'contour').each(function(cd) {
78127 var plotGroup = d3.select(this);
78128 var cd0 = cd[0];
78129 var trace = cd0.trace;
78130 var x = cd0.x;
78131 var y = cd0.y;
78132 var contours = trace.contours;
78133 var pathinfo = emptyPathinfo(contours, plotinfo, cd0);
78134
78135 // use a heatmap to fill - draw it behind the lines
78136 var heatmapColoringLayer = Lib.ensureSingle(plotGroup, 'g', 'heatmapcoloring');
78137 var cdheatmaps = [];
78138 if(contours.coloring === 'heatmap') {
78139 cdheatmaps = [cd];
78140 }
78141 heatmapPlot(gd, plotinfo, cdheatmaps, heatmapColoringLayer);
78142
78143 makeCrossings(pathinfo);
78144 findAllPaths(pathinfo);
78145
78146 var leftedge = xa.c2p(x[0], true);
78147 var rightedge = xa.c2p(x[x.length - 1], true);
78148 var bottomedge = ya.c2p(y[0], true);
78149 var topedge = ya.c2p(y[y.length - 1], true);
78150 var perimeter = [
78151 [leftedge, topedge],
78152 [rightedge, topedge],
78153 [rightedge, bottomedge],
78154 [leftedge, bottomedge]
78155 ];
78156
78157 var fillPathinfo = pathinfo;
78158 if(contours.type === 'constraint') {
78159 // N.B. this also mutates pathinfo
78160 fillPathinfo = convertToConstraints(pathinfo, contours._operation);
78161 }
78162
78163 // draw everything
78164 makeBackground(plotGroup, perimeter, contours);
78165 makeFills(plotGroup, fillPathinfo, perimeter, contours);
78166 makeLinesAndLabels(plotGroup, pathinfo, gd, cd0, contours);
78167 clipGaps(plotGroup, plotinfo, gd, cd0, perimeter);
78168 });
78169};
78170
78171function makeBackground(plotgroup, perimeter, contours) {
78172 var bggroup = Lib.ensureSingle(plotgroup, 'g', 'contourbg');
78173
78174 var bgfill = bggroup.selectAll('path')
78175 .data(contours.coloring === 'fill' ? [0] : []);
78176 bgfill.enter().append('path');
78177 bgfill.exit().remove();
78178 bgfill
78179 .attr('d', 'M' + perimeter.join('L') + 'Z')
78180 .style('stroke', 'none');
78181}
78182
78183function makeFills(plotgroup, pathinfo, perimeter, contours) {
78184 var hasFills = contours.coloring === 'fill' || (contours.type === 'constraint' && contours._operation !== '=');
78185 var boundaryPath = 'M' + perimeter.join('L') + 'Z';
78186
78187 // fills prefixBoundary in pathinfo items
78188 if(hasFills) {
78189 closeBoundaries(pathinfo, contours);
78190 }
78191
78192 var fillgroup = Lib.ensureSingle(plotgroup, 'g', 'contourfill');
78193
78194 var fillitems = fillgroup.selectAll('path').data(hasFills ? pathinfo : []);
78195 fillitems.enter().append('path');
78196 fillitems.exit().remove();
78197 fillitems.each(function(pi) {
78198 // join all paths for this level together into a single path
78199 // first follow clockwise around the perimeter to close any open paths
78200 // if the whole perimeter is above this level, start with a path
78201 // enclosing the whole thing. With all that, the parity should mean
78202 // that we always fill everything above the contour, nothing below
78203 var fullpath = (pi.prefixBoundary ? boundaryPath : '') +
78204 joinAllPaths(pi, perimeter);
78205
78206 if(!fullpath) {
78207 d3.select(this).remove();
78208 } else {
78209 d3.select(this)
78210 .attr('d', fullpath)
78211 .style('stroke', 'none');
78212 }
78213 });
78214}
78215
78216function joinAllPaths(pi, perimeter) {
78217 var fullpath = '';
78218 var i = 0;
78219 var startsleft = pi.edgepaths.map(function(v, i) { return i; });
78220 var newloop = true;
78221 var endpt;
78222 var newendpt;
78223 var cnt;
78224 var nexti;
78225 var possiblei;
78226 var addpath;
78227
78228 function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01; }
78229 function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01; }
78230 function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01; }
78231 function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01; }
78232
78233 while(startsleft.length) {
78234 addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing);
78235 fullpath += newloop ? addpath : addpath.replace(/^M/, 'L');
78236 startsleft.splice(startsleft.indexOf(i), 1);
78237 endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1];
78238 nexti = -1;
78239
78240 // now loop through sides, moving our endpoint until we find a new start
78241 for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops
78242 if(!endpt) {
78243 Lib.log('Missing end?', i, pi);
78244 break;
78245 }
78246
78247 if(istop(endpt) && !isright(endpt)) newendpt = perimeter[1]; // right top
78248 else if(isleft(endpt)) newendpt = perimeter[0]; // left top
78249 else if(isbottom(endpt)) newendpt = perimeter[3]; // right bottom
78250 else if(isright(endpt)) newendpt = perimeter[2]; // left bottom
78251
78252 for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) {
78253 var ptNew = pi.edgepaths[possiblei][0];
78254 // is ptNew on the (horz. or vert.) segment from endpt to newendpt?
78255 if(Math.abs(endpt[0] - newendpt[0]) < 0.01) {
78256 if(Math.abs(endpt[0] - ptNew[0]) < 0.01 &&
78257 (ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) {
78258 newendpt = ptNew;
78259 nexti = possiblei;
78260 }
78261 } else if(Math.abs(endpt[1] - newendpt[1]) < 0.01) {
78262 if(Math.abs(endpt[1] - ptNew[1]) < 0.01 &&
78263 (ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) {
78264 newendpt = ptNew;
78265 nexti = possiblei;
78266 }
78267 } else {
78268 Lib.log('endpt to newendpt is not vert. or horz.',
78269 endpt, newendpt, ptNew);
78270 }
78271 }
78272
78273 endpt = newendpt;
78274
78275 if(nexti >= 0) break;
78276 fullpath += 'L' + newendpt;
78277 }
78278
78279 if(nexti === pi.edgepaths.length) {
78280 Lib.log('unclosed perimeter path');
78281 break;
78282 }
78283
78284 i = nexti;
78285
78286 // if we closed back on a loop we already included,
78287 // close it and start a new loop
78288 newloop = (startsleft.indexOf(i) === -1);
78289 if(newloop) {
78290 i = startsleft[0];
78291 fullpath += 'Z';
78292 }
78293 }
78294
78295 // finally add the interior paths
78296 for(i = 0; i < pi.paths.length; i++) {
78297 fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing);
78298 }
78299
78300 return fullpath;
78301}
78302
78303function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours) {
78304 var lineContainer = Lib.ensureSingle(plotgroup, 'g', 'contourlines');
78305 var showLines = contours.showlines !== false;
78306 var showLabels = contours.showlabels;
78307 var clipLinesForLabels = showLines && showLabels;
78308
78309 // Even if we're not going to show lines, we need to create them
78310 // if we're showing labels, because the fill paths include the perimeter
78311 // so can't be used to position the labels correctly.
78312 // In this case we'll remove the lines after making the labels.
78313 var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo);
78314
78315 var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels, gd, cd0.trace.uid);
78316
78317 var labelGroup = plotgroup.selectAll('g.contourlabels')
78318 .data(showLabels ? [0] : []);
78319
78320 labelGroup.exit().remove();
78321
78322 labelGroup.enter().append('g')
78323 .classed('contourlabels', true);
78324
78325 if(showLabels) {
78326 var labelClipPathData = [];
78327 var labelData = [];
78328
78329 // invalidate the getTextLocation cache in case paths changed
78330 Lib.clearLocationCache();
78331
78332 var contourFormat = exports.labelFormatter(gd, cd0);
78333
78334 var dummyText = Drawing.tester.append('text')
78335 .attr('data-notex', 1)
78336 .call(Drawing.font, contours.labelfont);
78337
78338 var xa = pathinfo[0].xaxis;
78339 var ya = pathinfo[0].yaxis;
78340 var xLen = xa._length;
78341 var yLen = ya._length;
78342 var xRng = xa.range;
78343 var yRng = ya.range;
78344 var xMin = Lib.aggNums(Math.min, null, cd0.x);
78345 var xMax = Lib.aggNums(Math.max, null, cd0.x);
78346 var yMin = Lib.aggNums(Math.min, null, cd0.y);
78347 var yMax = Lib.aggNums(Math.max, null, cd0.y);
78348 var x0 = Math.max(xa.c2p(xMin, true), 0);
78349 var x1 = Math.min(xa.c2p(xMax, true), xLen);
78350 var y0 = Math.max(ya.c2p(yMax, true), 0);
78351 var y1 = Math.min(ya.c2p(yMin, true), yLen);
78352
78353 // visible bounds of the contour trace (and the midpoints, to
78354 // help with cost calculations)
78355 var bounds = {};
78356
78357 if(xRng[0] < xRng[1]) {
78358 bounds.left = x0;
78359 bounds.right = x1;
78360 } else {
78361 bounds.left = x1;
78362 bounds.right = x0;
78363 }
78364
78365 if(yRng[0] < yRng[1]) {
78366 bounds.top = y0;
78367 bounds.bottom = y1;
78368 } else {
78369 bounds.top = y1;
78370 bounds.bottom = y0;
78371 }
78372
78373 bounds.middle = (bounds.top + bounds.bottom) / 2;
78374 bounds.center = (bounds.left + bounds.right) / 2;
78375
78376 labelClipPathData.push([
78377 [bounds.left, bounds.top],
78378 [bounds.right, bounds.top],
78379 [bounds.right, bounds.bottom],
78380 [bounds.left, bounds.bottom]
78381 ]);
78382
78383 var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen);
78384
78385 // the path length to use to scale the number of labels to draw:
78386 var normLength = constants.LABELDISTANCE * plotDiagonal /
78387 Math.max(1, pathinfo.length / constants.LABELINCREASE);
78388
78389 linegroup.each(function(d) {
78390 var textOpts = exports.calcTextOpts(d.level, contourFormat, dummyText, gd);
78391
78392 d3.select(this).selectAll('path').each(function() {
78393 var path = this;
78394 var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2);
78395 if(!pathBounds) return;
78396
78397 if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return;
78398
78399 var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength),
78400 constants.LABELMAX);
78401
78402 for(var i = 0; i < maxLabels; i++) {
78403 var loc = exports.findBestTextLocation(path, pathBounds, textOpts,
78404 labelData, bounds);
78405
78406 if(!loc) break;
78407
78408 exports.addLabelData(loc, textOpts, labelData, labelClipPathData);
78409 }
78410 });
78411 });
78412
78413 dummyText.remove();
78414
78415 exports.drawLabels(labelGroup, labelData, gd, lineClip,
78416 clipLinesForLabels ? labelClipPathData : null);
78417 }
78418
78419 if(showLabels && !showLines) linegroup.remove();
78420}
78421
78422exports.createLines = function(lineContainer, makeLines, pathinfo) {
78423 var smoothing = pathinfo[0].smoothing;
78424
78425 var linegroup = lineContainer.selectAll('g.contourlevel')
78426 .data(makeLines ? pathinfo : []);
78427
78428 linegroup.exit().remove();
78429 linegroup.enter().append('g')
78430 .classed('contourlevel', true);
78431
78432 if(makeLines) {
78433 // pedgepaths / ppaths are used by contourcarpet, for the paths transformed from a/b to x/y
78434 // edgepaths / paths are used by contour since it's in x/y from the start
78435 var opencontourlines = linegroup.selectAll('path.openline')
78436 .data(function(d) { return d.pedgepaths || d.edgepaths; });
78437
78438 opencontourlines.exit().remove();
78439 opencontourlines.enter().append('path')
78440 .classed('openline', true);
78441
78442 opencontourlines
78443 .attr('d', function(d) {
78444 return Drawing.smoothopen(d, smoothing);
78445 })
78446 .style('stroke-miterlimit', 1)
78447 .style('vector-effect', 'non-scaling-stroke');
78448
78449 var closedcontourlines = linegroup.selectAll('path.closedline')
78450 .data(function(d) { return d.ppaths || d.paths; });
78451
78452 closedcontourlines.exit().remove();
78453 closedcontourlines.enter().append('path')
78454 .classed('closedline', true);
78455
78456 closedcontourlines
78457 .attr('d', function(d) {
78458 return Drawing.smoothclosed(d, smoothing);
78459 })
78460 .style('stroke-miterlimit', 1)
78461 .style('vector-effect', 'non-scaling-stroke');
78462 }
78463
78464 return linegroup;
78465};
78466
78467exports.createLineClip = function(lineContainer, clipLinesForLabels, gd, uid) {
78468 var clips = gd._fullLayout._clips;
78469 var clipId = clipLinesForLabels ? ('clipline' + uid) : null;
78470
78471 var lineClip = clips.selectAll('#' + clipId)
78472 .data(clipLinesForLabels ? [0] : []);
78473 lineClip.exit().remove();
78474
78475 lineClip.enter().append('clipPath')
78476 .classed('contourlineclip', true)
78477 .attr('id', clipId);
78478
78479 Drawing.setClipUrl(lineContainer, clipId, gd);
78480
78481 return lineClip;
78482};
78483
78484exports.labelFormatter = function(gd, cd0) {
78485 var fullLayout = gd._fullLayout;
78486 var trace = cd0.trace;
78487 var contours = trace.contours;
78488
78489 var formatAxis = {
78490 type: 'linear',
78491 _id: 'ycontour',
78492 showexponent: 'all',
78493 exponentformat: 'B'
78494 };
78495
78496 if(contours.labelformat) {
78497 formatAxis.tickformat = contours.labelformat;
78498 setConvert(formatAxis, fullLayout);
78499 } else {
78500 var cOpts = Colorscale.extractOpts(trace);
78501 if(cOpts && cOpts.colorbar && cOpts.colorbar._axis) {
78502 formatAxis = cOpts.colorbar._axis;
78503 } else {
78504 if(contours.type === 'constraint') {
78505 var value = contours.value;
78506 if(Array.isArray(value)) {
78507 formatAxis.range = [value[0], value[value.length - 1]];
78508 } else formatAxis.range = [value, value];
78509 } else {
78510 formatAxis.range = [contours.start, contours.end];
78511 formatAxis.nticks = (contours.end - contours.start) / contours.size;
78512 }
78513
78514 if(formatAxis.range[0] === formatAxis.range[1]) {
78515 formatAxis.range[1] += formatAxis.range[0] || 1;
78516 }
78517 if(!formatAxis.nticks) formatAxis.nticks = 1000;
78518
78519 setConvert(formatAxis, fullLayout);
78520 Axes.prepTicks(formatAxis);
78521 formatAxis._tmin = null;
78522 formatAxis._tmax = null;
78523 }
78524 }
78525
78526 return function(v) { return Axes.tickText(formatAxis, v).text; };
78527};
78528
78529exports.calcTextOpts = function(level, contourFormat, dummyText, gd) {
78530 var text = contourFormat(level);
78531 dummyText.text(text)
78532 .call(svgTextUtils.convertToTspans, gd);
78533
78534 var el = dummyText.node();
78535 var bBox = Drawing.bBox(el, true);
78536
78537 return {
78538 text: text,
78539 width: bBox.width,
78540 height: bBox.height,
78541 fontSize: +(el.style['font-size'].replace('px', '')),
78542 level: level,
78543 dy: (bBox.top + bBox.bottom) / 2
78544 };
78545};
78546
78547exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) {
78548 var textWidth = textOpts.width;
78549
78550 var p0, dp, pMax, pMin, loc;
78551 if(pathBounds.isClosed) {
78552 dp = pathBounds.len / costConstants.INITIALSEARCHPOINTS;
78553 p0 = pathBounds.min + dp / 2;
78554 pMax = pathBounds.max;
78555 } else {
78556 dp = (pathBounds.len - textWidth) / (costConstants.INITIALSEARCHPOINTS + 1);
78557 p0 = pathBounds.min + dp + textWidth / 2;
78558 pMax = pathBounds.max - (dp + textWidth) / 2;
78559 }
78560
78561 var cost = Infinity;
78562 for(var j = 0; j < costConstants.ITERATIONS; j++) {
78563 for(var p = p0; p < pMax; p += dp) {
78564 var newLocation = Lib.getTextLocation(path, pathBounds.total, p, textWidth);
78565 var newCost = locationCost(newLocation, textOpts, labelData, plotBounds);
78566 if(newCost < cost) {
78567 cost = newCost;
78568 loc = newLocation;
78569 pMin = p;
78570 }
78571 }
78572 if(cost > costConstants.MAXCOST * 2) break;
78573
78574 // subsequent iterations just look half steps away from the
78575 // best we found in the previous iteration
78576 if(j) dp /= 2;
78577 p0 = pMin - dp / 2;
78578 pMax = p0 + dp * 1.5;
78579 }
78580 if(cost <= costConstants.MAXCOST) return loc;
78581};
78582
78583/*
78584 * locationCost: a cost function for label locations
78585 * composed of three kinds of penalty:
78586 * - for open paths, being close to the end of the path
78587 * - the angle away from horizontal
78588 * - being too close to already placed neighbors
78589 */
78590function locationCost(loc, textOpts, labelData, bounds) {
78591 var halfWidth = textOpts.width / 2;
78592 var halfHeight = textOpts.height / 2;
78593 var x = loc.x;
78594 var y = loc.y;
78595 var theta = loc.theta;
78596 var dx = Math.cos(theta) * halfWidth;
78597 var dy = Math.sin(theta) * halfWidth;
78598
78599 // cost for being near an edge
78600 var normX = ((x > bounds.center) ? (bounds.right - x) : (x - bounds.left)) /
78601 (dx + Math.abs(Math.sin(theta) * halfHeight));
78602 var normY = ((y > bounds.middle) ? (bounds.bottom - y) : (y - bounds.top)) /
78603 (Math.abs(dy) + Math.cos(theta) * halfHeight);
78604 if(normX < 1 || normY < 1) return Infinity;
78605 var cost = costConstants.EDGECOST * (1 / (normX - 1) + 1 / (normY - 1));
78606
78607 // cost for not being horizontal
78608 cost += costConstants.ANGLECOST * theta * theta;
78609
78610 // cost for being close to other labels
78611 var x1 = x - dx;
78612 var y1 = y - dy;
78613 var x2 = x + dx;
78614 var y2 = y + dy;
78615 for(var i = 0; i < labelData.length; i++) {
78616 var labeli = labelData[i];
78617 var dxd = Math.cos(labeli.theta) * labeli.width / 2;
78618 var dyd = Math.sin(labeli.theta) * labeli.width / 2;
78619 var dist = Lib.segmentDistance(
78620 x1, y1,
78621 x2, y2,
78622 labeli.x - dxd, labeli.y - dyd,
78623 labeli.x + dxd, labeli.y + dyd
78624 ) * 2 / (textOpts.height + labeli.height);
78625
78626 var sameLevel = labeli.level === textOpts.level;
78627 var distOffset = sameLevel ? costConstants.SAMELEVELDISTANCE : 1;
78628
78629 if(dist <= distOffset) return Infinity;
78630
78631 var distFactor = costConstants.NEIGHBORCOST *
78632 (sameLevel ? costConstants.SAMELEVELFACTOR : 1);
78633
78634 cost += distFactor / (dist - distOffset);
78635 }
78636
78637 return cost;
78638}
78639
78640exports.addLabelData = function(loc, textOpts, labelData, labelClipPathData) {
78641 var fontSize = textOpts.fontSize;
78642 var w = textOpts.width + fontSize / 3;
78643 var h = Math.max(0, textOpts.height - fontSize / 3);
78644
78645 var x = loc.x;
78646 var y = loc.y;
78647 var theta = loc.theta;
78648
78649 var sin = Math.sin(theta);
78650 var cos = Math.cos(theta);
78651
78652 var rotateXY = function(dx, dy) {
78653 return [
78654 x + dx * cos - dy * sin,
78655 y + dx * sin + dy * cos
78656 ];
78657 };
78658
78659 var bBoxPts = [
78660 rotateXY(-w / 2, -h / 2),
78661 rotateXY(-w / 2, h / 2),
78662 rotateXY(w / 2, h / 2),
78663 rotateXY(w / 2, -h / 2)
78664 ];
78665
78666 labelData.push({
78667 text: textOpts.text,
78668 x: x,
78669 y: y,
78670 dy: textOpts.dy,
78671 theta: theta,
78672 level: textOpts.level,
78673 width: w,
78674 height: h
78675 });
78676
78677 labelClipPathData.push(bBoxPts);
78678};
78679
78680exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPathData) {
78681 var labels = labelGroup.selectAll('text')
78682 .data(labelData, function(d) {
78683 return d.text + ',' + d.x + ',' + d.y + ',' + d.theta;
78684 });
78685
78686 labels.exit().remove();
78687
78688 labels.enter().append('text')
78689 .attr({
78690 'data-notex': 1,
78691 'text-anchor': 'middle'
78692 })
78693 .each(function(d) {
78694 var x = d.x + Math.sin(d.theta) * d.dy;
78695 var y = d.y - Math.cos(d.theta) * d.dy;
78696 d3.select(this)
78697 .text(d.text)
78698 .attr({
78699 x: x,
78700 y: y,
78701 transform: 'rotate(' + (180 * d.theta / Math.PI) + ' ' + x + ' ' + y + ')'
78702 })
78703 .call(svgTextUtils.convertToTspans, gd);
78704 });
78705
78706 if(labelClipPathData) {
78707 var clipPath = '';
78708 for(var i = 0; i < labelClipPathData.length; i++) {
78709 clipPath += 'M' + labelClipPathData[i].join('L') + 'Z';
78710 }
78711
78712 var lineClipPath = Lib.ensureSingle(lineClip, 'path', '');
78713 lineClipPath.attr('d', clipPath);
78714 }
78715};
78716
78717function clipGaps(plotGroup, plotinfo, gd, cd0, perimeter) {
78718 var trace = cd0.trace;
78719 var clips = gd._fullLayout._clips;
78720 var clipId = 'clip' + trace.uid;
78721
78722 var clipPath = clips.selectAll('#' + clipId)
78723 .data(trace.connectgaps ? [] : [0]);
78724 clipPath.enter().append('clipPath')
78725 .classed('contourclip', true)
78726 .attr('id', clipId);
78727 clipPath.exit().remove();
78728
78729 if(trace.connectgaps === false) {
78730 var clipPathInfo = {
78731 // fraction of the way from missing to present point
78732 // to draw the boundary.
78733 // if you make this 1 (or 1-epsilon) then a point in
78734 // a sea of missing data will disappear entirely.
78735 level: 0.9,
78736 crossings: {},
78737 starts: [],
78738 edgepaths: [],
78739 paths: [],
78740 xaxis: plotinfo.xaxis,
78741 yaxis: plotinfo.yaxis,
78742 x: cd0.x,
78743 y: cd0.y,
78744 // 0 = no data, 1 = data
78745 z: makeClipMask(cd0),
78746 smoothing: 0
78747 };
78748
78749 makeCrossings([clipPathInfo]);
78750 findAllPaths([clipPathInfo]);
78751 closeBoundaries([clipPathInfo], {type: 'levels'});
78752
78753 var path = Lib.ensureSingle(clipPath, 'path', '');
78754 path.attr('d',
78755 (clipPathInfo.prefixBoundary ? 'M' + perimeter.join('L') + 'Z' : '') +
78756 joinAllPaths(clipPathInfo, perimeter)
78757 );
78758 } else clipId = null;
78759
78760 Drawing.setClipUrl(plotGroup, clipId, gd);
78761}
78762
78763function makeClipMask(cd0) {
78764 var empties = cd0.trace._emptypoints;
78765 var z = [];
78766 var m = cd0.z.length;
78767 var n = cd0.z[0].length;
78768 var i;
78769 var row = [];
78770 var emptyPoint;
78771
78772 for(i = 0; i < n; i++) row.push(1);
78773 for(i = 0; i < m; i++) z.push(row.slice());
78774 for(i = 0; i < empties.length; i++) {
78775 emptyPoint = empties[i];
78776 z[emptyPoint[0]][emptyPoint[1]] = 0;
78777 }
78778 // save this mask to determine whether to show this data in hover
78779 cd0.zmask = z;
78780 return z;
78781}
78782
78783},{"../../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){
78784/**
78785* Copyright 2012-2020, Plotly, Inc.
78786* All rights reserved.
78787*
78788* This source code is licensed under the MIT license found in the
78789* LICENSE file in the root directory of this source tree.
78790*/
78791
78792'use strict';
78793
78794var Axes = _dereq_('../../plots/cartesian/axes');
78795var Lib = _dereq_('../../lib');
78796
78797module.exports = function setContours(trace, vals) {
78798 var contours = trace.contours;
78799
78800 // check if we need to auto-choose contour levels
78801 if(trace.autocontour) {
78802 // N.B. do not try to use coloraxis cmin/cmax,
78803 // these values here are meant to remain "per-trace" for now
78804 var zmin = trace.zmin;
78805 var zmax = trace.zmax;
78806 if(trace.zauto || zmin === undefined) {
78807 zmin = Lib.aggNums(Math.min, null, vals);
78808 }
78809 if(trace.zauto || zmax === undefined) {
78810 zmax = Lib.aggNums(Math.max, null, vals);
78811 }
78812
78813 var dummyAx = autoContours(zmin, zmax, trace.ncontours);
78814 contours.size = dummyAx.dtick;
78815 contours.start = Axes.tickFirst(dummyAx);
78816 dummyAx.range.reverse();
78817 contours.end = Axes.tickFirst(dummyAx);
78818
78819 if(contours.start === zmin) contours.start += contours.size;
78820 if(contours.end === zmax) contours.end -= contours.size;
78821
78822 // if you set a small ncontours, *and* the ends are exactly on zmin/zmax
78823 // there's an edge case where start > end now. Make sure there's at least
78824 // one meaningful contour, put it midway between the crossed values
78825 if(contours.start > contours.end) {
78826 contours.start = contours.end = (contours.start + contours.end) / 2;
78827 }
78828
78829 // copy auto-contour info back to the source data.
78830 // previously we copied the whole contours object back, but that had
78831 // other info (coloring, showlines) that should be left to supplyDefaults
78832 if(!trace._input.contours) trace._input.contours = {};
78833 Lib.extendFlat(trace._input.contours, {
78834 start: contours.start,
78835 end: contours.end,
78836 size: contours.size
78837 });
78838 trace._input.autocontour = true;
78839 } else if(contours.type !== 'constraint') {
78840 // sanity checks on manually-supplied start/end/size
78841 var start = contours.start;
78842 var end = contours.end;
78843 var inputContours = trace._input.contours;
78844
78845 if(start > end) {
78846 contours.start = inputContours.start = end;
78847 end = contours.end = inputContours.end = start;
78848 start = contours.start;
78849 }
78850
78851 if(!(contours.size > 0)) {
78852 var sizeOut;
78853 if(start === end) sizeOut = 1;
78854 else sizeOut = autoContours(start, end, trace.ncontours).dtick;
78855
78856 inputContours.size = contours.size = sizeOut;
78857 }
78858 }
78859};
78860
78861
78862/*
78863 * autoContours: make a dummy axis object with dtick we can use
78864 * as contours.size, and if needed we can use Axes.tickFirst
78865 * with this axis object to calculate the start and end too
78866 *
78867 * start: the value to start the contours at
78868 * end: the value to end at (must be > start)
78869 * ncontours: max number of contours to make, like roughDTick
78870 *
78871 * returns: an axis object
78872 */
78873function autoContours(start, end, ncontours) {
78874 var dummyAx = {
78875 type: 'linear',
78876 range: [start, end]
78877 };
78878
78879 Axes.autoTicks(
78880 dummyAx,
78881 (end - start) / (ncontours || 15)
78882 );
78883
78884 return dummyAx;
78885}
78886
78887},{"../../lib":178,"../../plots/cartesian/axes":222}],328:[function(_dereq_,module,exports){
78888/**
78889* Copyright 2012-2020, Plotly, Inc.
78890* All rights reserved.
78891*
78892* This source code is licensed under the MIT license found in the
78893* LICENSE file in the root directory of this source tree.
78894*/
78895
78896
78897'use strict';
78898
78899var d3 = _dereq_('d3');
78900
78901var Drawing = _dereq_('../../components/drawing');
78902var heatmapStyle = _dereq_('../heatmap/style');
78903
78904var makeColorMap = _dereq_('./make_color_map');
78905
78906
78907module.exports = function style(gd) {
78908 var contours = d3.select(gd).selectAll('g.contour');
78909
78910 contours.style('opacity', function(d) {
78911 return d[0].trace.opacity;
78912 });
78913
78914 contours.each(function(d) {
78915 var c = d3.select(this);
78916 var trace = d[0].trace;
78917 var contours = trace.contours;
78918 var line = trace.line;
78919 var cs = contours.size || 1;
78920 var start = contours.start;
78921
78922 // for contourcarpet only - is this a constraint-type contour trace?
78923 var isConstraintType = contours.type === 'constraint';
78924 var colorLines = !isConstraintType && contours.coloring === 'lines';
78925 var colorFills = !isConstraintType && contours.coloring === 'fill';
78926
78927 var colorMap = (colorLines || colorFills) ? makeColorMap(trace) : null;
78928
78929 c.selectAll('g.contourlevel').each(function(d) {
78930 d3.select(this).selectAll('path')
78931 .call(Drawing.lineGroupStyle,
78932 line.width,
78933 colorLines ? colorMap(d.level) : line.color,
78934 line.dash);
78935 });
78936
78937 var labelFont = contours.labelfont;
78938 c.selectAll('g.contourlabels text').each(function(d) {
78939 Drawing.font(d3.select(this), {
78940 family: labelFont.family,
78941 size: labelFont.size,
78942 color: labelFont.color || (colorLines ? colorMap(d.level) : line.color)
78943 });
78944 });
78945
78946 if(isConstraintType) {
78947 c.selectAll('g.contourfill path')
78948 .style('fill', trace.fillcolor);
78949 } else if(colorFills) {
78950 var firstFill;
78951
78952 c.selectAll('g.contourfill path')
78953 .style('fill', function(d) {
78954 if(firstFill === undefined) firstFill = d.level;
78955 return colorMap(d.level + 0.5 * cs);
78956 });
78957
78958 if(firstFill === undefined) firstFill = start;
78959
78960 c.selectAll('g.contourbg path')
78961 .style('fill', colorMap(firstFill - 0.5 * cs));
78962 }
78963 });
78964
78965 heatmapStyle(gd);
78966};
78967
78968},{"../../components/drawing":74,"../heatmap/style":342,"./make_color_map":324,"d3":16}],329:[function(_dereq_,module,exports){
78969/**
78970* Copyright 2012-2020, Plotly, Inc.
78971* All rights reserved.
78972*
78973* This source code is licensed under the MIT license found in the
78974* LICENSE file in the root directory of this source tree.
78975*/
78976
78977
78978'use strict';
78979
78980var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
78981var handleLabelDefaults = _dereq_('./label_defaults');
78982
78983
78984module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, layout, opts) {
78985 var coloring = coerce('contours.coloring');
78986
78987 var showLines;
78988 var lineColor = '';
78989 if(coloring === 'fill') showLines = coerce('contours.showlines');
78990
78991 if(showLines !== false) {
78992 if(coloring !== 'lines') lineColor = coerce('line.color', '#000');
78993 coerce('line.width', 0.5);
78994 coerce('line.dash');
78995 }
78996
78997 if(coloring !== 'none') {
78998 // plots/plots always coerces showlegend to true, but in this case
78999 // we default to false and (by default) show a colorbar instead
79000 if(traceIn.showlegend !== true) traceOut.showlegend = false;
79001 traceOut._dfltShowLegend = false;
79002
79003 colorscaleDefaults(
79004 traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}
79005 );
79006 }
79007
79008 coerce('line.smoothing');
79009
79010 handleLabelDefaults(coerce, layout, lineColor, opts);
79011};
79012
79013},{"../../components/colorscale/defaults":62,"./label_defaults":323}],330:[function(_dereq_,module,exports){
79014/**
79015* Copyright 2012-2020, Plotly, Inc.
79016* All rights reserved.
79017*
79018* This source code is licensed under the MIT license found in the
79019* LICENSE file in the root directory of this source tree.
79020*/
79021
79022'use strict';
79023
79024var scatterAttrs = _dereq_('../scatter/attributes');
79025var baseAttrs = _dereq_('../../plots/attributes');
79026var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
79027var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
79028var FORMAT_LINK = _dereq_('../../constants/docs').FORMAT_LINK;
79029
79030var extendFlat = _dereq_('../../lib/extend').extendFlat;
79031
79032module.exports = extendFlat({
79033 z: {
79034 valType: 'data_array',
79035 editType: 'calc',
79036
79037 },
79038 x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}),
79039 x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}),
79040 dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}),
79041 y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}),
79042 y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}),
79043 dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}),
79044
79045 text: {
79046 valType: 'data_array',
79047 editType: 'calc',
79048
79049 },
79050 hovertext: {
79051 valType: 'data_array',
79052 editType: 'calc',
79053
79054 },
79055 transpose: {
79056 valType: 'boolean',
79057 dflt: false,
79058
79059 editType: 'calc',
79060
79061 },
79062 xtype: {
79063 valType: 'enumerated',
79064 values: ['array', 'scaled'],
79065
79066 editType: 'calc+clearAxisTypes',
79067
79068 },
79069 ytype: {
79070 valType: 'enumerated',
79071 values: ['array', 'scaled'],
79072
79073 editType: 'calc+clearAxisTypes',
79074
79075 },
79076 zsmooth: {
79077 valType: 'enumerated',
79078 values: ['fast', 'best', false],
79079 dflt: false,
79080
79081 editType: 'calc',
79082
79083 },
79084 hoverongaps: {
79085 valType: 'boolean',
79086 dflt: true,
79087
79088 editType: 'none',
79089
79090 },
79091 connectgaps: {
79092 valType: 'boolean',
79093
79094 editType: 'calc',
79095
79096 },
79097 xgap: {
79098 valType: 'number',
79099 dflt: 0,
79100 min: 0,
79101
79102 editType: 'plot',
79103
79104 },
79105 ygap: {
79106 valType: 'number',
79107 dflt: 0,
79108 min: 0,
79109
79110 editType: 'plot',
79111
79112 },
79113 zhoverformat: {
79114 valType: 'string',
79115 dflt: '',
79116
79117 editType: 'none',
79118
79119 },
79120 hovertemplate: hovertemplateAttrs(),
79121 showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
79122}, {
79123 transforms: undefined
79124},
79125 colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
79126);
79127
79128},{"../../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){
79129/**
79130* Copyright 2012-2020, Plotly, Inc.
79131* All rights reserved.
79132*
79133* This source code is licensed under the MIT license found in the
79134* LICENSE file in the root directory of this source tree.
79135*/
79136
79137'use strict';
79138
79139var Registry = _dereq_('../../registry');
79140var Lib = _dereq_('../../lib');
79141var Axes = _dereq_('../../plots/cartesian/axes');
79142
79143var histogram2dCalc = _dereq_('../histogram2d/calc');
79144var colorscaleCalc = _dereq_('../../components/colorscale/calc');
79145var convertColumnData = _dereq_('./convert_column_xyz');
79146var clean2dArray = _dereq_('./clean_2d_array');
79147var interp2d = _dereq_('./interp2d');
79148var findEmpties = _dereq_('./find_empties');
79149var makeBoundArray = _dereq_('./make_bound_array');
79150var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79151
79152module.exports = function calc(gd, trace) {
79153 // prepare the raw data
79154 // run makeCalcdata on x and y even for heatmaps, in case of category mappings
79155 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
79156 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
79157 var isContour = Registry.traceIs(trace, 'contour');
79158 var isHist = Registry.traceIs(trace, 'histogram');
79159 var isGL2D = Registry.traceIs(trace, 'gl2d');
79160 var zsmooth = isContour ? 'best' : trace.zsmooth;
79161 var x;
79162 var x0;
79163 var dx;
79164 var y;
79165 var y0;
79166 var dy;
79167 var z;
79168 var i;
79169 var binned;
79170
79171 // cancel minimum tick spacings (only applies to bars and boxes)
79172 xa._minDtick = 0;
79173 ya._minDtick = 0;
79174
79175 if(isHist) {
79176 binned = histogram2dCalc(gd, trace);
79177 x = binned.x;
79178 x0 = binned.x0;
79179 dx = binned.dx;
79180 y = binned.y;
79181 y0 = binned.y0;
79182 dy = binned.dy;
79183 z = binned.z;
79184 } else {
79185 var zIn = trace.z;
79186 if(Lib.isArray1D(zIn)) {
79187 convertColumnData(trace, xa, ya, 'x', 'y', ['z']);
79188 x = trace._x;
79189 y = trace._y;
79190 zIn = trace._z;
79191 } else {
79192 x = trace._x = trace.x ? xa.makeCalcdata(trace, 'x') : [];
79193 y = trace._y = trace.y ? ya.makeCalcdata(trace, 'y') : [];
79194 }
79195
79196 x0 = trace.x0;
79197 dx = trace.dx;
79198 y0 = trace.y0;
79199 dy = trace.dy;
79200
79201 z = clean2dArray(zIn, trace, xa, ya);
79202 }
79203
79204 if(xa.rangebreaks || ya.rangebreaks) {
79205 z = dropZonBreaks(x, y, z);
79206
79207 if(!isHist) {
79208 x = skipBreaks(x);
79209 y = skipBreaks(y);
79210
79211 trace._x = x;
79212 trace._y = y;
79213 }
79214 }
79215
79216 if(!isHist && (isContour || trace.connectgaps)) {
79217 trace._emptypoints = findEmpties(z);
79218 interp2d(z, trace._emptypoints);
79219 }
79220
79221 function noZsmooth(msg) {
79222 zsmooth = trace._input.zsmooth = trace.zsmooth = false;
79223 Lib.warn('cannot use zsmooth: "fast": ' + msg);
79224 }
79225
79226 // check whether we really can smooth (ie all boxes are about the same size)
79227 if(zsmooth === 'fast') {
79228 if(xa.type === 'log' || ya.type === 'log') {
79229 noZsmooth('log axis found');
79230 } else if(!isHist) {
79231 if(x.length) {
79232 var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1);
79233 var maxErrX = Math.abs(avgdx / 100);
79234 for(i = 0; i < x.length - 1; i++) {
79235 if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) {
79236 noZsmooth('x scale is not linear');
79237 break;
79238 }
79239 }
79240 }
79241 if(y.length && zsmooth === 'fast') {
79242 var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1);
79243 var maxErrY = Math.abs(avgdy / 100);
79244 for(i = 0; i < y.length - 1; i++) {
79245 if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) {
79246 noZsmooth('y scale is not linear');
79247 break;
79248 }
79249 }
79250 }
79251 }
79252 }
79253
79254 // create arrays of brick boundaries, to be used by autorange and heatmap.plot
79255 var xlen = Lib.maxRowLength(z);
79256 var xIn = trace.xtype === 'scaled' ? '' : x;
79257 var xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa);
79258 var yIn = trace.ytype === 'scaled' ? '' : y;
79259 var yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya);
79260
79261 // handled in gl2d convert step
79262 if(!isGL2D) {
79263 trace._extremes[xa._id] = Axes.findExtremes(xa, xArray);
79264 trace._extremes[ya._id] = Axes.findExtremes(ya, yArray);
79265 }
79266
79267 var cd0 = {
79268 x: xArray,
79269 y: yArray,
79270 z: z,
79271 text: trace._text || trace.text,
79272 hovertext: trace._hovertext || trace.hovertext
79273 };
79274
79275 if(xIn && xIn.length === xArray.length - 1) cd0.xCenter = xIn;
79276 if(yIn && yIn.length === yArray.length - 1) cd0.yCenter = yIn;
79277
79278 if(isHist) {
79279 cd0.xRanges = binned.xRanges;
79280 cd0.yRanges = binned.yRanges;
79281 cd0.pts = binned.pts;
79282 }
79283
79284 if(!isContour) {
79285 colorscaleCalc(gd, trace, {vals: z, cLetter: 'z'});
79286 }
79287
79288 if(isContour && trace.contours && trace.contours.coloring === 'heatmap') {
79289 var dummyTrace = {
79290 type: trace.type === 'contour' ? 'heatmap' : 'histogram2d',
79291 xcalendar: trace.xcalendar,
79292 ycalendar: trace.ycalendar
79293 };
79294 cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa);
79295 cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya);
79296 }
79297
79298 return [cd0];
79299};
79300
79301function skipBreaks(a) {
79302 var b = [];
79303 var len = a.length;
79304 for(var i = 0; i < len; i++) {
79305 var v = a[i];
79306 if(v !== BADNUM) b.push(v);
79307 }
79308 return b;
79309}
79310
79311function dropZonBreaks(x, y, z) {
79312 var newZ = [];
79313 var k = -1;
79314 for(var i = 0; i < z.length; i++) {
79315 if(y[i] === BADNUM) continue;
79316 k++;
79317 newZ[k] = [];
79318 for(var j = 0; j < z[i].length; j++) {
79319 if(x[j] === BADNUM) continue;
79320
79321 newZ[k].push(z[i][j]);
79322 }
79323 }
79324 return newZ;
79325}
79326
79327},{"../../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){
79328/**
79329* Copyright 2012-2020, Plotly, Inc.
79330* All rights reserved.
79331*
79332* This source code is licensed under the MIT license found in the
79333* LICENSE file in the root directory of this source tree.
79334*/
79335
79336'use strict';
79337
79338var isNumeric = _dereq_('fast-isnumeric');
79339var Lib = _dereq_('../../lib');
79340var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79341
79342module.exports = function clean2dArray(zOld, trace, xa, ya) {
79343 var rowlen, collen, getCollen, old2new, i, j;
79344
79345 function cleanZvalue(v) {
79346 if(!isNumeric(v)) return undefined;
79347 return +v;
79348 }
79349
79350 if(trace && trace.transpose) {
79351 rowlen = 0;
79352 for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length);
79353 if(rowlen === 0) return false;
79354 getCollen = function(zOld) { return zOld.length; };
79355 old2new = function(zOld, i, j) { return (zOld[j] || [])[i]; };
79356 } else {
79357 rowlen = zOld.length;
79358 getCollen = function(zOld, i) { return zOld[i].length; };
79359 old2new = function(zOld, i, j) { return (zOld[i] || [])[j]; };
79360 }
79361
79362 var padOld2new = function(zOld, i, j) {
79363 if(i === BADNUM || j === BADNUM) return BADNUM;
79364 return old2new(zOld, i, j);
79365 };
79366
79367 function axisMapping(ax) {
79368 if(trace && trace.type !== 'carpet' && trace.type !== 'contourcarpet' &&
79369 ax && ax.type === 'category' && trace['_' + ax._id.charAt(0)].length) {
79370 var axLetter = ax._id.charAt(0);
79371 var axMapping = {};
79372 var traceCategories = trace['_' + axLetter + 'CategoryMap'] || trace[axLetter];
79373 for(i = 0; i < traceCategories.length; i++) {
79374 axMapping[traceCategories[i]] = i;
79375 }
79376 return function(i) {
79377 var ind = axMapping[ax._categories[i]];
79378 return ind + 1 ? ind : BADNUM;
79379 };
79380 } else {
79381 return Lib.identity;
79382 }
79383 }
79384
79385 var xMap = axisMapping(xa);
79386 var yMap = axisMapping(ya);
79387
79388 if(ya && ya.type === 'category') rowlen = ya._categories.length;
79389 var zNew = new Array(rowlen);
79390
79391 for(i = 0; i < rowlen; i++) {
79392 if(xa && xa.type === 'category') {
79393 collen = xa._categories.length;
79394 } else {
79395 collen = getCollen(zOld, i);
79396 }
79397 zNew[i] = new Array(collen);
79398 for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(padOld2new(zOld, yMap(i), xMap(j)));
79399 }
79400
79401 return zNew;
79402};
79403
79404},{"../../constants/numerical":158,"../../lib":178,"fast-isnumeric":18}],333:[function(_dereq_,module,exports){
79405/**
79406* Copyright 2012-2020, Plotly, Inc.
79407* All rights reserved.
79408*
79409* This source code is licensed under the MIT license found in the
79410* LICENSE file in the root directory of this source tree.
79411*/
79412
79413'use strict';
79414
79415module.exports = {
79416 min: 'zmin',
79417 max: 'zmax'
79418};
79419
79420},{}],334:[function(_dereq_,module,exports){
79421/**
79422* Copyright 2012-2020, Plotly, Inc.
79423* All rights reserved.
79424*
79425* This source code is licensed under the MIT license found in the
79426* LICENSE file in the root directory of this source tree.
79427*/
79428
79429
79430'use strict';
79431
79432var Lib = _dereq_('../../lib');
79433var BADNUM = _dereq_('../../constants/numerical').BADNUM;
79434
79435module.exports = function convertColumnData(trace, ax1, ax2, var1Name, var2Name, arrayVarNames) {
79436 var colLen = trace._length;
79437 var col1 = ax1.makeCalcdata(trace, var1Name);
79438 var col2 = ax2.makeCalcdata(trace, var2Name);
79439 var textCol = trace.text;
79440 var hasColumnText = (textCol !== undefined && Lib.isArray1D(textCol));
79441 var hoverTextCol = trace.hovertext;
79442 var hasColumnHoverText = (hoverTextCol !== undefined && Lib.isArray1D(hoverTextCol));
79443 var i, j;
79444
79445 var col1dv = Lib.distinctVals(col1);
79446 var col1vals = col1dv.vals;
79447 var col2dv = Lib.distinctVals(col2);
79448 var col2vals = col2dv.vals;
79449 var newArrays = [];
79450 var text;
79451 var hovertext;
79452
79453 var nI = col2vals.length;
79454 var nJ = col1vals.length;
79455
79456 for(i = 0; i < arrayVarNames.length; i++) {
79457 newArrays[i] = Lib.init2dArray(nI, nJ);
79458 }
79459
79460 if(hasColumnText) {
79461 text = Lib.init2dArray(nI, nJ);
79462 }
79463 if(hasColumnHoverText) {
79464 hovertext = Lib.init2dArray(nI, nJ);
79465 }
79466
79467 var after2before = Lib.init2dArray(nI, nJ);
79468
79469 for(i = 0; i < colLen; i++) {
79470 if(col1[i] !== BADNUM && col2[i] !== BADNUM) {
79471 var i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals);
79472 var i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals);
79473
79474 for(j = 0; j < arrayVarNames.length; j++) {
79475 var arrayVarName = arrayVarNames[j];
79476 var arrayVar = trace[arrayVarName];
79477 var newArray = newArrays[j];
79478 newArray[i2][i1] = arrayVar[i];
79479 after2before[i2][i1] = i;
79480 }
79481
79482 if(hasColumnText) text[i2][i1] = textCol[i];
79483 if(hasColumnHoverText) hovertext[i2][i1] = hoverTextCol[i];
79484 }
79485 }
79486
79487 trace['_' + var1Name] = col1vals;
79488 trace['_' + var2Name] = col2vals;
79489 for(j = 0; j < arrayVarNames.length; j++) {
79490 trace['_' + arrayVarNames[j]] = newArrays[j];
79491 }
79492 if(hasColumnText) trace._text = text;
79493 if(hasColumnHoverText) trace._hovertext = hovertext;
79494
79495 if(ax1 && ax1.type === 'category') {
79496 trace['_' + var1Name + 'CategoryMap'] = col1vals.map(function(v) { return ax1._categories[v];});
79497 }
79498
79499 if(ax2 && ax2.type === 'category') {
79500 trace['_' + var2Name + 'CategoryMap'] = col2vals.map(function(v) { return ax2._categories[v];});
79501 }
79502
79503 trace._after2before = after2before;
79504};
79505
79506},{"../../constants/numerical":158,"../../lib":178}],335:[function(_dereq_,module,exports){
79507/**
79508* Copyright 2012-2020, Plotly, Inc.
79509* All rights reserved.
79510*
79511* This source code is licensed under the MIT license found in the
79512* LICENSE file in the root directory of this source tree.
79513*/
79514
79515
79516'use strict';
79517
79518var Lib = _dereq_('../../lib');
79519
79520var handleXYZDefaults = _dereq_('./xyz_defaults');
79521var handleStyleDefaults = _dereq_('./style_defaults');
79522var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
79523var attributes = _dereq_('./attributes');
79524
79525
79526module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
79527 function coerce(attr, dflt) {
79528 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
79529 }
79530
79531 var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout);
79532 if(!validData) {
79533 traceOut.visible = false;
79534 return;
79535 }
79536
79537 coerce('text');
79538 coerce('hovertext');
79539 coerce('hovertemplate');
79540
79541 handleStyleDefaults(traceIn, traceOut, coerce, layout);
79542
79543 coerce('hoverongaps');
79544 coerce('connectgaps', Lib.isArray1D(traceOut.z) && (traceOut.zsmooth !== false));
79545
79546 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
79547};
79548
79549},{"../../components/colorscale/defaults":62,"../../lib":178,"./attributes":330,"./style_defaults":343,"./xyz_defaults":344}],336:[function(_dereq_,module,exports){
79550/**
79551* Copyright 2012-2020, Plotly, Inc.
79552* All rights reserved.
79553*
79554* This source code is licensed under the MIT license found in the
79555* LICENSE file in the root directory of this source tree.
79556*/
79557
79558'use strict';
79559
79560var maxRowLength = _dereq_('../../lib').maxRowLength;
79561
79562/* Return a list of empty points in 2D array z
79563 * each empty point z[i][j] gives an array [i, j, neighborCount]
79564 * neighborCount is the count of 4 nearest neighbors that DO exist
79565 * this is to give us an order of points to evaluate for interpolation.
79566 * if no neighbors exist, we iteratively look for neighbors that HAVE
79567 * neighbors, and add a fractional neighborCount
79568 */
79569module.exports = function findEmpties(z) {
79570 var empties = [];
79571 var neighborHash = {};
79572 var noNeighborList = [];
79573 var nextRow = z[0];
79574 var row = [];
79575 var blank = [0, 0, 0];
79576 var rowLength = maxRowLength(z);
79577 var prevRow;
79578 var i;
79579 var j;
79580 var thisPt;
79581 var p;
79582 var neighborCount;
79583 var newNeighborHash;
79584 var foundNewNeighbors;
79585
79586 for(i = 0; i < z.length; i++) {
79587 prevRow = row;
79588 row = nextRow;
79589 nextRow = z[i + 1] || [];
79590 for(j = 0; j < rowLength; j++) {
79591 if(row[j] === undefined) {
79592 neighborCount = (row[j - 1] !== undefined ? 1 : 0) +
79593 (row[j + 1] !== undefined ? 1 : 0) +
79594 (prevRow[j] !== undefined ? 1 : 0) +
79595 (nextRow[j] !== undefined ? 1 : 0);
79596
79597 if(neighborCount) {
79598 // for this purpose, don't count off-the-edge points
79599 // as undefined neighbors
79600 if(i === 0) neighborCount++;
79601 if(j === 0) neighborCount++;
79602 if(i === z.length - 1) neighborCount++;
79603 if(j === row.length - 1) neighborCount++;
79604
79605 // if all neighbors that could exist do, we don't
79606 // need this for finding farther neighbors
79607 if(neighborCount < 4) {
79608 neighborHash[[i, j]] = [i, j, neighborCount];
79609 }
79610
79611 empties.push([i, j, neighborCount]);
79612 } else noNeighborList.push([i, j]);
79613 }
79614 }
79615 }
79616
79617 while(noNeighborList.length) {
79618 newNeighborHash = {};
79619 foundNewNeighbors = false;
79620
79621 // look for cells that now have neighbors but didn't before
79622 for(p = noNeighborList.length - 1; p >= 0; p--) {
79623 thisPt = noNeighborList[p];
79624 i = thisPt[0];
79625 j = thisPt[1];
79626
79627 neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] +
79628 (neighborHash[[i + 1, j]] || blank)[2] +
79629 (neighborHash[[i, j - 1]] || blank)[2] +
79630 (neighborHash[[i, j + 1]] || blank)[2]) / 20;
79631
79632 if(neighborCount) {
79633 newNeighborHash[thisPt] = [i, j, neighborCount];
79634 noNeighborList.splice(p, 1);
79635 foundNewNeighbors = true;
79636 }
79637 }
79638
79639 if(!foundNewNeighbors) {
79640 throw 'findEmpties iterated with no new neighbors';
79641 }
79642
79643 // put these new cells into the main neighbor list
79644 for(thisPt in newNeighborHash) {
79645 neighborHash[thisPt] = newNeighborHash[thisPt];
79646 empties.push(newNeighborHash[thisPt]);
79647 }
79648 }
79649
79650 // sort the full list in descending order of neighbor count
79651 return empties.sort(function(a, b) { return b[2] - a[2]; });
79652};
79653
79654},{"../../lib":178}],337:[function(_dereq_,module,exports){
79655/**
79656* Copyright 2012-2020, Plotly, Inc.
79657* All rights reserved.
79658*
79659* This source code is licensed under the MIT license found in the
79660* LICENSE file in the root directory of this source tree.
79661*/
79662
79663'use strict';
79664
79665var Fx = _dereq_('../../components/fx');
79666var Lib = _dereq_('../../lib');
79667var Axes = _dereq_('../../plots/cartesian/axes');
79668var extractOpts = _dereq_('../../components/colorscale').extractOpts;
79669
79670module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) {
79671 var cd0 = pointData.cd[0];
79672 var trace = cd0.trace;
79673 var xa = pointData.xa;
79674 var ya = pointData.ya;
79675 var x = cd0.x;
79676 var y = cd0.y;
79677 var z = cd0.z;
79678 var xc = cd0.xCenter;
79679 var yc = cd0.yCenter;
79680 var zmask = cd0.zmask;
79681 var zhoverformat = trace.zhoverformat;
79682 var x2 = x;
79683 var y2 = y;
79684
79685 var xl, yl, nx, ny;
79686
79687 if(pointData.index !== false) {
79688 try {
79689 nx = Math.round(pointData.index[1]);
79690 ny = Math.round(pointData.index[0]);
79691 } catch(e) {
79692 Lib.error('Error hovering on heatmap, ' +
79693 'pointNumber must be [row,col], found:', pointData.index);
79694 return;
79695 }
79696 if(nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) {
79697 return;
79698 }
79699 } else if(Fx.inbox(xval - x[0], xval - x[x.length - 1], 0) > 0 ||
79700 Fx.inbox(yval - y[0], yval - y[y.length - 1], 0) > 0) {
79701 return;
79702 } else {
79703 if(contour) {
79704 var i2;
79705 x2 = [2 * x[0] - x[1]];
79706
79707 for(i2 = 1; i2 < x.length; i2++) {
79708 x2.push((x[i2] + x[i2 - 1]) / 2);
79709 }
79710 x2.push([2 * x[x.length - 1] - x[x.length - 2]]);
79711
79712 y2 = [2 * y[0] - y[1]];
79713 for(i2 = 1; i2 < y.length; i2++) {
79714 y2.push((y[i2] + y[i2 - 1]) / 2);
79715 }
79716 y2.push([2 * y[y.length - 1] - y[y.length - 2]]);
79717 }
79718 nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2)));
79719 ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2)));
79720 }
79721
79722 var x0 = xa.c2p(x[nx]);
79723 var x1 = xa.c2p(x[nx + 1]);
79724 var y0 = ya.c2p(y[ny]);
79725 var y1 = ya.c2p(y[ny + 1]);
79726
79727 if(contour) {
79728 x1 = x0;
79729 xl = x[nx];
79730 y1 = y0;
79731 yl = y[ny];
79732 } else {
79733 xl = xc ? xc[nx] : ((x[nx] + x[nx + 1]) / 2);
79734 yl = yc ? yc[ny] : ((y[ny] + y[ny + 1]) / 2);
79735
79736 if(xa && xa.type === 'category') xl = x[nx];
79737 if(ya && ya.type === 'category') yl = y[ny];
79738
79739 if(trace.zsmooth) {
79740 x0 = x1 = xa.c2p(xl);
79741 y0 = y1 = ya.c2p(yl);
79742 }
79743 }
79744
79745 var zVal = z[ny][nx];
79746 if(zmask && !zmask[ny][nx]) zVal = undefined;
79747
79748 if(zVal === undefined && !trace.hoverongaps) return;
79749
79750 var text;
79751 if(Array.isArray(cd0.hovertext) && Array.isArray(cd0.hovertext[ny])) {
79752 text = cd0.hovertext[ny][nx];
79753 } else if(Array.isArray(cd0.text) && Array.isArray(cd0.text[ny])) {
79754 text = cd0.text[ny][nx];
79755 }
79756
79757 // dummy axis for formatting the z value
79758 var cOpts = extractOpts(trace);
79759 var dummyAx = {
79760 type: 'linear',
79761 range: [cOpts.min, cOpts.max],
79762 hoverformat: zhoverformat,
79763 _separators: xa._separators,
79764 _numFormat: xa._numFormat
79765 };
79766 var zLabel = Axes.tickText(dummyAx, zVal, 'hover').text;
79767
79768 return [Lib.extendFlat(pointData, {
79769 index: trace._after2before ? trace._after2before[ny][nx] : [ny, nx],
79770 // never let a 2D override 1D type as closest point
79771 distance: pointData.maxHoverDistance,
79772 spikeDistance: pointData.maxSpikeDistance,
79773 x0: x0,
79774 x1: x1,
79775 y0: y0,
79776 y1: y1,
79777 xLabelVal: xl,
79778 yLabelVal: yl,
79779 zLabelVal: zVal,
79780 zLabel: zLabel,
79781 text: text
79782 })];
79783};
79784
79785},{"../../components/colorscale":64,"../../components/fx":92,"../../lib":178,"../../plots/cartesian/axes":222}],338:[function(_dereq_,module,exports){
79786/**
79787* Copyright 2012-2020, Plotly, Inc.
79788* All rights reserved.
79789*
79790* This source code is licensed under the MIT license found in the
79791* LICENSE file in the root directory of this source tree.
79792*/
79793
79794'use strict';
79795
79796module.exports = {
79797 attributes: _dereq_('./attributes'),
79798 supplyDefaults: _dereq_('./defaults'),
79799 calc: _dereq_('./calc'),
79800 plot: _dereq_('./plot'),
79801 colorbar: _dereq_('./colorbar'),
79802 style: _dereq_('./style'),
79803 hoverPoints: _dereq_('./hover'),
79804
79805 moduleType: 'trace',
79806 name: 'heatmap',
79807 basePlotModule: _dereq_('../../plots/cartesian'),
79808 categories: ['cartesian', 'svg', '2dMap', 'showLegend'],
79809 meta: {
79810
79811 }
79812};
79813
79814},{"../../plots/cartesian":235,"./attributes":330,"./calc":331,"./colorbar":333,"./defaults":335,"./hover":337,"./plot":341,"./style":342}],339:[function(_dereq_,module,exports){
79815/**
79816* Copyright 2012-2020, Plotly, Inc.
79817* All rights reserved.
79818*
79819* This source code is licensed under the MIT license found in the
79820* LICENSE file in the root directory of this source tree.
79821*/
79822
79823'use strict';
79824
79825var Lib = _dereq_('../../lib');
79826
79827var INTERPTHRESHOLD = 1e-2;
79828var NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]];
79829
79830function correctionOvershoot(maxFractionalChange) {
79831 // start with less overshoot, until we know it's converging,
79832 // then ramp up the overshoot for faster convergence
79833 return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5);
79834}
79835
79836/*
79837 * interp2d: Fill in missing data from a 2D array using an iterative
79838 * poisson equation solver with zero-derivative BC at edges.
79839 * Amazingly, this just amounts to repeatedly averaging all the existing
79840 * nearest neighbors, at least if we don't take x/y scaling into account,
79841 * which is the right approach here where x and y may not even have the
79842 * same units.
79843 *
79844 * @param {array of arrays} z
79845 * The 2D array to fill in. Will be mutated here. Assumed to already be
79846 * cleaned, so all entries are numbers except gaps, which are `undefined`.
79847 * @param {array of arrays} emptyPoints
79848 * Each entry [i, j, neighborCount] for empty points z[i][j] and the number
79849 * of neighbors that are *not* missing. Assumed to be sorted from most to
79850 * least neighbors, as produced by heatmap/find_empties.
79851 */
79852module.exports = function interp2d(z, emptyPoints) {
79853 var maxFractionalChange = 1;
79854 var i;
79855
79856 // one pass to fill in a starting value for all the empties
79857 iterateInterp2d(z, emptyPoints);
79858
79859 // we're don't need to iterate lone empties - remove them
79860 for(i = 0; i < emptyPoints.length; i++) {
79861 if(emptyPoints[i][2] < 4) break;
79862 }
79863 // but don't remove these points from the original array,
79864 // we'll use them for masking, so make a copy.
79865 emptyPoints = emptyPoints.slice(i);
79866
79867 for(i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) {
79868 maxFractionalChange = iterateInterp2d(z, emptyPoints,
79869 correctionOvershoot(maxFractionalChange));
79870 }
79871 if(maxFractionalChange > INTERPTHRESHOLD) {
79872 Lib.log('interp2d didn\'t converge quickly', maxFractionalChange);
79873 }
79874
79875 return z;
79876};
79877
79878function iterateInterp2d(z, emptyPoints, overshoot) {
79879 var maxFractionalChange = 0;
79880 var thisPt;
79881 var i;
79882 var j;
79883 var p;
79884 var q;
79885 var neighborShift;
79886 var neighborRow;
79887 var neighborVal;
79888 var neighborCount;
79889 var neighborSum;
79890 var initialVal;
79891 var minNeighbor;
79892 var maxNeighbor;
79893
79894 for(p = 0; p < emptyPoints.length; p++) {
79895 thisPt = emptyPoints[p];
79896 i = thisPt[0];
79897 j = thisPt[1];
79898 initialVal = z[i][j];
79899 neighborSum = 0;
79900 neighborCount = 0;
79901
79902 for(q = 0; q < 4; q++) {
79903 neighborShift = NEIGHBORSHIFTS[q];
79904 neighborRow = z[i + neighborShift[0]];
79905 if(!neighborRow) continue;
79906 neighborVal = neighborRow[j + neighborShift[1]];
79907 if(neighborVal !== undefined) {
79908 if(neighborSum === 0) {
79909 minNeighbor = maxNeighbor = neighborVal;
79910 } else {
79911 minNeighbor = Math.min(minNeighbor, neighborVal);
79912 maxNeighbor = Math.max(maxNeighbor, neighborVal);
79913 }
79914 neighborCount++;
79915 neighborSum += neighborVal;
79916 }
79917 }
79918
79919 if(neighborCount === 0) {
79920 throw 'iterateInterp2d order is wrong: no defined neighbors';
79921 }
79922
79923 // this is the laplace equation interpolation:
79924 // each point is just the average of its neighbors
79925 // note that this ignores differential x/y scaling
79926 // which I think is the right approach, since we
79927 // don't know what that scaling means
79928 z[i][j] = neighborSum / neighborCount;
79929
79930 if(initialVal === undefined) {
79931 if(neighborCount < 4) maxFractionalChange = 1;
79932 } else {
79933 // we can make large empty regions converge faster
79934 // if we overshoot the change vs the previous value
79935 z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal;
79936
79937 if(maxNeighbor > minNeighbor) {
79938 maxFractionalChange = Math.max(maxFractionalChange,
79939 Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor));
79940 }
79941 }
79942 }
79943
79944 return maxFractionalChange;
79945}
79946
79947},{"../../lib":178}],340:[function(_dereq_,module,exports){
79948/**
79949* Copyright 2012-2020, Plotly, Inc.
79950* All rights reserved.
79951*
79952* This source code is licensed under the MIT license found in the
79953* LICENSE file in the root directory of this source tree.
79954*/
79955
79956'use strict';
79957
79958var Registry = _dereq_('../../registry');
79959var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
79960
79961module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) {
79962 var arrayOut = [];
79963 var isContour = Registry.traceIs(trace, 'contour');
79964 var isHist = Registry.traceIs(trace, 'histogram');
79965 var isGL2D = Registry.traceIs(trace, 'gl2d');
79966 var v0;
79967 var dv;
79968 var i;
79969
79970 var isArrayOfTwoItemsOrMore = isArrayOrTypedArray(arrayIn) && arrayIn.length > 1;
79971
79972 if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) {
79973 var len = arrayIn.length;
79974
79975 // given vals are brick centers
79976 // hopefully length === numbricks, but use this method even if too few are supplied
79977 // and extend it linearly based on the last two points
79978 if(len <= numbricks) {
79979 // contour plots only want the centers
79980 if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks);
79981 else if(numbricks === 1) {
79982 arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5];
79983 } else {
79984 arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]];
79985
79986 for(i = 1; i < len; i++) {
79987 arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5);
79988 }
79989
79990 arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]);
79991 }
79992
79993 if(len < numbricks) {
79994 var lastPt = arrayOut[arrayOut.length - 1];
79995 var delta = lastPt - arrayOut[arrayOut.length - 2];
79996
79997 for(i = len; i < numbricks; i++) {
79998 lastPt += delta;
79999 arrayOut.push(lastPt);
80000 }
80001 }
80002 } else {
80003 // hopefully length === numbricks+1, but do something regardless:
80004 // given vals are brick boundaries
80005 return isContour ?
80006 arrayIn.slice(0, numbricks) : // we must be strict for contours
80007 arrayIn.slice(0, numbricks + 1);
80008 }
80009 } else {
80010 var calendar = trace[ax._id.charAt(0) + 'calendar'];
80011
80012 if(isHist) {
80013 v0 = ax.r2c(v0In, 0, calendar);
80014 } else {
80015 if(isArrayOrTypedArray(arrayIn) && arrayIn.length === 1) {
80016 v0 = arrayIn[0];
80017 } else if(v0In === undefined) {
80018 v0 = 0;
80019 } else {
80020 var fn = ax.type === 'log' ? ax.d2c : ax.r2c;
80021 v0 = fn(v0In, 0, calendar);
80022 }
80023 }
80024
80025 dv = dvIn || 1;
80026
80027 for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) {
80028 arrayOut.push(v0 + dv * i);
80029 }
80030 }
80031
80032 return arrayOut;
80033};
80034
80035},{"../../lib":178,"../../registry":269}],341:[function(_dereq_,module,exports){
80036/**
80037* Copyright 2012-2020, Plotly, Inc.
80038* All rights reserved.
80039*
80040* This source code is licensed under the MIT license found in the
80041* LICENSE file in the root directory of this source tree.
80042*/
80043
80044
80045'use strict';
80046
80047var d3 = _dereq_('d3');
80048var tinycolor = _dereq_('tinycolor2');
80049
80050var Registry = _dereq_('../../registry');
80051var Lib = _dereq_('../../lib');
80052var makeColorScaleFuncFromTrace = _dereq_('../../components/colorscale').makeColorScaleFuncFromTrace;
80053var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
80054
80055module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) {
80056 var xa = plotinfo.xaxis;
80057 var ya = plotinfo.yaxis;
80058
80059 Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function(cd) {
80060 var plotGroup = d3.select(this);
80061 var cd0 = cd[0];
80062 var trace = cd0.trace;
80063
80064 var z = cd0.z;
80065 var x = cd0.x;
80066 var y = cd0.y;
80067 var xc = cd0.xCenter;
80068 var yc = cd0.yCenter;
80069 var isContour = Registry.traceIs(trace, 'contour');
80070 var zsmooth = isContour ? 'best' : trace.zsmooth;
80071
80072 // get z dims
80073 var m = z.length;
80074 var n = Lib.maxRowLength(z);
80075 var xrev = false;
80076 var yrev = false;
80077
80078 var left, right, temp, top, bottom, i;
80079
80080 // TODO: if there are multiple overlapping categorical heatmaps,
80081 // or if we allow category sorting, then the categories may not be
80082 // sequential... may need to reorder and/or expand z
80083
80084 // Get edges of png in pixels (xa.c2p() maps axes coordinates to pixel coordinates)
80085 // figure out if either axis is reversed (y is usually reversed, in pixel coords)
80086 // also clip the image to maximum 50% outside the visible plot area
80087 // bigger image lets you pan more naturally, but slows performance.
80088 // TODO: use low-resolution images outside the visible plot for panning
80089 // these while loops find the first and last brick bounds that are defined
80090 // (in case of log of a negative)
80091 i = 0;
80092 while(left === undefined && i < x.length - 1) {
80093 left = xa.c2p(x[i]);
80094 i++;
80095 }
80096 i = x.length - 1;
80097 while(right === undefined && i > 0) {
80098 right = xa.c2p(x[i]);
80099 i--;
80100 }
80101
80102 if(right < left) {
80103 temp = right;
80104 right = left;
80105 left = temp;
80106 xrev = true;
80107 }
80108
80109 i = 0;
80110 while(top === undefined && i < y.length - 1) {
80111 top = ya.c2p(y[i]);
80112 i++;
80113 }
80114 i = y.length - 1;
80115 while(bottom === undefined && i > 0) {
80116 bottom = ya.c2p(y[i]);
80117 i--;
80118 }
80119
80120 if(bottom < top) {
80121 temp = top;
80122 top = bottom;
80123 bottom = temp;
80124 yrev = true;
80125 }
80126
80127 // for contours with heatmap fill, we generate the boundaries based on
80128 // brick centers but then use the brick edges for drawing the bricks
80129 if(isContour) {
80130 xc = x;
80131 yc = y;
80132 x = cd0.xfill;
80133 y = cd0.yfill;
80134 }
80135
80136 // make an image that goes at most half a screen off either side, to keep
80137 // time reasonable when you zoom in. if zsmooth is true/fast, don't worry
80138 // about this, because zooming doesn't increase number of pixels
80139 // if zsmooth is best, don't include anything off screen because it takes too long
80140 if(zsmooth !== 'fast') {
80141 var extra = zsmooth === 'best' ? 0 : 0.5;
80142 left = Math.max(-extra * xa._length, left);
80143 right = Math.min((1 + extra) * xa._length, right);
80144 top = Math.max(-extra * ya._length, top);
80145 bottom = Math.min((1 + extra) * ya._length, bottom);
80146 }
80147
80148 var imageWidth = Math.round(right - left);
80149 var imageHeight = Math.round(bottom - top);
80150
80151 // setup image nodes
80152
80153 // if image is entirely off-screen, don't even draw it
80154 var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
80155
80156 if(isOffScreen) {
80157 var noImage = plotGroup.selectAll('image').data([]);
80158 noImage.exit().remove();
80159 return;
80160 }
80161
80162 // generate image data
80163
80164 var canvasW, canvasH;
80165 if(zsmooth === 'fast') {
80166 canvasW = n;
80167 canvasH = m;
80168 } else {
80169 canvasW = imageWidth;
80170 canvasH = imageHeight;
80171 }
80172
80173 var canvas = document.createElement('canvas');
80174 canvas.width = canvasW;
80175 canvas.height = canvasH;
80176 var context = canvas.getContext('2d');
80177
80178 var sclFunc = makeColorScaleFuncFromTrace(trace, {noNumericCheck: true, returnArray: true});
80179
80180 // map brick boundaries to image pixels
80181 var xpx,
80182 ypx;
80183 if(zsmooth === 'fast') {
80184 xpx = xrev ?
80185 function(index) { return n - 1 - index; } :
80186 Lib.identity;
80187 ypx = yrev ?
80188 function(index) { return m - 1 - index; } :
80189 Lib.identity;
80190 } else {
80191 xpx = function(index) {
80192 return Lib.constrain(Math.round(xa.c2p(x[index]) - left),
80193 0, imageWidth);
80194 };
80195 ypx = function(index) {
80196 return Lib.constrain(Math.round(ya.c2p(y[index]) - top),
80197 0, imageHeight);
80198 };
80199 }
80200
80201 // build the pixel map brick-by-brick
80202 // cruise through z-matrix row-by-row
80203 // build a brick at each z-matrix value
80204 var yi = ypx(0);
80205 var yb = [yi, yi];
80206 var xbi = xrev ? 0 : 1;
80207 var ybi = yrev ? 0 : 1;
80208 // for collecting an average luminosity of the heatmap
80209 var pixcount = 0;
80210 var rcount = 0;
80211 var gcount = 0;
80212 var bcount = 0;
80213
80214 var xb, j, xi, v, row, c;
80215
80216 function setColor(v, pixsize) {
80217 if(v !== undefined) {
80218 var c = sclFunc(v);
80219 c[0] = Math.round(c[0]);
80220 c[1] = Math.round(c[1]);
80221 c[2] = Math.round(c[2]);
80222
80223 pixcount += pixsize;
80224 rcount += c[0] * pixsize;
80225 gcount += c[1] * pixsize;
80226 bcount += c[2] * pixsize;
80227 return c;
80228 }
80229 return [0, 0, 0, 0];
80230 }
80231
80232 function interpColor(r0, r1, xinterp, yinterp) {
80233 var z00 = r0[xinterp.bin0];
80234 if(z00 === undefined) return setColor(undefined, 1);
80235
80236 var z01 = r0[xinterp.bin1];
80237 var z10 = r1[xinterp.bin0];
80238 var z11 = r1[xinterp.bin1];
80239 var dx = (z01 - z00) || 0;
80240 var dy = (z10 - z00) || 0;
80241 var dxy;
80242
80243 // the bilinear interpolation term needs different calculations
80244 // for all the different permutations of missing data
80245 // among the neighbors of the main point, to ensure
80246 // continuity across brick boundaries.
80247 if(z01 === undefined) {
80248 if(z11 === undefined) dxy = 0;
80249 else if(z10 === undefined) dxy = 2 * (z11 - z00);
80250 else dxy = (2 * z11 - z10 - z00) * 2 / 3;
80251 } else if(z11 === undefined) {
80252 if(z10 === undefined) dxy = 0;
80253 else dxy = (2 * z00 - z01 - z10) * 2 / 3;
80254 } else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3;
80255 else dxy = (z11 + z00 - z01 - z10);
80256
80257 return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy));
80258 }
80259
80260 if(zsmooth) { // best or fast, works fastest with imageData
80261 var pxIndex = 0;
80262 var pixels;
80263
80264 try {
80265 pixels = new Uint8Array(imageWidth * imageHeight * 4);
80266 } catch(e) {
80267 pixels = new Array(imageWidth * imageHeight * 4);
80268 }
80269
80270 if(zsmooth === 'best') {
80271 var xForPx = xc || x;
80272 var yForPx = yc || y;
80273 var xPixArray = new Array(xForPx.length);
80274 var yPixArray = new Array(yForPx.length);
80275 var xinterpArray = new Array(imageWidth);
80276 var findInterpX = xc ? findInterpFromCenters : findInterp;
80277 var findInterpY = yc ? findInterpFromCenters : findInterp;
80278 var yinterp, r0, r1;
80279
80280 // first make arrays of x and y pixel locations of brick boundaries
80281 for(i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left);
80282 for(i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top);
80283
80284 // then make arrays of interpolations
80285 // (bin0=closest, bin1=next, frac=fractional dist.)
80286 for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray);
80287
80288 // now do the interpolations and fill the png
80289 for(j = 0; j < imageHeight; j++) {
80290 yinterp = findInterpY(j, yPixArray);
80291 r0 = z[yinterp.bin0];
80292 r1 = z[yinterp.bin1];
80293 for(i = 0; i < imageWidth; i++, pxIndex += 4) {
80294 c = interpColor(r0, r1, xinterpArray[i], yinterp);
80295 putColor(pixels, pxIndex, c);
80296 }
80297 }
80298 } else { // zsmooth = fast
80299 for(j = 0; j < m; j++) {
80300 row = z[j];
80301 yb = ypx(j);
80302 for(i = 0; i < imageWidth; i++) {
80303 c = setColor(row[i], 1);
80304 pxIndex = (yb * imageWidth + xpx(i)) * 4;
80305 putColor(pixels, pxIndex, c);
80306 }
80307 }
80308 }
80309
80310 var imageData = context.createImageData(imageWidth, imageHeight);
80311 try {
80312 imageData.data.set(pixels);
80313 } catch(e) {
80314 var pxArray = imageData.data;
80315 var dlen = pxArray.length;
80316 for(j = 0; j < dlen; j ++) {
80317 pxArray[j] = pixels[j];
80318 }
80319 }
80320
80321 context.putImageData(imageData, 0, 0);
80322 } else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect
80323 // gaps do not need to be exact integers, but if they *are* we will get
80324 // cleaner edges by rounding at least one edge
80325 var xGap = trace.xgap;
80326 var yGap = trace.ygap;
80327 var xGapLeft = Math.floor(xGap / 2);
80328 var yGapTop = Math.floor(yGap / 2);
80329
80330 for(j = 0; j < m; j++) {
80331 row = z[j];
80332 yb.reverse();
80333 yb[ybi] = ypx(j + 1);
80334 if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) {
80335 continue;
80336 }
80337 xi = xpx(0);
80338 xb = [xi, xi];
80339 for(i = 0; i < n; i++) {
80340 // build one color brick!
80341 xb.reverse();
80342 xb[xbi] = xpx(i + 1);
80343 if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) {
80344 continue;
80345 }
80346 v = row[i];
80347 c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0]));
80348 context.fillStyle = 'rgba(' + c.join(',') + ')';
80349
80350 context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop,
80351 xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap);
80352 }
80353 }
80354 }
80355
80356 rcount = Math.round(rcount / pixcount);
80357 gcount = Math.round(gcount / pixcount);
80358 bcount = Math.round(bcount / pixcount);
80359 var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')');
80360
80361 gd._hmpixcount = (gd._hmpixcount||0) + pixcount;
80362 gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance();
80363
80364 var image3 = plotGroup.selectAll('image')
80365 .data(cd);
80366
80367 image3.enter().append('svg:image').attr({
80368 xmlns: xmlnsNamespaces.svg,
80369 preserveAspectRatio: 'none'
80370 });
80371
80372 image3.attr({
80373 height: imageHeight,
80374 width: imageWidth,
80375 x: left,
80376 y: top,
80377 'xlink:href': canvas.toDataURL('image/png')
80378 });
80379 });
80380};
80381
80382// get interpolated bin value. Returns {bin0:closest bin, frac:fractional dist to next, bin1:next bin}
80383function findInterp(pixel, pixArray) {
80384 var maxBin = pixArray.length - 2;
80385 var bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxBin);
80386 var pix0 = pixArray[bin];
80387 var pix1 = pixArray[bin + 1];
80388 var interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxBin);
80389 var bin0 = Math.round(interp);
80390 var frac = Math.abs(interp - bin0);
80391
80392 if(!interp || interp === maxBin || !frac) {
80393 return {
80394 bin0: bin0,
80395 bin1: bin0,
80396 frac: 0
80397 };
80398 }
80399 return {
80400 bin0: bin0,
80401 frac: frac,
80402 bin1: Math.round(bin0 + frac / (interp - bin0))
80403 };
80404}
80405
80406function findInterpFromCenters(pixel, centerPixArray) {
80407 var maxBin = centerPixArray.length - 1;
80408 var bin = Lib.constrain(Lib.findBin(pixel, centerPixArray), 0, maxBin);
80409 var pix0 = centerPixArray[bin];
80410 var pix1 = centerPixArray[bin + 1];
80411 var frac = ((pixel - pix0) / (pix1 - pix0)) || 0;
80412 if(frac <= 0) {
80413 return {
80414 bin0: bin,
80415 bin1: bin,
80416 frac: 0
80417 };
80418 }
80419 if(frac < 0.5) {
80420 return {
80421 bin0: bin,
80422 bin1: bin + 1,
80423 frac: frac
80424 };
80425 }
80426 return {
80427 bin0: bin + 1,
80428 bin1: bin,
80429 frac: 1 - frac
80430 };
80431}
80432
80433function putColor(pixels, pxIndex, c) {
80434 pixels[pxIndex] = c[0];
80435 pixels[pxIndex + 1] = c[1];
80436 pixels[pxIndex + 2] = c[2];
80437 pixels[pxIndex + 3] = Math.round(c[3] * 255);
80438}
80439
80440},{"../../components/colorscale":64,"../../constants/xmlns_namespaces":159,"../../lib":178,"../../registry":269,"d3":16,"tinycolor2":35}],342:[function(_dereq_,module,exports){
80441/**
80442* Copyright 2012-2020, Plotly, Inc.
80443* All rights reserved.
80444*
80445* This source code is licensed under the MIT license found in the
80446* LICENSE file in the root directory of this source tree.
80447*/
80448
80449
80450'use strict';
80451
80452var d3 = _dereq_('d3');
80453
80454module.exports = function style(gd) {
80455 d3.select(gd).selectAll('.hm image')
80456 .style('opacity', function(d) {
80457 return d.trace.opacity;
80458 });
80459};
80460
80461},{"d3":16}],343:[function(_dereq_,module,exports){
80462/**
80463* Copyright 2012-2020, Plotly, Inc.
80464* All rights reserved.
80465*
80466* This source code is licensed under the MIT license found in the
80467* LICENSE file in the root directory of this source tree.
80468*/
80469
80470
80471'use strict';
80472
80473module.exports = function handleStyleDefaults(traceIn, traceOut, coerce) {
80474 var zsmooth = coerce('zsmooth');
80475 if(zsmooth === false) {
80476 // ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect.
80477 coerce('xgap');
80478 coerce('ygap');
80479 }
80480
80481 coerce('zhoverformat');
80482};
80483
80484},{}],344:[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'use strict';
80494
80495var isNumeric = _dereq_('fast-isnumeric');
80496var Lib = _dereq_('../../lib');
80497
80498var Registry = _dereq_('../../registry');
80499
80500module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, xName, yName) {
80501 var z = coerce('z');
80502 xName = xName || 'x';
80503 yName = yName || 'y';
80504 var x, y;
80505
80506 if(z === undefined || !z.length) return 0;
80507
80508 if(Lib.isArray1D(traceIn.z)) {
80509 x = coerce(xName);
80510 y = coerce(yName);
80511
80512 var xlen = Lib.minRowLength(x);
80513 var ylen = Lib.minRowLength(y);
80514
80515 // column z must be accompanied by xName and yName arrays
80516 if(xlen === 0 || ylen === 0) return 0;
80517
80518 traceOut._length = Math.min(xlen, ylen, z.length);
80519 } else {
80520 x = coordDefaults(xName, coerce);
80521 y = coordDefaults(yName, coerce);
80522
80523 // TODO put z validation elsewhere
80524 if(!isValidZ(z)) return 0;
80525
80526 coerce('transpose');
80527
80528 traceOut._length = null;
80529 }
80530
80531 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
80532 handleCalendarDefaults(traceIn, traceOut, [xName, yName], layout);
80533
80534 return true;
80535};
80536
80537function coordDefaults(coordStr, coerce) {
80538 var coord = coerce(coordStr);
80539 var coordType = coord ? coerce(coordStr + 'type', 'array') : 'scaled';
80540
80541 if(coordType === 'scaled') {
80542 coerce(coordStr + '0');
80543 coerce('d' + coordStr);
80544 }
80545
80546 return coord;
80547}
80548
80549function isValidZ(z) {
80550 var allRowsAreArrays = true;
80551 var oneRowIsFilled = false;
80552 var hasOneNumber = false;
80553 var zi;
80554
80555 /*
80556 * Without this step:
80557 *
80558 * hasOneNumber = false breaks contour but not heatmap
80559 * allRowsAreArrays = false breaks contour but not heatmap
80560 * oneRowIsFilled = false breaks both
80561 */
80562
80563 for(var i = 0; i < z.length; i++) {
80564 zi = z[i];
80565 if(!Lib.isArrayOrTypedArray(zi)) {
80566 allRowsAreArrays = false;
80567 break;
80568 }
80569 if(zi.length > 0) oneRowIsFilled = true;
80570 for(var j = 0; j < zi.length; j++) {
80571 if(isNumeric(zi[j])) {
80572 hasOneNumber = true;
80573 break;
80574 }
80575 }
80576 }
80577
80578 return (allRowsAreArrays && oneRowIsFilled && hasOneNumber);
80579}
80580
80581},{"../../lib":178,"../../registry":269,"fast-isnumeric":18}],345:[function(_dereq_,module,exports){
80582/**
80583* Copyright 2012-2020, Plotly, Inc.
80584* All rights reserved.
80585*
80586* This source code is licensed under the MIT license found in the
80587* LICENSE file in the root directory of this source tree.
80588*/
80589
80590'use strict';
80591
80592var barAttrs = _dereq_('../bar/attributes');
80593var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
80594var makeBinAttrs = _dereq_('./bin_attributes');
80595var constants = _dereq_('./constants');
80596var extendFlat = _dereq_('../../lib/extend').extendFlat;
80597
80598module.exports = {
80599 x: {
80600 valType: 'data_array',
80601 editType: 'calc+clearAxisTypes',
80602
80603 },
80604 y: {
80605 valType: 'data_array',
80606 editType: 'calc+clearAxisTypes',
80607
80608 },
80609
80610 text: extendFlat({}, barAttrs.text, {
80611
80612 }),
80613 hovertext: extendFlat({}, barAttrs.hovertext, {
80614
80615 }),
80616 orientation: barAttrs.orientation,
80617
80618 histfunc: {
80619 valType: 'enumerated',
80620 values: ['count', 'sum', 'avg', 'min', 'max'],
80621
80622 dflt: 'count',
80623 editType: 'calc',
80624
80625 },
80626 histnorm: {
80627 valType: 'enumerated',
80628 values: ['', 'percent', 'probability', 'density', 'probability density'],
80629 dflt: '',
80630
80631 editType: 'calc',
80632
80633 },
80634
80635 cumulative: {
80636 enabled: {
80637 valType: 'boolean',
80638 dflt: false,
80639
80640 editType: 'calc',
80641
80642 },
80643
80644 direction: {
80645 valType: 'enumerated',
80646 values: ['increasing', 'decreasing'],
80647 dflt: 'increasing',
80648
80649 editType: 'calc',
80650
80651 },
80652
80653 currentbin: {
80654 valType: 'enumerated',
80655 values: ['include', 'exclude', 'half'],
80656 dflt: 'include',
80657
80658 editType: 'calc',
80659
80660 },
80661 editType: 'calc'
80662 },
80663 nbinsx: {
80664 valType: 'integer',
80665 min: 0,
80666 dflt: 0,
80667
80668 editType: 'calc',
80669
80670 },
80671 xbins: makeBinAttrs('x', true),
80672
80673 nbinsy: {
80674 valType: 'integer',
80675 min: 0,
80676 dflt: 0,
80677
80678 editType: 'calc',
80679
80680 },
80681 ybins: makeBinAttrs('y', true),
80682 autobinx: {
80683 valType: 'boolean',
80684 dflt: null,
80685
80686 editType: 'calc',
80687
80688 },
80689 autobiny: {
80690 valType: 'boolean',
80691 dflt: null,
80692
80693 editType: 'calc',
80694
80695 },
80696
80697 bingroup: {
80698 valType: 'string',
80699
80700 dflt: '',
80701 editType: 'calc',
80702
80703 },
80704
80705 hovertemplate: hovertemplateAttrs({}, {
80706 keys: constants.eventDataKeys
80707 }),
80708
80709 marker: barAttrs.marker,
80710
80711 offsetgroup: barAttrs.offsetgroup,
80712 alignmentgroup: barAttrs.alignmentgroup,
80713
80714 selected: barAttrs.selected,
80715 unselected: barAttrs.unselected,
80716
80717 _deprecated: {
80718 bardir: barAttrs._deprecated.bardir
80719 }
80720};
80721
80722},{"../../lib/extend":173,"../../plots/template_attributes":264,"../bar/attributes":279,"./bin_attributes":347,"./constants":351}],346:[function(_dereq_,module,exports){
80723/**
80724* Copyright 2012-2020, Plotly, Inc.
80725* All rights reserved.
80726*
80727* This source code is licensed under the MIT license found in the
80728* LICENSE file in the root directory of this source tree.
80729*/
80730
80731
80732'use strict';
80733
80734
80735module.exports = function doAvg(size, counts) {
80736 var nMax = size.length;
80737 var total = 0;
80738 for(var i = 0; i < nMax; i++) {
80739 if(counts[i]) {
80740 size[i] /= counts[i];
80741 total += size[i];
80742 } else size[i] = null;
80743 }
80744 return total;
80745};
80746
80747},{}],347:[function(_dereq_,module,exports){
80748/**
80749* Copyright 2012-2020, Plotly, Inc.
80750* All rights reserved.
80751*
80752* This source code is licensed under the MIT license found in the
80753* LICENSE file in the root directory of this source tree.
80754*/
80755
80756'use strict';
80757
80758module.exports = function makeBinAttrs(axLetter, match) {
80759 return {
80760 start: {
80761 valType: 'any', // for date axes
80762
80763 editType: 'calc',
80764
80765 },
80766 end: {
80767 valType: 'any', // for date axes
80768
80769 editType: 'calc',
80770
80771 },
80772 size: {
80773 valType: 'any', // for date axes
80774
80775 editType: 'calc',
80776
80777 },
80778 editType: 'calc'
80779 };
80780};
80781
80782},{}],348:[function(_dereq_,module,exports){
80783/**
80784* Copyright 2012-2020, Plotly, Inc.
80785* All rights reserved.
80786*
80787* This source code is licensed under the MIT license found in the
80788* LICENSE file in the root directory of this source tree.
80789*/
80790
80791
80792'use strict';
80793
80794var isNumeric = _dereq_('fast-isnumeric');
80795
80796
80797module.exports = {
80798 count: function(n, i, size) {
80799 size[n]++;
80800 return 1;
80801 },
80802
80803 sum: function(n, i, size, counterData) {
80804 var v = counterData[i];
80805 if(isNumeric(v)) {
80806 v = Number(v);
80807 size[n] += v;
80808 return v;
80809 }
80810 return 0;
80811 },
80812
80813 avg: function(n, i, size, counterData, counts) {
80814 var v = counterData[i];
80815 if(isNumeric(v)) {
80816 v = Number(v);
80817 size[n] += v;
80818 counts[n]++;
80819 }
80820 return 0;
80821 },
80822
80823 min: function(n, i, size, counterData) {
80824 var v = counterData[i];
80825 if(isNumeric(v)) {
80826 v = Number(v);
80827 if(!isNumeric(size[n])) {
80828 size[n] = v;
80829 return v;
80830 } else if(size[n] > v) {
80831 var delta = v - size[n];
80832 size[n] = v;
80833 return delta;
80834 }
80835 }
80836 return 0;
80837 },
80838
80839 max: function(n, i, size, counterData) {
80840 var v = counterData[i];
80841 if(isNumeric(v)) {
80842 v = Number(v);
80843 if(!isNumeric(size[n])) {
80844 size[n] = v;
80845 return v;
80846 } else if(size[n] < v) {
80847 var delta = v - size[n];
80848 size[n] = v;
80849 return delta;
80850 }
80851 }
80852 return 0;
80853 }
80854};
80855
80856},{"fast-isnumeric":18}],349:[function(_dereq_,module,exports){
80857/**
80858* Copyright 2012-2020, Plotly, Inc.
80859* All rights reserved.
80860*
80861* This source code is licensed under the MIT license found in the
80862* LICENSE file in the root directory of this source tree.
80863*/
80864
80865
80866'use strict';
80867
80868var numConstants = _dereq_('../../constants/numerical');
80869var oneYear = numConstants.ONEAVGYEAR;
80870var oneMonth = numConstants.ONEAVGMONTH;
80871var oneDay = numConstants.ONEDAY;
80872var oneHour = numConstants.ONEHOUR;
80873var oneMin = numConstants.ONEMIN;
80874var oneSec = numConstants.ONESEC;
80875var tickIncrement = _dereq_('../../plots/cartesian/axes').tickIncrement;
80876
80877
80878/*
80879 * make a function that will find rounded bin edges
80880 * @param {number} leftGap: how far from the left edge of any bin is the closest data value?
80881 * @param {number} rightGap: how far from the right edge of any bin is the closest data value?
80882 * @param {Array[number]} binEdges: the actual edge values used in binning
80883 * @param {object} pa: the position axis
80884 * @param {string} calendar: the data calendar
80885 *
80886 * @return {function(v, isRightEdge)}:
80887 * find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v`
80888 */
80889module.exports = function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) {
80890 // the rounding digit is the largest digit that changes in *all* of 4 regions:
80891 // - inside the rightGap before binEdges[0] (shifted 10% to the left)
80892 // - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end)
80893 // - same for binEdges[1]
80894 var dv0 = -1.1 * rightGap;
80895 var dv1 = -0.1 * rightGap;
80896 var dv2 = leftGap - dv1;
80897 var edge0 = binEdges[0];
80898 var edge1 = binEdges[1];
80899 var leftDigit = Math.min(
80900 biggestDigitChanged(edge0 + dv1, edge0 + dv2, pa, calendar),
80901 biggestDigitChanged(edge1 + dv1, edge1 + dv2, pa, calendar)
80902 );
80903 var rightDigit = Math.min(
80904 biggestDigitChanged(edge0 + dv0, edge0 + dv1, pa, calendar),
80905 biggestDigitChanged(edge1 + dv0, edge1 + dv1, pa, calendar)
80906 );
80907
80908 // normally we try to make the label for the right edge different from
80909 // the left edge label, so it's unambiguous which bin gets data on the edge.
80910 // but if this results in more than 3 extra digits (or for dates, more than
80911 // 2 fields ie hr&min or min&sec, which is 3600x), it'll be more clutter than
80912 // useful so keep the label cleaner instead
80913 var digit, disambiguateEdges;
80914 if(leftDigit > rightDigit && rightDigit < Math.abs(edge1 - edge0) / 4000) {
80915 digit = leftDigit;
80916 disambiguateEdges = false;
80917 } else {
80918 digit = Math.min(leftDigit, rightDigit);
80919 disambiguateEdges = true;
80920 }
80921
80922 if(pa.type === 'date' && digit > oneDay) {
80923 var dashExclude = (digit === oneYear) ? 1 : 6;
80924 var increment = (digit === oneYear) ? 'M12' : 'M1';
80925
80926 return function(v, isRightEdge) {
80927 var dateStr = pa.c2d(v, oneYear, calendar);
80928 var dashPos = dateStr.indexOf('-', dashExclude);
80929 if(dashPos > 0) dateStr = dateStr.substr(0, dashPos);
80930 var roundedV = pa.d2c(dateStr, 0, calendar);
80931
80932 if(roundedV < v) {
80933 var nextV = tickIncrement(roundedV, increment, false, calendar);
80934 if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV;
80935 }
80936
80937 if(isRightEdge && disambiguateEdges) {
80938 return tickIncrement(roundedV, increment, true, calendar);
80939 }
80940
80941 return roundedV;
80942 };
80943 }
80944
80945 return function(v, isRightEdge) {
80946 var roundedV = digit * Math.round(v / digit);
80947 // if we rounded down and we could round up and still be < leftGap
80948 // (or what leftGap values round to), do that
80949 if(roundedV + (digit / 10) < v && roundedV + (digit * 0.9) < v + leftGap) {
80950 roundedV += digit;
80951 }
80952 // finally for the right edge back off one digit - but only if we can do that
80953 // and not clip off any data that's potentially in the bin
80954 if(isRightEdge && disambiguateEdges) {
80955 roundedV -= digit;
80956 }
80957 return roundedV;
80958 };
80959};
80960
80961/*
80962 * Find the largest digit that changes within a (calcdata) region [v1, v2]
80963 * if dates, "digit" means date/time part when it's bigger than a second
80964 * returns the unit value to round to this digit, eg 0.01 to round to hundredths, or
80965 * 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding,
80966 * so that Math.min will work, rather than 'M1' and 'M12'
80967 */
80968function biggestDigitChanged(v1, v2, pa, calendar) {
80969 // are we crossing zero? can't say anything.
80970 // in principle this doesn't apply to dates but turns out this doesn't matter.
80971 if(v1 * v2 <= 0) return Infinity;
80972
80973 var dv = Math.abs(v2 - v1);
80974 var isDate = pa.type === 'date';
80975 var digit = biggestGuaranteedDigitChanged(dv, isDate);
80976 // see if a larger digit also changed
80977 for(var i = 0; i < 10; i++) {
80978 // numbers: next digit needs to be >10x but <100x then gets rounded down.
80979 // dates: next digit can be as much as 60x (then rounded down)
80980 var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate);
80981 // if we get to years, the chain stops
80982 if(digit === nextDigit) break;
80983 if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit;
80984 else break;
80985 }
80986 return digit;
80987}
80988
80989/*
80990 * Find the largest digit that *definitely* changes in a region [v, v + dv] for any v
80991 * for nonuniform date regions (months/years) pick the largest
80992 */
80993function biggestGuaranteedDigitChanged(dv, isDate) {
80994 if(isDate && dv > oneSec) {
80995 // this is supposed to be the biggest *guaranteed* change
80996 // so compare to the longest month and year across any calendar,
80997 // and we'll iterate back up later
80998 // note: does not support rounding larger than one year. We could add
80999 // that if anyone wants it, but seems unusual and not strictly necessary.
81000 if(dv > oneDay) {
81001 if(dv > oneYear * 1.1) return oneYear;
81002 if(dv > oneMonth * 1.1) return oneMonth;
81003 return oneDay;
81004 }
81005
81006 if(dv > oneHour) return oneHour;
81007 if(dv > oneMin) return oneMin;
81008 return oneSec;
81009 }
81010 return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10));
81011}
81012
81013function didDigitChange(digit, v1, v2, isDate, pa, calendar) {
81014 if(isDate && digit > oneDay) {
81015 var dateParts1 = dateParts(v1, pa, calendar);
81016 var dateParts2 = dateParts(v2, pa, calendar);
81017 var parti = (digit === oneYear) ? 0 : 1;
81018 return dateParts1[parti] !== dateParts2[parti];
81019 }
81020 return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1;
81021}
81022
81023function dateParts(v, pa, calendar) {
81024 var parts = pa.c2d(v, oneYear, calendar).split('-');
81025 if(parts[0] === '') {
81026 parts.unshift();
81027 parts[0] = '-' + parts[0];
81028 }
81029 return parts;
81030}
81031
81032},{"../../constants/numerical":158,"../../plots/cartesian/axes":222}],350:[function(_dereq_,module,exports){
81033/**
81034* Copyright 2012-2020, Plotly, Inc.
81035* All rights reserved.
81036*
81037* This source code is licensed under the MIT license found in the
81038* LICENSE file in the root directory of this source tree.
81039*/
81040
81041'use strict';
81042
81043var isNumeric = _dereq_('fast-isnumeric');
81044
81045var Lib = _dereq_('../../lib');
81046var Registry = _dereq_('../../registry');
81047var Axes = _dereq_('../../plots/cartesian/axes');
81048
81049var arraysToCalcdata = _dereq_('../bar/arrays_to_calcdata');
81050var binFunctions = _dereq_('./bin_functions');
81051var normFunctions = _dereq_('./norm_functions');
81052var doAvg = _dereq_('./average');
81053var getBinSpanLabelRound = _dereq_('./bin_label_vals');
81054
81055function calc(gd, trace) {
81056 var pos = [];
81057 var size = [];
81058 var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis);
81059 var mainData = trace.orientation === 'h' ? 'y' : 'x';
81060 var counterData = {x: 'y', y: 'x'}[mainData];
81061 var calendar = trace[mainData + 'calendar'];
81062 var cumulativeSpec = trace.cumulative;
81063 var i;
81064
81065 var binsAndPos = calcAllAutoBins(gd, trace, pa, mainData);
81066 var binSpec = binsAndPos[0];
81067 var pos0 = binsAndPos[1];
81068
81069 var nonuniformBins = typeof binSpec.size === 'string';
81070 var binEdges = [];
81071 var bins = nonuniformBins ? binEdges : binSpec;
81072 // make the empty bin array
81073 var inc = [];
81074 var counts = [];
81075 var inputPoints = [];
81076 var total = 0;
81077 var norm = trace.histnorm;
81078 var func = trace.histfunc;
81079 var densityNorm = norm.indexOf('density') !== -1;
81080 var i2, binEnd, n;
81081
81082 if(cumulativeSpec.enabled && densityNorm) {
81083 // we treat "cumulative" like it means "integral" if you use a density norm,
81084 // which in the end means it's the same as without "density"
81085 norm = norm.replace(/ ?density$/, '');
81086 densityNorm = false;
81087 }
81088
81089 var extremeFunc = func === 'max' || func === 'min';
81090 var sizeInit = extremeFunc ? null : 0;
81091 var binFunc = binFunctions.count;
81092 var normFunc = normFunctions[norm];
81093 var isAvg = false;
81094 var pr2c = function(v) { return pa.r2c(v, 0, calendar); };
81095 var rawCounterData;
81096
81097 if(Lib.isArrayOrTypedArray(trace[counterData]) && func !== 'count') {
81098 rawCounterData = trace[counterData];
81099 isAvg = func === 'avg';
81100 binFunc = binFunctions[func];
81101 }
81102
81103 // create the bins (and any extra arrays needed)
81104 // assume more than 1e6 bins is an error, so we don't crash the browser
81105 i = pr2c(binSpec.start);
81106
81107 // decrease end a little in case of rounding errors
81108 binEnd = pr2c(binSpec.end) + (i - Axes.tickIncrement(i, binSpec.size, false, calendar)) / 1e6;
81109
81110 while(i < binEnd && pos.length < 1e6) {
81111 i2 = Axes.tickIncrement(i, binSpec.size, false, calendar);
81112 pos.push((i + i2) / 2);
81113 size.push(sizeInit);
81114 inputPoints.push([]);
81115 // nonuniform bins (like months) we need to search,
81116 // rather than straight calculate the bin we're in
81117 binEdges.push(i);
81118 // nonuniform bins also need nonuniform normalization factors
81119 if(densityNorm) inc.push(1 / (i2 - i));
81120 if(isAvg) counts.push(0);
81121 // break to avoid infinite loops
81122 if(i2 <= i) break;
81123 i = i2;
81124 }
81125 binEdges.push(i);
81126
81127 // for date axes we need bin bounds to be calcdata. For nonuniform bins
81128 // we already have this, but uniform with start/end/size they're still strings.
81129 if(!nonuniformBins && pa.type === 'date') {
81130 bins = {
81131 start: pr2c(bins.start),
81132 end: pr2c(bins.end),
81133 size: bins.size
81134 };
81135 }
81136
81137 // stash left and right gaps by group
81138 if(!gd._fullLayout._roundFnOpts) gd._fullLayout._roundFnOpts = {};
81139 var groupName = trace['_' + mainData + 'bingroup'];
81140 var roundFnOpts = {leftGap: Infinity, rightGap: Infinity};
81141 if(groupName) {
81142 if(!gd._fullLayout._roundFnOpts[groupName]) gd._fullLayout._roundFnOpts[groupName] = roundFnOpts;
81143 roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
81144 }
81145
81146 // bin the data
81147 // and make histogram-specific pt-number-to-cd-index map object
81148 var nMax = size.length;
81149 var uniqueValsPerBin = true;
81150 var leftGap = roundFnOpts.leftGap;
81151 var rightGap = roundFnOpts.rightGap;
81152 var ptNumber2cdIndex = {};
81153 for(i = 0; i < pos0.length; i++) {
81154 var posi = pos0[i];
81155 n = Lib.findBin(posi, bins);
81156 if(n >= 0 && n < nMax) {
81157 total += binFunc(n, i, size, rawCounterData, counts);
81158 if(uniqueValsPerBin && inputPoints[n].length && posi !== pos0[inputPoints[n][0]]) {
81159 uniqueValsPerBin = false;
81160 }
81161 inputPoints[n].push(i);
81162 ptNumber2cdIndex[i] = n;
81163
81164 leftGap = Math.min(leftGap, posi - binEdges[n]);
81165 rightGap = Math.min(rightGap, binEdges[n + 1] - posi);
81166 }
81167 }
81168 roundFnOpts.leftGap = leftGap;
81169 roundFnOpts.rightGap = rightGap;
81170
81171 var roundFn;
81172 if(!uniqueValsPerBin) {
81173 roundFn = function(v, isRightEdge) {
81174 return function() {
81175 var roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
81176 return getBinSpanLabelRound(
81177 roundFnOpts.leftGap,
81178 roundFnOpts.rightGap,
81179 binEdges, pa, calendar
81180 )(v, isRightEdge);
81181 };
81182 };
81183 }
81184
81185 // average and/or normalize the data, if needed
81186 if(isAvg) total = doAvg(size, counts);
81187 if(normFunc) normFunc(size, total, inc);
81188
81189 // after all normalization etc, now we can accumulate if desired
81190 if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);
81191
81192 var seriesLen = Math.min(pos.length, size.length);
81193 var cd = [];
81194 var firstNonzero = 0;
81195 var lastNonzero = seriesLen - 1;
81196
81197 // look for empty bins at the ends to remove, so autoscale omits them
81198 for(i = 0; i < seriesLen; i++) {
81199 if(size[i]) {
81200 firstNonzero = i;
81201 break;
81202 }
81203 }
81204 for(i = seriesLen - 1; i >= firstNonzero; i--) {
81205 if(size[i]) {
81206 lastNonzero = i;
81207 break;
81208 }
81209 }
81210
81211 // create the "calculated data" to plot
81212 for(i = firstNonzero; i <= lastNonzero; i++) {
81213 if((isNumeric(pos[i]) && isNumeric(size[i]))) {
81214 var cdi = {
81215 p: pos[i],
81216 s: size[i],
81217 b: 0
81218 };
81219
81220 // setup hover and event data fields,
81221 // N.B. pts and "hover" positions ph0/ph1 don't seem to make much sense
81222 // for cumulative distributions
81223 if(!cumulativeSpec.enabled) {
81224 cdi.pts = inputPoints[i];
81225 if(uniqueValsPerBin) {
81226 cdi.ph0 = cdi.ph1 = (inputPoints[i].length) ? pos0[inputPoints[i][0]] : pos[i];
81227 } else {
81228 // Defer evaluation of ph(0|1) in crossTraceCalc
81229 trace._computePh = true;
81230 cdi.ph0 = roundFn(binEdges[i]);
81231 cdi.ph1 = roundFn(binEdges[i + 1], true);
81232 }
81233 }
81234 cd.push(cdi);
81235 }
81236 }
81237
81238 if(cd.length === 1) {
81239 // when we collapse to a single bin, calcdata no longer describes bin size
81240 // so we need to explicitly specify it
81241 cd[0].width1 = Axes.tickIncrement(cd[0].p, binSpec.size, false, calendar) - cd[0].p;
81242 }
81243
81244 arraysToCalcdata(cd, trace);
81245
81246 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
81247 Lib.tagSelected(cd, trace, ptNumber2cdIndex);
81248 }
81249
81250 return cd;
81251}
81252
81253/*
81254 * calcAllAutoBins: we want all histograms inside the same bingroup
81255 * (see logic in Histogram.crossTraceDefaults) to share bin specs
81256 *
81257 * If the user has explicitly specified differing
81258 * bin specs, there's nothing we can do, but if possible we will try to use the
81259 * smallest bins of any of the auto values for all histograms inside the same
81260 * bingroup.
81261 */
81262function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
81263 var binAttr = mainData + 'bins';
81264 var fullLayout = gd._fullLayout;
81265 var groupName = trace['_' + mainData + 'bingroup'];
81266 var binOpts = fullLayout._histogramBinOpts[groupName];
81267 var isOverlay = fullLayout.barmode === 'overlay';
81268 var i, traces, tracei, calendar, pos0, autoVals, cumulativeSpec;
81269
81270 var r2c = function(v) { return pa.r2c(v, 0, calendar); };
81271 var c2r = function(v) { return pa.c2r(v, 0, calendar); };
81272
81273 var cleanBound = pa.type === 'date' ?
81274 function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } :
81275 function(v) { return isNumeric(v) ? Number(v) : null; };
81276
81277 function setBound(attr, bins, newBins) {
81278 if(bins[attr + 'Found']) {
81279 bins[attr] = cleanBound(bins[attr]);
81280 if(bins[attr] === null) bins[attr] = newBins[attr];
81281 } else {
81282 autoVals[attr] = bins[attr] = newBins[attr];
81283 Lib.nestedProperty(traces[0], binAttr + '.' + attr).set(newBins[attr]);
81284 }
81285 }
81286
81287 // all but the first trace in this group has already been marked finished
81288 // clear this flag, so next time we run calc we will run autobin again
81289 if(trace['_' + mainData + 'autoBinFinished']) {
81290 delete trace['_' + mainData + 'autoBinFinished'];
81291 } else {
81292 traces = binOpts.traces;
81293 var allPos = [];
81294
81295 // Note: we're including `legendonly` traces here for autobin purposes,
81296 // so that showing & hiding from the legend won't affect bins.
81297 // But this complicates things a bit since those traces don't `calc`,
81298 // hence `isFirstVisible`.
81299 var isFirstVisible = true;
81300 var has2dMap = false;
81301 var hasHist2dContour = false;
81302 for(i = 0; i < traces.length; i++) {
81303 tracei = traces[i];
81304
81305 if(tracei.visible) {
81306 var mainDatai = binOpts.dirs[i];
81307 pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai);
81308
81309 allPos = Lib.concat(allPos, pos0);
81310 delete tracei['_' + mainData + 'autoBinFinished'];
81311
81312 if(trace.visible === true) {
81313 if(isFirstVisible) {
81314 isFirstVisible = false;
81315 } else {
81316 delete tracei._autoBin;
81317 tracei['_' + mainData + 'autoBinFinished'] = 1;
81318 }
81319 if(Registry.traceIs(tracei, '2dMap')) {
81320 has2dMap = true;
81321 }
81322 if(tracei.type === 'histogram2dcontour') {
81323 hasHist2dContour = true;
81324 }
81325 }
81326 }
81327 }
81328
81329 calendar = traces[0][mainData + 'calendar'];
81330 var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size);
81331
81332 var autoBin = traces[0]._autoBin = {};
81333 autoVals = autoBin[binOpts.dirs[0]] = {};
81334
81335 if(hasHist2dContour) {
81336 // the "true" 2nd argument reverses the tick direction (which we can't
81337 // just do with a minus sign because of month bins)
81338 if(!binOpts.size) {
81339 newBinSpec.start = c2r(Axes.tickIncrement(
81340 r2c(newBinSpec.start), newBinSpec.size, true, calendar));
81341 }
81342 if(binOpts.end === undefined) {
81343 newBinSpec.end = c2r(Axes.tickIncrement(
81344 r2c(newBinSpec.end), newBinSpec.size, false, calendar));
81345 }
81346 }
81347
81348 // Edge case: single-valued histogram overlaying others
81349 // Use them all together to calculate the bin size for the single-valued one
81350 if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 &&
81351 pa.type !== 'category' && pa.type !== 'multicategory') {
81352 // Several single-valued histograms! Stop infinite recursion,
81353 // just return an extra flag that tells handleSingleValueOverlays
81354 // to sort out this trace too
81355 if(_overlayEdgeCase) return [newBinSpec, pos0, true];
81356
81357 newBinSpec = handleSingleValueOverlays(gd, trace, pa, mainData, binAttr);
81358 }
81359
81360 // adjust for CDF edge cases
81361 cumulativeSpec = tracei.cumulative || {};
81362 if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
81363 if(cumulativeSpec.direction === 'decreasing') {
81364 newBinSpec.start = c2r(Axes.tickIncrement(
81365 r2c(newBinSpec.start), newBinSpec.size, true, calendar));
81366 } else {
81367 newBinSpec.end = c2r(Axes.tickIncrement(
81368 r2c(newBinSpec.end), newBinSpec.size, false, calendar));
81369 }
81370 }
81371
81372 binOpts.size = newBinSpec.size;
81373 if(!binOpts.sizeFound) {
81374 autoVals.size = newBinSpec.size;
81375 Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size);
81376 }
81377
81378 setBound('start', binOpts, newBinSpec);
81379 setBound('end', binOpts, newBinSpec);
81380 }
81381
81382 pos0 = trace['_' + mainData + 'pos0'];
81383 delete trace['_' + mainData + 'pos0'];
81384
81385 // Each trace can specify its own start/end, or if omitted
81386 // we ensure they're beyond the bounds of this trace's data,
81387 // and we need to make sure start is aligned with the main start
81388 var traceInputBins = trace._input[binAttr] || {};
81389 var traceBinOptsCalc = Lib.extendFlat({}, binOpts);
81390 var mainStart = binOpts.start;
81391 var startIn = pa.r2l(traceInputBins.start);
81392 var hasStart = startIn !== undefined;
81393 if((binOpts.startFound || hasStart) && startIn !== pa.r2l(mainStart)) {
81394 // We have an explicit start to reconcile across traces
81395 // if this trace has an explicit start, shift it down to a bin edge
81396 // if another trace had an explicit start, shift it down to a
81397 // bin edge past our data
81398 var traceStart = hasStart ?
81399 startIn :
81400 Lib.aggNums(Math.min, null, pos0);
81401
81402 var dummyAx = {
81403 type: (pa.type === 'category' || pa.type === 'multicategory') ? 'linear' : pa.type,
81404 r2l: pa.r2l,
81405 dtick: binOpts.size,
81406 tick0: mainStart,
81407 calendar: calendar,
81408 range: ([traceStart, Axes.tickIncrement(traceStart, binOpts.size, false, calendar)]).map(pa.l2r)
81409 };
81410 var newStart = Axes.tickFirst(dummyAx);
81411 if(newStart > pa.r2l(traceStart)) {
81412 newStart = Axes.tickIncrement(newStart, binOpts.size, true, calendar);
81413 }
81414 traceBinOptsCalc.start = pa.l2r(newStart);
81415 if(!hasStart) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.start);
81416 }
81417
81418 var mainEnd = binOpts.end;
81419 var endIn = pa.r2l(traceInputBins.end);
81420 var hasEnd = endIn !== undefined;
81421 if((binOpts.endFound || hasEnd) && endIn !== pa.r2l(mainEnd)) {
81422 // Reconciling an explicit end is easier, as it doesn't need to
81423 // match bin edges
81424 var traceEnd = hasEnd ?
81425 endIn :
81426 Lib.aggNums(Math.max, null, pos0);
81427
81428 traceBinOptsCalc.end = pa.l2r(traceEnd);
81429 if(!hasEnd) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.end);
81430 }
81431
81432 // Backward compatibility for one-time autobinning.
81433 // autobin: true is handled in cleanData, but autobin: false
81434 // needs to be here where we have determined the values.
81435 var autoBinAttr = 'autobin' + mainData;
81436 if(trace._input[autoBinAttr] === false) {
81437 trace._input[binAttr] = Lib.extendFlat({}, trace[binAttr] || {});
81438 delete trace._input[autoBinAttr];
81439 delete trace[autoBinAttr];
81440 }
81441
81442 return [traceBinOptsCalc, pos0];
81443}
81444
81445/*
81446 * Adjust single-value histograms in overlay mode to make as good a
81447 * guess as we can at autobin values the user would like.
81448 *
81449 * Returns the binSpec for the trace that sparked all this
81450 */
81451function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
81452 var fullLayout = gd._fullLayout;
81453 var overlaidTraceGroup = getConnectedHistograms(gd, trace);
81454 var pastThisTrace = false;
81455 var minSize = Infinity;
81456 var singleValuedTraces = [trace];
81457 var i, tracei, binOpts;
81458
81459 // first collect all the:
81460 // - min bin size from all multi-valued traces
81461 // - single-valued traces
81462 for(i = 0; i < overlaidTraceGroup.length; i++) {
81463 tracei = overlaidTraceGroup[i];
81464
81465 if(tracei === trace) {
81466 pastThisTrace = true;
81467 } else if(!pastThisTrace) {
81468 // This trace has already had its autobins calculated, so either:
81469 // - it is part of a bingroup
81470 // - it is NOT a single-valued trace
81471 binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
81472 minSize = Math.min(minSize, binOpts.size || tracei[binAttr].size);
81473 } else {
81474 var resulti = calcAllAutoBins(gd, tracei, pa, mainData, true);
81475 var binSpeci = resulti[0];
81476 var isSingleValued = resulti[2];
81477
81478 // so we can use this result when we get to tracei in the normal
81479 // course of events, mark it as done and put _pos0 back
81480 tracei['_' + mainData + 'autoBinFinished'] = 1;
81481 tracei['_' + mainData + 'pos0'] = resulti[1];
81482
81483 if(isSingleValued) {
81484 singleValuedTraces.push(tracei);
81485 } else {
81486 minSize = Math.min(minSize, binSpeci.size);
81487 }
81488 }
81489 }
81490
81491 // find the real data values for each single-valued trace
81492 // hunt through pos0 for the first valid value
81493 var dataVals = new Array(singleValuedTraces.length);
81494 for(i = 0; i < singleValuedTraces.length; i++) {
81495 var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0'];
81496 for(var j = 0; j < pos0.length; j++) {
81497 if(pos0[j] !== undefined) {
81498 dataVals[i] = pos0[j];
81499 break;
81500 }
81501 }
81502 }
81503
81504 // are ALL traces are single-valued? use the min difference between
81505 // all of their values (which defaults to 1 if there's still only one)
81506 if(!isFinite(minSize)) {
81507 minSize = Lib.distinctVals(dataVals).minDiff;
81508 }
81509
81510 // now apply the min size we found to all single-valued traces
81511 for(i = 0; i < singleValuedTraces.length; i++) {
81512 tracei = singleValuedTraces[i];
81513 var calendar = tracei[mainData + 'calendar'];
81514
81515 var newBins = {
81516 start: pa.c2r(dataVals[i] - minSize / 2, 0, calendar),
81517 end: pa.c2r(dataVals[i] + minSize / 2, 0, calendar),
81518 size: minSize
81519 };
81520
81521 tracei._input[binAttr] = tracei[binAttr] = newBins;
81522
81523 binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
81524 if(binOpts) Lib.extendFlat(binOpts, newBins);
81525 }
81526
81527 return trace[binAttr];
81528}
81529
81530/*
81531 * Return an array of histograms that share axes and orientation.
81532 *
81533 * Only considers histograms. In principle we could include bars in a
81534 * similar way to how we do manually binned histograms, though this
81535 * would have tons of edge cases and value judgments to make.
81536 */
81537function getConnectedHistograms(gd, trace) {
81538 var xid = trace.xaxis;
81539 var yid = trace.yaxis;
81540 var orientation = trace.orientation;
81541
81542 var out = [];
81543 var fullData = gd._fullData;
81544 for(var i = 0; i < fullData.length; i++) {
81545 var tracei = fullData[i];
81546 if(tracei.type === 'histogram' &&
81547 tracei.visible === true &&
81548 tracei.orientation === orientation &&
81549 tracei.xaxis === xid && tracei.yaxis === yid
81550 ) {
81551 out.push(tracei);
81552 }
81553 }
81554
81555 return out;
81556}
81557
81558function cdf(size, direction, currentBin) {
81559 var i, vi, prevSum;
81560
81561 function firstHalfPoint(i) {
81562 prevSum = size[i];
81563 size[i] /= 2;
81564 }
81565
81566 function nextHalfPoint(i) {
81567 vi = size[i];
81568 size[i] = prevSum + vi / 2;
81569 prevSum += vi;
81570 }
81571
81572 if(currentBin === 'half') {
81573 if(direction === 'increasing') {
81574 firstHalfPoint(0);
81575 for(i = 1; i < size.length; i++) {
81576 nextHalfPoint(i);
81577 }
81578 } else {
81579 firstHalfPoint(size.length - 1);
81580 for(i = size.length - 2; i >= 0; i--) {
81581 nextHalfPoint(i);
81582 }
81583 }
81584 } else if(direction === 'increasing') {
81585 for(i = 1; i < size.length; i++) {
81586 size[i] += size[i - 1];
81587 }
81588
81589 // 'exclude' is identical to 'include' just shifted one bin over
81590 if(currentBin === 'exclude') {
81591 size.unshift(0);
81592 size.pop();
81593 }
81594 } else {
81595 for(i = size.length - 2; i >= 0; i--) {
81596 size[i] += size[i + 1];
81597 }
81598
81599 if(currentBin === 'exclude') {
81600 size.push(0);
81601 size.shift();
81602 }
81603 }
81604}
81605
81606module.exports = {
81607 calc: calc,
81608 calcAllAutoBins: calcAllAutoBins
81609};
81610
81611},{"../../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){
81612/**
81613* Copyright 2012-2020, Plotly, Inc.
81614* All rights reserved.
81615*
81616* This source code is licensed under the MIT license found in the
81617* LICENSE file in the root directory of this source tree.
81618*/
81619
81620
81621'use strict';
81622
81623module.exports = {
81624 eventDataKeys: ['binNumber']
81625};
81626
81627},{}],352:[function(_dereq_,module,exports){
81628/**
81629* Copyright 2012-2020, Plotly, Inc.
81630* All rights reserved.
81631*
81632* This source code is licensed under the MIT license found in the
81633* LICENSE file in the root directory of this source tree.
81634*/
81635
81636'use strict';
81637
81638var Lib = _dereq_('../../lib');
81639var axisIds = _dereq_('../../plots/cartesian/axis_ids');
81640
81641var traceIs = _dereq_('../../registry').traceIs;
81642var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
81643
81644var nestedProperty = Lib.nestedProperty;
81645var getAxisGroup = axisIds.getAxisGroup;
81646
81647var BINATTRS = [
81648 {aStr: {x: 'xbins.start', y: 'ybins.start'}, name: 'start'},
81649 {aStr: {x: 'xbins.end', y: 'ybins.end'}, name: 'end'},
81650 {aStr: {x: 'xbins.size', y: 'ybins.size'}, name: 'size'},
81651 {aStr: {x: 'nbinsx', y: 'nbinsy'}, name: 'nbins'}
81652];
81653
81654var BINDIRECTIONS = ['x', 'y'];
81655
81656// handle bin attrs and relink auto-determined values so fullData is complete
81657module.exports = function crossTraceDefaults(fullData, fullLayout) {
81658 var allBinOpts = fullLayout._histogramBinOpts = {};
81659 var histTraces = [];
81660 var mustMatchTracesLookup = {};
81661 var otherTracesList = [];
81662
81663 var traceOut, traces, groupName, binDir;
81664 var i, j, k;
81665
81666 function coerce(attr, dflt) {
81667 return Lib.coerce(traceOut._input, traceOut, traceOut._module.attributes, attr, dflt);
81668 }
81669
81670 function orientation2binDir(traceOut) {
81671 return traceOut.orientation === 'v' ? 'x' : 'y';
81672 }
81673
81674 function getAxisType(traceOut, binDir) {
81675 var ax = axisIds.getFromTrace({_fullLayout: fullLayout}, traceOut, binDir);
81676 return ax.type;
81677 }
81678
81679 function fillBinOpts(traceOut, groupName, binDir) {
81680 // N.B. group traces that don't have a bingroup with themselves
81681 var fallbackGroupName = traceOut.uid + '__' + binDir;
81682 if(!groupName) groupName = fallbackGroupName;
81683
81684 var axType = getAxisType(traceOut, binDir);
81685 var calendar = traceOut[binDir + 'calendar'] || '';
81686 var binOpts = allBinOpts[groupName];
81687 var needsNewItem = true;
81688
81689 if(binOpts) {
81690 if(axType === binOpts.axType && calendar === binOpts.calendar) {
81691 needsNewItem = false;
81692 binOpts.traces.push(traceOut);
81693 binOpts.dirs.push(binDir);
81694 } else {
81695 groupName = fallbackGroupName;
81696
81697 if(axType !== binOpts.axType) {
81698 Lib.warn([
81699 'Attempted to group the bins of trace', traceOut.index,
81700 'set on a', 'type:' + axType, 'axis',
81701 'with bins on', 'type:' + binOpts.axType, 'axis.'
81702 ].join(' '));
81703 }
81704 if(calendar !== binOpts.calendar) {
81705 // prohibit bingroup for traces using different calendar,
81706 // there's probably a way to make this work, but skip for now
81707 Lib.warn([
81708 'Attempted to group the bins of trace', traceOut.index,
81709 'set with a', calendar, 'calendar',
81710 'with bins',
81711 (binOpts.calendar ? 'on a ' + binOpts.calendar + ' calendar' : 'w/o a set calendar')
81712 ].join(' '));
81713 }
81714 }
81715 }
81716
81717 if(needsNewItem) {
81718 allBinOpts[groupName] = {
81719 traces: [traceOut],
81720 dirs: [binDir],
81721 axType: axType,
81722 calendar: traceOut[binDir + 'calendar'] || ''
81723 };
81724 }
81725 traceOut['_' + binDir + 'bingroup'] = groupName;
81726 }
81727
81728 for(i = 0; i < fullData.length; i++) {
81729 traceOut = fullData[i];
81730
81731 if(traceIs(traceOut, 'histogram')) {
81732 histTraces.push(traceOut);
81733
81734 // TODO: this shouldn't be relinked as it's only used within calc
81735 // https://github.com/plotly/plotly.js/issues/749
81736 delete traceOut._xautoBinFinished;
81737 delete traceOut._yautoBinFinished;
81738
81739 // N.B. need to coerce *alignmentgroup* before *bingroup*, as traces
81740 // in same alignmentgroup "have to match"
81741 if(!traceIs(traceOut, '2dMap')) {
81742 handleGroupingDefaults(traceOut._input, traceOut, fullLayout, coerce);
81743 }
81744 }
81745 }
81746
81747 var alignmentOpts = fullLayout._alignmentOpts || {};
81748
81749 // Look for traces that "have to match", that is:
81750 // - 1d histogram traces on the same subplot with same orientation under barmode:stack,
81751 // - 1d histogram traces on the same subplot with same orientation under barmode:group
81752 // - 1d histogram traces on the same position axis with the same orientation
81753 // and the same *alignmentgroup* (coerced under barmode:group)
81754 // - Once `stackgroup` gets implemented (see https://github.com/plotly/plotly.js/issues/3614),
81755 // traces within the same stackgroup will also "have to match"
81756 for(i = 0; i < histTraces.length; i++) {
81757 traceOut = histTraces[i];
81758 groupName = '';
81759
81760 if(!traceIs(traceOut, '2dMap')) {
81761 binDir = orientation2binDir(traceOut);
81762
81763 if(fullLayout.barmode === 'group' && traceOut.alignmentgroup) {
81764 var pa = traceOut[binDir + 'axis'];
81765 var aGroupId = getAxisGroup(fullLayout, pa) + traceOut.orientation;
81766 if((alignmentOpts[aGroupId] || {})[traceOut.alignmentgroup]) {
81767 groupName = aGroupId;
81768 }
81769 }
81770
81771 if(!groupName && fullLayout.barmode !== 'overlay') {
81772 groupName = (
81773 getAxisGroup(fullLayout, traceOut.xaxis) +
81774 getAxisGroup(fullLayout, traceOut.yaxis) +
81775 orientation2binDir(traceOut)
81776 );
81777 }
81778 }
81779
81780 if(groupName) {
81781 if(!mustMatchTracesLookup[groupName]) {
81782 mustMatchTracesLookup[groupName] = [];
81783 }
81784 mustMatchTracesLookup[groupName].push(traceOut);
81785 } else {
81786 otherTracesList.push(traceOut);
81787 }
81788 }
81789
81790 // Setup binOpts for traces that have to match,
81791 // if the traces have a valid bingroup, use that
81792 // if not use axis+binDir groupName
81793 for(groupName in mustMatchTracesLookup) {
81794 traces = mustMatchTracesLookup[groupName];
81795
81796 // no need to 'force' anything when a single
81797 // trace is detected as "must match"
81798 if(traces.length === 1) {
81799 otherTracesList.push(traces[0]);
81800 continue;
81801 }
81802
81803 var binGroupFound = false;
81804 for(i = 0; i < traces.length; i++) {
81805 traceOut = traces[i];
81806 binGroupFound = coerce('bingroup');
81807 break;
81808 }
81809
81810 groupName = binGroupFound || groupName;
81811
81812 for(i = 0; i < traces.length; i++) {
81813 traceOut = traces[i];
81814 var bingroupIn = traceOut._input.bingroup;
81815 if(bingroupIn && bingroupIn !== groupName) {
81816 Lib.warn([
81817 'Trace', traceOut.index, 'must match',
81818 'within bingroup', groupName + '.',
81819 'Ignoring its bingroup:', bingroupIn, 'setting.'
81820 ].join(' '));
81821 }
81822 traceOut.bingroup = groupName;
81823
81824 // N.B. no need to worry about 2dMap case
81825 // (where both bin direction are set in each trace)
81826 // as 2dMap trace never "have to match"
81827 fillBinOpts(traceOut, groupName, orientation2binDir(traceOut));
81828 }
81829 }
81830
81831 // setup binOpts for traces that can but don't have to match,
81832 // notice that these traces can be matched with traces that have to match
81833 for(i = 0; i < otherTracesList.length; i++) {
81834 traceOut = otherTracesList[i];
81835
81836 var binGroup = coerce('bingroup');
81837
81838 if(traceIs(traceOut, '2dMap')) {
81839 for(k = 0; k < 2; k++) {
81840 binDir = BINDIRECTIONS[k];
81841 var binGroupInDir = coerce(binDir + 'bingroup',
81842 binGroup ? binGroup + '__' + binDir : null
81843 );
81844 fillBinOpts(traceOut, binGroupInDir, binDir);
81845 }
81846 } else {
81847 fillBinOpts(traceOut, binGroup, orientation2binDir(traceOut));
81848 }
81849 }
81850
81851 // coerce bin attrs!
81852 for(groupName in allBinOpts) {
81853 var binOpts = allBinOpts[groupName];
81854 traces = binOpts.traces;
81855
81856 for(j = 0; j < BINATTRS.length; j++) {
81857 var attrSpec = BINATTRS[j];
81858 var attr = attrSpec.name;
81859 var aStr;
81860 var autoVals;
81861
81862 // nbins(x|y) is moot if we have a size. This depends on
81863 // nbins coming after size in binAttrs.
81864 if(attr === 'nbins' && binOpts.sizeFound) continue;
81865
81866 for(i = 0; i < traces.length; i++) {
81867 traceOut = traces[i];
81868 binDir = binOpts.dirs[i];
81869 aStr = attrSpec.aStr[binDir];
81870
81871 if(nestedProperty(traceOut._input, aStr).get() !== undefined) {
81872 binOpts[attr] = coerce(aStr);
81873 binOpts[attr + 'Found'] = true;
81874 break;
81875 }
81876
81877 autoVals = (traceOut._autoBin || {})[binDir] || {};
81878 if(autoVals[attr]) {
81879 // if this is the *first* autoval
81880 nestedProperty(traceOut, aStr).set(autoVals[attr]);
81881 }
81882 }
81883
81884 // start and end we need to coerce anyway, after having collected the
81885 // first of each into binOpts, in case a trace wants to restrict its
81886 // data to a certain range
81887 if(attr === 'start' || attr === 'end') {
81888 for(; i < traces.length; i++) {
81889 traceOut = traces[i];
81890 if(traceOut['_' + binDir + 'bingroup']) {
81891 autoVals = (traceOut._autoBin || {})[binDir] || {};
81892 coerce(aStr, autoVals[attr]);
81893 }
81894 }
81895 }
81896
81897 if(attr === 'nbins' && !binOpts.sizeFound && !binOpts.nbinsFound) {
81898 traceOut = traces[0];
81899 binOpts[attr] = coerce(aStr);
81900 }
81901 }
81902 }
81903};
81904
81905},{"../../lib":178,"../../plots/cartesian/axis_ids":225,"../../registry":269,"../bar/defaults":283}],353:[function(_dereq_,module,exports){
81906/**
81907* Copyright 2012-2020, Plotly, Inc.
81908* All rights reserved.
81909*
81910* This source code is licensed under the MIT license found in the
81911* LICENSE file in the root directory of this source tree.
81912*/
81913
81914'use strict';
81915
81916var Registry = _dereq_('../../registry');
81917var Lib = _dereq_('../../lib');
81918var Color = _dereq_('../../components/color');
81919
81920var handleStyleDefaults = _dereq_('../bar/style_defaults');
81921var attributes = _dereq_('./attributes');
81922
81923module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
81924 function coerce(attr, dflt) {
81925 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
81926 }
81927
81928 var x = coerce('x');
81929 var y = coerce('y');
81930
81931 var cumulative = coerce('cumulative.enabled');
81932 if(cumulative) {
81933 coerce('cumulative.direction');
81934 coerce('cumulative.currentbin');
81935 }
81936
81937 coerce('text');
81938 coerce('hovertext');
81939 coerce('hovertemplate');
81940
81941 var orientation = coerce('orientation', (y && !x) ? 'h' : 'v');
81942 var sampleLetter = orientation === 'v' ? 'x' : 'y';
81943 var aggLetter = orientation === 'v' ? 'y' : 'x';
81944
81945 var len = (x && y) ?
81946 Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) :
81947 Lib.minRowLength(traceOut[sampleLetter] || []);
81948
81949 if(!len) {
81950 traceOut.visible = false;
81951 return;
81952 }
81953
81954 traceOut._length = len;
81955
81956 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
81957 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
81958
81959 var hasAggregationData = traceOut[aggLetter];
81960 if(hasAggregationData) coerce('histfunc');
81961 coerce('histnorm');
81962
81963 // Note: bin defaults are now handled in Histogram.crossTraceDefaults
81964 // autobin(x|y) are only included here to appease Plotly.validate
81965 coerce('autobin' + sampleLetter);
81966
81967 handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
81968
81969 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
81970
81971 var lineColor = (traceOut.marker.line || {}).color;
81972
81973 // override defaultColor for error bars with defaultLine
81974 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
81975 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
81976 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
81977};
81978
81979},{"../../components/color":52,"../../lib":178,"../../registry":269,"../bar/style_defaults":294,"./attributes":345}],354:[function(_dereq_,module,exports){
81980/**
81981* Copyright 2012-2020, Plotly, Inc.
81982* All rights reserved.
81983*
81984* This source code is licensed under the MIT license found in the
81985* LICENSE file in the root directory of this source tree.
81986*/
81987
81988'use strict';
81989
81990module.exports = function eventData(out, pt, trace, cd, pointNumber) {
81991 // standard cartesian event data
81992 out.x = 'xVal' in pt ? pt.xVal : pt.x;
81993 out.y = 'yVal' in pt ? pt.yVal : pt.y;
81994
81995 // for 2d histograms
81996 if('zLabelVal' in pt) out.z = pt.zLabelVal;
81997
81998 if(pt.xa) out.xaxis = pt.xa;
81999 if(pt.ya) out.yaxis = pt.ya;
82000
82001 // specific to histogram - CDFs do not have pts (yet?)
82002 if(!(trace.cumulative || {}).enabled) {
82003 var pts = Array.isArray(pointNumber) ?
82004 cd[0].pts[pointNumber[0]][pointNumber[1]] :
82005 cd[pointNumber].pts;
82006
82007 out.pointNumbers = pts;
82008 out.binNumber = out.pointNumber;
82009 delete out.pointNumber;
82010 delete out.pointIndex;
82011
82012 var pointIndices;
82013 if(trace._indexToPoints) {
82014 pointIndices = [];
82015 for(var i = 0; i < pts.length; i++) {
82016 pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]);
82017 }
82018 } else {
82019 pointIndices = pts;
82020 }
82021
82022 out.pointIndices = pointIndices;
82023 }
82024
82025 return out;
82026};
82027
82028},{}],355:[function(_dereq_,module,exports){
82029/**
82030* Copyright 2012-2020, Plotly, Inc.
82031* All rights reserved.
82032*
82033* This source code is licensed under the MIT license found in the
82034* LICENSE file in the root directory of this source tree.
82035*/
82036
82037
82038'use strict';
82039
82040var barHover = _dereq_('../bar/hover').hoverPoints;
82041var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
82042
82043module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
82044 var pts = barHover(pointData, xval, yval, hovermode);
82045
82046 if(!pts) return;
82047
82048 pointData = pts[0];
82049 var di = pointData.cd[pointData.index];
82050 var trace = pointData.cd[0].trace;
82051
82052 if(!trace.cumulative.enabled) {
82053 var posLetter = trace.orientation === 'h' ? 'y' : 'x';
82054
82055 pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], di.ph0, di.ph1);
82056 }
82057
82058 return pts;
82059};
82060
82061},{"../../plots/cartesian/axes":222,"../bar/hover":286}],356:[function(_dereq_,module,exports){
82062/**
82063* Copyright 2012-2020, Plotly, Inc.
82064* All rights reserved.
82065*
82066* This source code is licensed under the MIT license found in the
82067* LICENSE file in the root directory of this source tree.
82068*/
82069
82070'use strict';
82071
82072/**
82073 * Histogram has its own attribute, defaults and calc steps,
82074 * but uses bar's plot to display
82075 * and bar's crossTraceCalc (formerly known as setPositions) for stacking and grouping
82076 */
82077
82078/**
82079 * histogram errorBarsOK is debatable, but it's put in for backward compat.
82080 * there are use cases for it - sqrt for a simple histogram works right now,
82081 * constant and % work but they're not so meaningful. I guess it could be cool
82082 * to allow quadrature combination of errors in summed histograms...
82083 */
82084
82085module.exports = {
82086 attributes: _dereq_('./attributes'),
82087 layoutAttributes: _dereq_('../bar/layout_attributes'),
82088 supplyDefaults: _dereq_('./defaults'),
82089 crossTraceDefaults: _dereq_('./cross_trace_defaults'),
82090 supplyLayoutDefaults: _dereq_('../bar/layout_defaults'),
82091 calc: _dereq_('./calc').calc,
82092 crossTraceCalc: _dereq_('../bar/cross_trace_calc').crossTraceCalc,
82093 plot: _dereq_('../bar/plot').plot,
82094 layerName: 'barlayer',
82095 style: _dereq_('../bar/style').style,
82096 styleOnSelect: _dereq_('../bar/style').styleOnSelect,
82097 colorbar: _dereq_('../scatter/marker_colorbar'),
82098 hoverPoints: _dereq_('./hover'),
82099 selectPoints: _dereq_('../bar/select'),
82100 eventData: _dereq_('./event_data'),
82101
82102 moduleType: 'trace',
82103 name: 'histogram',
82104 basePlotModule: _dereq_('../../plots/cartesian'),
82105 categories: ['bar-like', 'cartesian', 'svg', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend'],
82106 meta: {
82107
82108 }
82109};
82110
82111},{"../../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){
82112/**
82113* Copyright 2012-2020, Plotly, Inc.
82114* All rights reserved.
82115*
82116* This source code is licensed under the MIT license found in the
82117* LICENSE file in the root directory of this source tree.
82118*/
82119
82120
82121'use strict';
82122
82123
82124module.exports = {
82125 percent: function(size, total) {
82126 var nMax = size.length;
82127 var norm = 100 / total;
82128 for(var n = 0; n < nMax; n++) size[n] *= norm;
82129 },
82130 probability: function(size, total) {
82131 var nMax = size.length;
82132 for(var n = 0; n < nMax; n++) size[n] /= total;
82133 },
82134 density: function(size, total, inc, yinc) {
82135 var nMax = size.length;
82136 yinc = yinc || 1;
82137 for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc;
82138 },
82139 'probability density': function(size, total, inc, yinc) {
82140 var nMax = size.length;
82141 if(yinc) total /= yinc;
82142 for(var n = 0; n < nMax; n++) size[n] *= inc[n] / total;
82143 }
82144};
82145
82146},{}],358:[function(_dereq_,module,exports){
82147/**
82148* Copyright 2012-2020, Plotly, Inc.
82149* All rights reserved.
82150*
82151* This source code is licensed under the MIT license found in the
82152* LICENSE file in the root directory of this source tree.
82153*/
82154
82155'use strict';
82156
82157var histogramAttrs = _dereq_('../histogram/attributes');
82158var makeBinAttrs = _dereq_('../histogram/bin_attributes');
82159var heatmapAttrs = _dereq_('../heatmap/attributes');
82160var baseAttrs = _dereq_('../../plots/attributes');
82161var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
82162var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
82163
82164var extendFlat = _dereq_('../../lib/extend').extendFlat;
82165
82166module.exports = extendFlat(
82167 {
82168 x: histogramAttrs.x,
82169 y: histogramAttrs.y,
82170
82171 z: {
82172 valType: 'data_array',
82173 editType: 'calc',
82174
82175 },
82176 marker: {
82177 color: {
82178 valType: 'data_array',
82179 editType: 'calc',
82180
82181 },
82182 editType: 'calc'
82183 },
82184
82185 histnorm: histogramAttrs.histnorm,
82186 histfunc: histogramAttrs.histfunc,
82187 nbinsx: histogramAttrs.nbinsx,
82188 xbins: makeBinAttrs('x'),
82189 nbinsy: histogramAttrs.nbinsy,
82190 ybins: makeBinAttrs('y'),
82191 autobinx: histogramAttrs.autobinx,
82192 autobiny: histogramAttrs.autobiny,
82193
82194 bingroup: extendFlat({}, histogramAttrs.bingroup, {
82195
82196 }),
82197 xbingroup: extendFlat({}, histogramAttrs.bingroup, {
82198
82199 }),
82200 ybingroup: extendFlat({}, histogramAttrs.bingroup, {
82201
82202 }),
82203
82204 xgap: heatmapAttrs.xgap,
82205 ygap: heatmapAttrs.ygap,
82206 zsmooth: heatmapAttrs.zsmooth,
82207 zhoverformat: heatmapAttrs.zhoverformat,
82208 hovertemplate: hovertemplateAttrs({}, {keys: 'z'}),
82209 showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
82210 },
82211 colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
82212);
82213
82214},{"../../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){
82215/**
82216* Copyright 2012-2020, Plotly, Inc.
82217* All rights reserved.
82218*
82219* This source code is licensed under the MIT license found in the
82220* LICENSE file in the root directory of this source tree.
82221*/
82222
82223'use strict';
82224
82225var Lib = _dereq_('../../lib');
82226var Axes = _dereq_('../../plots/cartesian/axes');
82227
82228var binFunctions = _dereq_('../histogram/bin_functions');
82229var normFunctions = _dereq_('../histogram/norm_functions');
82230var doAvg = _dereq_('../histogram/average');
82231var getBinSpanLabelRound = _dereq_('../histogram/bin_label_vals');
82232var calcAllAutoBins = _dereq_('../histogram/calc').calcAllAutoBins;
82233
82234module.exports = function calc(gd, trace) {
82235 var xa = Axes.getFromId(gd, trace.xaxis);
82236 var ya = Axes.getFromId(gd, trace.yaxis);
82237
82238 var xcalendar = trace.xcalendar;
82239 var ycalendar = trace.ycalendar;
82240 var xr2c = function(v) { return xa.r2c(v, 0, xcalendar); };
82241 var yr2c = function(v) { return ya.r2c(v, 0, ycalendar); };
82242 var xc2r = function(v) { return xa.c2r(v, 0, xcalendar); };
82243 var yc2r = function(v) { return ya.c2r(v, 0, ycalendar); };
82244
82245 var i, j, n, m;
82246
82247 // calculate the bins
82248 var xBinsAndPos = calcAllAutoBins(gd, trace, xa, 'x');
82249 var xBinSpec = xBinsAndPos[0];
82250 var xPos0 = xBinsAndPos[1];
82251 var yBinsAndPos = calcAllAutoBins(gd, trace, ya, 'y');
82252 var yBinSpec = yBinsAndPos[0];
82253 var yPos0 = yBinsAndPos[1];
82254
82255 var serieslen = trace._length;
82256 if(xPos0.length > serieslen) xPos0.splice(serieslen, xPos0.length - serieslen);
82257 if(yPos0.length > serieslen) yPos0.splice(serieslen, yPos0.length - serieslen);
82258
82259 // make the empty bin array & scale the map
82260 var z = [];
82261 var onecol = [];
82262 var zerocol = [];
82263 var nonuniformBinsX = typeof xBinSpec.size === 'string';
82264 var nonuniformBinsY = typeof yBinSpec.size === 'string';
82265 var xEdges = [];
82266 var yEdges = [];
82267 var xbins = nonuniformBinsX ? xEdges : xBinSpec;
82268 var ybins = nonuniformBinsY ? yEdges : yBinSpec;
82269 var total = 0;
82270 var counts = [];
82271 var inputPoints = [];
82272 var norm = trace.histnorm;
82273 var func = trace.histfunc;
82274 var densitynorm = norm.indexOf('density') !== -1;
82275 var extremefunc = func === 'max' || func === 'min';
82276 var sizeinit = extremefunc ? null : 0;
82277 var binfunc = binFunctions.count;
82278 var normfunc = normFunctions[norm];
82279 var doavg = false;
82280 var xinc = [];
82281 var yinc = [];
82282
82283 // set a binning function other than count?
82284 // for binning functions: check first for 'z',
82285 // then 'mc' in case we had a colored scatter plot
82286 // and want to transfer these colors to the 2D histo
82287 // TODO: axe this, make it the responsibility of the app changing type? or an impliedEdit?
82288 var rawCounterData = ('z' in trace) ?
82289 trace.z :
82290 (('marker' in trace && Array.isArray(trace.marker.color)) ?
82291 trace.marker.color : '');
82292 if(rawCounterData && func !== 'count') {
82293 doavg = func === 'avg';
82294 binfunc = binFunctions[func];
82295 }
82296
82297 // decrease end a little in case of rounding errors
82298 var xBinSize = xBinSpec.size;
82299 var xBinStart = xr2c(xBinSpec.start);
82300 var xBinEnd = xr2c(xBinSpec.end) +
82301 (xBinStart - Axes.tickIncrement(xBinStart, xBinSize, false, xcalendar)) / 1e6;
82302
82303 for(i = xBinStart; i < xBinEnd; i = Axes.tickIncrement(i, xBinSize, false, xcalendar)) {
82304 onecol.push(sizeinit);
82305 xEdges.push(i);
82306 if(doavg) zerocol.push(0);
82307 }
82308 xEdges.push(i);
82309
82310 var nx = onecol.length;
82311 var dx = (i - xBinStart) / nx;
82312 var x0 = xc2r(xBinStart + dx / 2);
82313
82314 var yBinSize = yBinSpec.size;
82315 var yBinStart = yr2c(yBinSpec.start);
82316 var yBinEnd = yr2c(yBinSpec.end) +
82317 (yBinStart - Axes.tickIncrement(yBinStart, yBinSize, false, ycalendar)) / 1e6;
82318
82319 for(i = yBinStart; i < yBinEnd; i = Axes.tickIncrement(i, yBinSize, false, ycalendar)) {
82320 z.push(onecol.slice());
82321 yEdges.push(i);
82322 var ipCol = new Array(nx);
82323 for(j = 0; j < nx; j++) ipCol[j] = [];
82324 inputPoints.push(ipCol);
82325 if(doavg) counts.push(zerocol.slice());
82326 }
82327 yEdges.push(i);
82328
82329 var ny = z.length;
82330 var dy = (i - yBinStart) / ny;
82331 var y0 = yc2r(yBinStart + dy / 2);
82332
82333 if(densitynorm) {
82334 xinc = makeIncrements(onecol.length, xbins, dx, nonuniformBinsX);
82335 yinc = makeIncrements(z.length, ybins, dy, nonuniformBinsY);
82336 }
82337
82338 // for date axes we need bin bounds to be calcdata. For nonuniform bins
82339 // we already have this, but uniform with start/end/size they're still strings.
82340 if(!nonuniformBinsX && xa.type === 'date') xbins = binsToCalc(xr2c, xbins);
82341 if(!nonuniformBinsY && ya.type === 'date') ybins = binsToCalc(yr2c, ybins);
82342
82343 // put data into bins
82344 var uniqueValsPerX = true;
82345 var uniqueValsPerY = true;
82346 var xVals = new Array(nx);
82347 var yVals = new Array(ny);
82348 var xGapLow = Infinity;
82349 var xGapHigh = Infinity;
82350 var yGapLow = Infinity;
82351 var yGapHigh = Infinity;
82352 for(i = 0; i < serieslen; i++) {
82353 var xi = xPos0[i];
82354 var yi = yPos0[i];
82355 n = Lib.findBin(xi, xbins);
82356 m = Lib.findBin(yi, ybins);
82357 if(n >= 0 && n < nx && m >= 0 && m < ny) {
82358 total += binfunc(n, i, z[m], rawCounterData, counts[m]);
82359 inputPoints[m][n].push(i);
82360
82361 if(uniqueValsPerX) {
82362 if(xVals[n] === undefined) xVals[n] = xi;
82363 else if(xVals[n] !== xi) uniqueValsPerX = false;
82364 }
82365 if(uniqueValsPerY) {
82366 if(yVals[m] === undefined) yVals[m] = yi;
82367 else if(yVals[m] !== yi) uniqueValsPerY = false;
82368 }
82369
82370 xGapLow = Math.min(xGapLow, xi - xEdges[n]);
82371 xGapHigh = Math.min(xGapHigh, xEdges[n + 1] - xi);
82372 yGapLow = Math.min(yGapLow, yi - yEdges[m]);
82373 yGapHigh = Math.min(yGapHigh, yEdges[m + 1] - yi);
82374 }
82375 }
82376 // normalize, if needed
82377 if(doavg) {
82378 for(m = 0; m < ny; m++) total += doAvg(z[m], counts[m]);
82379 }
82380 if(normfunc) {
82381 for(m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]);
82382 }
82383
82384 return {
82385 x: xPos0,
82386 xRanges: getRanges(xEdges, uniqueValsPerX && xVals, xGapLow, xGapHigh, xa, xcalendar),
82387 x0: x0,
82388 dx: dx,
82389 y: yPos0,
82390 yRanges: getRanges(yEdges, uniqueValsPerY && yVals, yGapLow, yGapHigh, ya, ycalendar),
82391 y0: y0,
82392 dy: dy,
82393 z: z,
82394 pts: inputPoints
82395 };
82396};
82397
82398function makeIncrements(len, bins, dv, nonuniform) {
82399 var out = new Array(len);
82400 var i;
82401 if(nonuniform) {
82402 for(i = 0; i < len; i++) out[i] = 1 / (bins[i + 1] - bins[i]);
82403 } else {
82404 var inc = 1 / dv;
82405 for(i = 0; i < len; i++) out[i] = inc;
82406 }
82407 return out;
82408}
82409
82410function binsToCalc(r2c, bins) {
82411 return {
82412 start: r2c(bins.start),
82413 end: r2c(bins.end),
82414 size: bins.size
82415 };
82416}
82417
82418function getRanges(edges, uniqueVals, gapLow, gapHigh, ax, calendar) {
82419 var i;
82420 var len = edges.length - 1;
82421 var out = new Array(len);
82422 var roundFn = getBinSpanLabelRound(gapLow, gapHigh, edges, ax, calendar);
82423
82424 for(i = 0; i < len; i++) {
82425 var v = (uniqueVals || [])[i];
82426 out[i] = v === undefined ?
82427 [roundFn(edges[i]), roundFn(edges[i + 1], true)] :
82428 [v, v];
82429 }
82430 return out;
82431}
82432
82433},{"../../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){
82434/**
82435* Copyright 2012-2020, Plotly, Inc.
82436* All rights reserved.
82437*
82438* This source code is licensed under the MIT license found in the
82439* LICENSE file in the root directory of this source tree.
82440*/
82441
82442
82443'use strict';
82444
82445var Lib = _dereq_('../../lib');
82446
82447var handleSampleDefaults = _dereq_('./sample_defaults');
82448var handleStyleDefaults = _dereq_('../heatmap/style_defaults');
82449var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
82450var attributes = _dereq_('./attributes');
82451
82452
82453module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
82454 function coerce(attr, dflt) {
82455 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82456 }
82457
82458 handleSampleDefaults(traceIn, traceOut, coerce, layout);
82459 if(traceOut.visible === false) return;
82460
82461 handleStyleDefaults(traceIn, traceOut, coerce, layout);
82462 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
82463 coerce('hovertemplate');
82464};
82465
82466},{"../../components/colorscale/defaults":62,"../../lib":178,"../heatmap/style_defaults":343,"./attributes":358,"./sample_defaults":363}],361:[function(_dereq_,module,exports){
82467/**
82468* Copyright 2012-2020, Plotly, Inc.
82469* All rights reserved.
82470*
82471* This source code is licensed under the MIT license found in the
82472* LICENSE file in the root directory of this source tree.
82473*/
82474
82475
82476'use strict';
82477
82478var heatmapHover = _dereq_('../heatmap/hover');
82479var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
82480
82481module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer, contour) {
82482 var pts = heatmapHover(pointData, xval, yval, hovermode, hoverLayer, contour);
82483
82484 if(!pts) return;
82485
82486 pointData = pts[0];
82487 var indices = pointData.index;
82488 var ny = indices[0];
82489 var nx = indices[1];
82490 var cd0 = pointData.cd[0];
82491 var xRange = cd0.xRanges[nx];
82492 var yRange = cd0.yRanges[ny];
82493
82494 pointData.xLabel = hoverLabelText(pointData.xa, xRange[0], xRange[1]);
82495 pointData.yLabel = hoverLabelText(pointData.ya, yRange[0], yRange[1]);
82496
82497 return pts;
82498};
82499
82500},{"../../plots/cartesian/axes":222,"../heatmap/hover":337}],362:[function(_dereq_,module,exports){
82501/**
82502* Copyright 2012-2020, Plotly, Inc.
82503* All rights reserved.
82504*
82505* This source code is licensed under the MIT license found in the
82506* LICENSE file in the root directory of this source tree.
82507*/
82508
82509'use strict';
82510
82511module.exports = {
82512 attributes: _dereq_('./attributes'),
82513 supplyDefaults: _dereq_('./defaults'),
82514 crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
82515 calc: _dereq_('../heatmap/calc'),
82516 plot: _dereq_('../heatmap/plot'),
82517 layerName: 'heatmaplayer',
82518 colorbar: _dereq_('../heatmap/colorbar'),
82519 style: _dereq_('../heatmap/style'),
82520 hoverPoints: _dereq_('./hover'),
82521 eventData: _dereq_('../histogram/event_data'),
82522
82523 moduleType: 'trace',
82524 name: 'histogram2d',
82525 basePlotModule: _dereq_('../../plots/cartesian'),
82526 categories: ['cartesian', 'svg', '2dMap', 'histogram', 'showLegend'],
82527 meta: {
82528
82529
82530 }
82531};
82532
82533},{"../../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){
82534/**
82535* Copyright 2012-2020, Plotly, Inc.
82536* All rights reserved.
82537*
82538* This source code is licensed under the MIT license found in the
82539* LICENSE file in the root directory of this source tree.
82540*/
82541
82542'use strict';
82543
82544var Registry = _dereq_('../../registry');
82545var Lib = _dereq_('../../lib');
82546
82547module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
82548 var x = coerce('x');
82549 var y = coerce('y');
82550 var xlen = Lib.minRowLength(x);
82551 var ylen = Lib.minRowLength(y);
82552
82553 // we could try to accept x0 and dx, etc...
82554 // but that's a pretty weird use case.
82555 // for now require both x and y explicitly specified.
82556 if(!xlen || !ylen) {
82557 traceOut.visible = false;
82558 return;
82559 }
82560
82561 traceOut._length = Math.min(xlen, ylen);
82562
82563 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
82564 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
82565
82566 // if marker.color is an array, we can use it in aggregation instead of z
82567 var hasAggregationData = coerce('z') || coerce('marker.color');
82568
82569 if(hasAggregationData) coerce('histfunc');
82570 coerce('histnorm');
82571
82572 // Note: bin defaults are now handled in Histogram2D.crossTraceDefaults
82573 // autobin(x|y) are only included here to appease Plotly.validate
82574 coerce('autobinx');
82575 coerce('autobiny');
82576};
82577
82578},{"../../lib":178,"../../registry":269}],364:[function(_dereq_,module,exports){
82579/**
82580* Copyright 2012-2020, Plotly, Inc.
82581* All rights reserved.
82582*
82583* This source code is licensed under the MIT license found in the
82584* LICENSE file in the root directory of this source tree.
82585*/
82586
82587'use strict';
82588
82589var histogram2dAttrs = _dereq_('../histogram2d/attributes');
82590var contourAttrs = _dereq_('../contour/attributes');
82591var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
82592
82593var extendFlat = _dereq_('../../lib/extend').extendFlat;
82594
82595module.exports = extendFlat({
82596 x: histogram2dAttrs.x,
82597 y: histogram2dAttrs.y,
82598 z: histogram2dAttrs.z,
82599 marker: histogram2dAttrs.marker,
82600
82601 histnorm: histogram2dAttrs.histnorm,
82602 histfunc: histogram2dAttrs.histfunc,
82603 nbinsx: histogram2dAttrs.nbinsx,
82604 xbins: histogram2dAttrs.xbins,
82605 nbinsy: histogram2dAttrs.nbinsy,
82606 ybins: histogram2dAttrs.ybins,
82607 autobinx: histogram2dAttrs.autobinx,
82608 autobiny: histogram2dAttrs.autobiny,
82609
82610 bingroup: histogram2dAttrs.bingroup,
82611 xbingroup: histogram2dAttrs.xbingroup,
82612 ybingroup: histogram2dAttrs.ybingroup,
82613
82614 autocontour: contourAttrs.autocontour,
82615 ncontours: contourAttrs.ncontours,
82616 contours: contourAttrs.contours,
82617 line: {
82618 color: contourAttrs.line.color,
82619 width: extendFlat({}, contourAttrs.line.width, {
82620 dflt: 0.5,
82621
82622 }),
82623 dash: contourAttrs.line.dash,
82624 smoothing: contourAttrs.line.smoothing,
82625 editType: 'plot'
82626 },
82627 zhoverformat: histogram2dAttrs.zhoverformat,
82628 hovertemplate: histogram2dAttrs.hovertemplate
82629},
82630 colorScaleAttrs('', {
82631 cLetter: 'z',
82632 editTypeOverride: 'calc'
82633 })
82634);
82635
82636},{"../../components/colorscale/attributes":59,"../../lib/extend":173,"../contour/attributes":308,"../histogram2d/attributes":358}],365:[function(_dereq_,module,exports){
82637/**
82638* Copyright 2012-2020, Plotly, Inc.
82639* All rights reserved.
82640*
82641* This source code is licensed under the MIT license found in the
82642* LICENSE file in the root directory of this source tree.
82643*/
82644
82645
82646'use strict';
82647
82648var Lib = _dereq_('../../lib');
82649
82650var handleSampleDefaults = _dereq_('../histogram2d/sample_defaults');
82651var handleContoursDefaults = _dereq_('../contour/contours_defaults');
82652var handleStyleDefaults = _dereq_('../contour/style_defaults');
82653var attributes = _dereq_('./attributes');
82654
82655
82656module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
82657 function coerce(attr, dflt) {
82658 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82659 }
82660
82661 function coerce2(attr) {
82662 return Lib.coerce2(traceIn, traceOut, attributes, attr);
82663 }
82664
82665 handleSampleDefaults(traceIn, traceOut, coerce, layout);
82666 if(traceOut.visible === false) return;
82667
82668 handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
82669 handleStyleDefaults(traceIn, traceOut, coerce, layout);
82670 coerce('hovertemplate');
82671};
82672
82673},{"../../lib":178,"../contour/contours_defaults":315,"../contour/style_defaults":329,"../histogram2d/sample_defaults":363,"./attributes":364}],366:[function(_dereq_,module,exports){
82674/**
82675* Copyright 2012-2020, Plotly, Inc.
82676* All rights reserved.
82677*
82678* This source code is licensed under the MIT license found in the
82679* LICENSE file in the root directory of this source tree.
82680*/
82681
82682'use strict';
82683
82684module.exports = {
82685 attributes: _dereq_('./attributes'),
82686 supplyDefaults: _dereq_('./defaults'),
82687 crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
82688 calc: _dereq_('../contour/calc'),
82689 plot: _dereq_('../contour/plot').plot,
82690 layerName: 'contourlayer',
82691 style: _dereq_('../contour/style'),
82692 colorbar: _dereq_('../contour/colorbar'),
82693 hoverPoints: _dereq_('../contour/hover'),
82694
82695 moduleType: 'trace',
82696 name: 'histogram2dcontour',
82697 basePlotModule: _dereq_('../../plots/cartesian'),
82698 categories: ['cartesian', 'svg', '2dMap', 'contour', 'histogram', 'showLegend'],
82699 meta: {
82700
82701
82702 }
82703};
82704
82705},{"../../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){
82706/**
82707* Copyright 2012-2020, Plotly, Inc.
82708* All rights reserved.
82709*
82710* This source code is licensed under the MIT license found in the
82711* LICENSE file in the root directory of this source tree.
82712*/
82713
82714'use strict';
82715
82716var baseAttrs = _dereq_('../../plots/attributes');
82717var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
82718var extendFlat = _dereq_('../../lib/extend').extendFlat;
82719var colormodel = _dereq_('./constants').colormodel;
82720
82721var cm = ['rgb', 'rgba', 'hsl', 'hsla'];
82722var zminDesc = [];
82723var zmaxDesc = [];
82724for(var i = 0; i < cm.length; i++) {
82725 zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].min.join(', ') + '].');
82726 zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + colormodel[cm[i]].max.join(', ') + '].');
82727}
82728
82729module.exports = extendFlat({
82730 z: {
82731 valType: 'data_array',
82732
82733 editType: 'calc',
82734
82735 },
82736 colormodel: {
82737 valType: 'enumerated',
82738 values: cm,
82739 dflt: 'rgb',
82740
82741 editType: 'calc',
82742
82743 },
82744 zmin: {
82745 valType: 'info_array',
82746 items: [
82747 {valType: 'number', editType: 'calc'},
82748 {valType: 'number', editType: 'calc'},
82749 {valType: 'number', editType: 'calc'},
82750 {valType: 'number', editType: 'calc'}
82751 ],
82752
82753 editType: 'calc',
82754
82755 },
82756 zmax: {
82757 valType: 'info_array',
82758 items: [
82759 {valType: 'number', editType: 'calc'},
82760 {valType: 'number', editType: 'calc'},
82761 {valType: 'number', editType: 'calc'},
82762 {valType: 'number', editType: 'calc'}
82763 ],
82764
82765 editType: 'calc',
82766
82767 },
82768 x0: {
82769 valType: 'any',
82770 dflt: 0,
82771
82772 editType: 'calc+clearAxisTypes',
82773
82774 },
82775 y0: {
82776 valType: 'any',
82777 dflt: 0,
82778
82779 editType: 'calc+clearAxisTypes',
82780
82781 },
82782 dx: {
82783 valType: 'number',
82784 dflt: 1,
82785
82786 editType: 'calc',
82787
82788 },
82789 dy: {
82790 valType: 'number',
82791 dflt: 1,
82792
82793 editType: 'calc',
82794
82795 },
82796 text: {
82797 valType: 'data_array',
82798 editType: 'plot',
82799
82800 },
82801 hovertext: {
82802 valType: 'data_array',
82803 editType: 'plot',
82804
82805 },
82806 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
82807 flags: ['x', 'y', 'z', 'color', 'name', 'text'],
82808 dflt: 'x+y+z+text+name'
82809 }),
82810 hovertemplate: hovertemplateAttrs({}, {
82811 keys: ['z', 'color', 'colormodel']
82812 }),
82813
82814 transforms: undefined
82815});
82816
82817},{"../../lib/extend":173,"../../plots/attributes":219,"../../plots/template_attributes":264,"./constants":369}],368:[function(_dereq_,module,exports){
82818/**
82819* Copyright 2012-2020, Plotly, Inc.
82820* All rights reserved.
82821*
82822* This source code is licensed under the MIT license found in the
82823* LICENSE file in the root directory of this source tree.
82824*/
82825
82826'use strict';
82827
82828var Lib = _dereq_('../../lib');
82829var constants = _dereq_('./constants');
82830var isNumeric = _dereq_('fast-isnumeric');
82831var Axes = _dereq_('../../plots/cartesian/axes');
82832var maxRowLength = _dereq_('../../lib').maxRowLength;
82833
82834module.exports = function calc(gd, trace) {
82835 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
82836 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
82837
82838 var x0 = xa.d2c(trace.x0) - trace.dx / 2;
82839 var y0 = ya.d2c(trace.y0) - trace.dy / 2;
82840 var h = trace.z.length;
82841 var w = maxRowLength(trace.z);
82842
82843 // Set axis range
82844 var i;
82845 var xrange = [x0, x0 + w * trace.dx];
82846 var yrange = [y0, y0 + h * trace.dy];
82847 if(xa && xa.type === 'log') for(i = 0; i < w; i++) xrange.push(x0 + i * trace.dx);
82848 if(ya && ya.type === 'log') for(i = 0; i < h; i++) yrange.push(y0 + i * trace.dy);
82849 trace._extremes[xa._id] = Axes.findExtremes(xa, xrange);
82850 trace._extremes[ya._id] = Axes.findExtremes(ya, yrange);
82851 trace._scaler = makeScaler(trace);
82852
82853 var cd0 = {
82854 x0: x0,
82855 y0: y0,
82856 z: trace.z,
82857 w: w,
82858 h: h
82859 };
82860 return [cd0];
82861};
82862
82863function scale(zero, ratio, min, max) {
82864 return function(c) {
82865 return Lib.constrain((c - zero) * ratio, min, max);
82866 };
82867}
82868
82869function constrain(min, max) {
82870 return function(c) { return Lib.constrain(c, min, max);};
82871}
82872
82873// Generate a function to scale color components according to zmin/zmax and the colormodel
82874function makeScaler(trace) {
82875 var colormodel = trace.colormodel;
82876 var n = colormodel.length;
82877 var cr = constants.colormodel[colormodel];
82878
82879 trace._sArray = [];
82880 // Loop over all color components
82881 for(var k = 0; k < n; k++) {
82882 if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) {
82883 trace._sArray.push(scale(
82884 trace.zmin[k],
82885 (cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]),
82886 cr.min[k],
82887 cr.max[k]
82888 ));
82889 } else {
82890 trace._sArray.push(constrain(cr.min[k], cr.max[k]));
82891 }
82892 }
82893
82894 return function(pixel) {
82895 var c = pixel.slice(0, n);
82896 for(var k = 0; k < n; k++) {
82897 var ck = c[k];
82898 if(!isNumeric(ck)) return false;
82899 c[k] = trace._sArray[k](ck);
82900 }
82901 return c;
82902 };
82903}
82904
82905},{"../../lib":178,"../../plots/cartesian/axes":222,"./constants":369,"fast-isnumeric":18}],369:[function(_dereq_,module,exports){
82906/**
82907* Copyright 2012-2020, Plotly, Inc.
82908* All rights reserved.
82909*
82910* This source code is licensed under the MIT license found in the
82911* LICENSE file in the root directory of this source tree.
82912*/
82913
82914'use strict';
82915
82916module.exports = {
82917 colormodel: {
82918 rgb: {
82919 min: [0, 0, 0],
82920 max: [255, 255, 255],
82921 fmt: function(c) {return c.slice(0, 3);},
82922 suffix: ['', '', '']
82923 },
82924 rgba: {
82925 min: [0, 0, 0, 0],
82926 max: [255, 255, 255, 1],
82927 fmt: function(c) {return c.slice(0, 4);},
82928 suffix: ['', '', '', '']
82929 },
82930 hsl: {
82931 min: [0, 0, 0],
82932 max: [360, 100, 100],
82933 fmt: function(c) {
82934 var p = c.slice(0, 3);
82935 p[1] = p[1] + '%';
82936 p[2] = p[2] + '%';
82937 return p;
82938 },
82939 suffix: ['°', '%', '%']
82940 },
82941 hsla: {
82942 min: [0, 0, 0, 0],
82943 max: [360, 100, 100, 1],
82944 fmt: function(c) {
82945 var p = c.slice(0, 4);
82946 p[1] = p[1] + '%';
82947 p[2] = p[2] + '%';
82948 return p;
82949 },
82950 suffix: ['°', '%', '%', '']
82951 }
82952 }
82953};
82954
82955},{}],370:[function(_dereq_,module,exports){
82956/**
82957* Copyright 2012-2020, Plotly, Inc.
82958* All rights reserved.
82959*
82960* This source code is licensed under the MIT license found in the
82961* LICENSE file in the root directory of this source tree.
82962*/
82963
82964'use strict';
82965
82966var Lib = _dereq_('../../lib');
82967var attributes = _dereq_('./attributes');
82968var constants = _dereq_('./constants');
82969
82970module.exports = function supplyDefaults(traceIn, traceOut) {
82971 function coerce(attr, dflt) {
82972 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
82973 }
82974 var z = coerce('z');
82975 if(z === undefined || !z.length || !z[0] || !z[0].length) {
82976 traceOut.visible = false;
82977 return;
82978 }
82979
82980 coerce('x0');
82981 coerce('y0');
82982 coerce('dx');
82983 coerce('dy');
82984 var colormodel = coerce('colormodel');
82985
82986 coerce('zmin', constants.colormodel[colormodel].min);
82987 coerce('zmax', constants.colormodel[colormodel].max);
82988
82989 coerce('text');
82990 coerce('hovertext');
82991 coerce('hovertemplate');
82992
82993 traceOut._length = null;
82994};
82995
82996},{"../../lib":178,"./attributes":367,"./constants":369}],371:[function(_dereq_,module,exports){
82997/**
82998* Copyright 2012-2020, Plotly, Inc.
82999* All rights reserved.
83000*
83001* This source code is licensed under the MIT license found in the
83002* LICENSE file in the root directory of this source tree.
83003*/
83004
83005'use strict';
83006
83007module.exports = function eventData(out, pt) {
83008 if('xVal' in pt) out.x = pt.xVal;
83009 if('yVal' in pt) out.y = pt.yVal;
83010 if(pt.xa) out.xaxis = pt.xa;
83011 if(pt.ya) out.yaxis = pt.ya;
83012 out.color = pt.color;
83013 out.colormodel = pt.trace.colormodel;
83014 return out;
83015};
83016
83017},{}],372:[function(_dereq_,module,exports){
83018/**
83019* Copyright 2012-2020, Plotly, Inc.
83020* All rights reserved.
83021*
83022* This source code is licensed under the MIT license found in the
83023* LICENSE file in the root directory of this source tree.
83024*/
83025
83026'use strict';
83027
83028var Fx = _dereq_('../../components/fx');
83029var Lib = _dereq_('../../lib');
83030var constants = _dereq_('./constants');
83031
83032module.exports = function hoverPoints(pointData, xval, yval) {
83033 var cd0 = pointData.cd[0];
83034 var trace = cd0.trace;
83035 var xa = pointData.xa;
83036 var ya = pointData.ya;
83037
83038 // Return early if not on image
83039 if(Fx.inbox(xval - cd0.x0, xval - (cd0.x0 + cd0.w * trace.dx), 0) > 0 ||
83040 Fx.inbox(yval - cd0.y0, yval - (cd0.y0 + cd0.h * trace.dy), 0) > 0) {
83041 return;
83042 }
83043
83044 // Find nearest pixel's index
83045 var nx = Math.floor((xval - cd0.x0) / trace.dx);
83046 var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy);
83047
83048 // return early if pixel is undefined
83049 if(!cd0.z[ny][nx]) return;
83050
83051 var hoverinfo = cd0.hi || trace.hoverinfo;
83052 var fmtColor;
83053 if(hoverinfo) {
83054 var parts = hoverinfo.split('+');
83055 if(parts.indexOf('all') !== -1) parts = ['color'];
83056 if(parts.indexOf('color') !== -1) fmtColor = true;
83057 }
83058
83059 var colormodel = trace.colormodel;
83060 var dims = colormodel.length;
83061 var c = trace._scaler(cd0.z[ny][nx]);
83062 var s = constants.colormodel[colormodel].suffix;
83063
83064 var colorstring = [];
83065 if(trace.hovertemplate || fmtColor) {
83066 colorstring.push('[' + [c[0] + s[0], c[1] + s[1], c[2] + s[2]].join(', '));
83067 if(dims === 4) colorstring.push(', ' + c[3] + s[3]);
83068 colorstring.push(']');
83069 colorstring = colorstring.join('');
83070 pointData.extraText = colormodel.toUpperCase() + ': ' + colorstring;
83071 }
83072
83073 var text;
83074 if(Array.isArray(trace.hovertext) && Array.isArray(trace.hovertext[ny])) {
83075 text = trace.hovertext[ny][nx];
83076 } else if(Array.isArray(trace.text) && Array.isArray(trace.text[ny])) {
83077 text = trace.text[ny][nx];
83078 }
83079
83080 // TODO: for color model with 3 dims, display something useful for hovertemplate `%{color[3]}`
83081 var py = ya.c2p(cd0.y0 + (ny + 0.5) * trace.dy);
83082 var xVal = cd0.x0 + (nx + 0.5) * trace.dx;
83083 var yVal = cd0.y0 + (ny + 0.5) * trace.dy;
83084 var zLabel = '[' + cd0.z[ny][nx].slice(0, trace.colormodel.length).join(', ') + ']';
83085 return [Lib.extendFlat(pointData, {
83086 index: [ny, nx],
83087 x0: xa.c2p(cd0.x0 + nx * trace.dx),
83088 x1: xa.c2p(cd0.x0 + (nx + 1) * trace.dx),
83089 y0: py,
83090 y1: py,
83091 color: c,
83092 xVal: xVal,
83093 xLabelVal: xVal,
83094 yVal: yVal,
83095 yLabelVal: yVal,
83096 zLabelVal: zLabel,
83097 text: text,
83098 hovertemplateLabels: {
83099 'zLabel': zLabel,
83100 'colorLabel': colorstring,
83101 'color[0]Label': c[0] + s[0],
83102 'color[1]Label': c[1] + s[1],
83103 'color[2]Label': c[2] + s[2],
83104 'color[3]Label': c[3] + s[3]
83105 }
83106 })];
83107};
83108
83109},{"../../components/fx":92,"../../lib":178,"./constants":369}],373:[function(_dereq_,module,exports){
83110/**
83111* Copyright 2012-2020, Plotly, Inc.
83112* All rights reserved.
83113*
83114* This source code is licensed under the MIT license found in the
83115* LICENSE file in the root directory of this source tree.
83116*/
83117
83118'use strict';
83119
83120module.exports = {
83121 attributes: _dereq_('./attributes'),
83122 supplyDefaults: _dereq_('./defaults'),
83123 calc: _dereq_('./calc'),
83124 plot: _dereq_('./plot'),
83125 style: _dereq_('./style'),
83126 hoverPoints: _dereq_('./hover'),
83127 eventData: _dereq_('./event_data'),
83128
83129 moduleType: 'trace',
83130 name: 'image',
83131 basePlotModule: _dereq_('../../plots/cartesian'),
83132 categories: ['cartesian', 'svg', '2dMap', 'noSortingByValue'],
83133 animatable: false,
83134 meta: {
83135
83136 }
83137};
83138
83139},{"../../plots/cartesian":235,"./attributes":367,"./calc":368,"./defaults":370,"./event_data":371,"./hover":372,"./plot":374,"./style":375}],374:[function(_dereq_,module,exports){
83140/**
83141* Copyright 2012-2020, Plotly, Inc.
83142* All rights reserved.
83143*
83144* This source code is licensed under the MIT license found in the
83145* LICENSE file in the root directory of this source tree.
83146*/
83147
83148'use strict';
83149
83150var d3 = _dereq_('d3');
83151var Lib = _dereq_('../../lib');
83152var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
83153var constants = _dereq_('./constants');
83154
83155module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
83156 var xa = plotinfo.xaxis;
83157 var ya = plotinfo.yaxis;
83158
83159 Lib.makeTraceGroups(imageLayer, cdimage, 'im').each(function(cd) {
83160 var plotGroup = d3.select(this);
83161 var cd0 = cd[0];
83162 var trace = cd0.trace;
83163
83164 var z = cd0.z;
83165 var x0 = cd0.x0;
83166 var y0 = cd0.y0;
83167 var w = cd0.w;
83168 var h = cd0.h;
83169 var dx = trace.dx;
83170 var dy = trace.dy;
83171
83172 var left, right, temp, top, bottom, i;
83173 // in case of log of a negative
83174 i = 0;
83175 while(left === undefined && i < w) {
83176 left = xa.c2p(x0 + i * dx);
83177 i++;
83178 }
83179 i = w;
83180 while(right === undefined && i > 0) {
83181 right = xa.c2p(x0 + i * dx);
83182 i--;
83183 }
83184 i = 0;
83185 while(top === undefined && i < h) {
83186 top = ya.c2p(y0 + i * dy);
83187 i++;
83188 }
83189 i = h;
83190 while(bottom === undefined && i > 0) {
83191 bottom = ya.c2p(y0 + i * dy);
83192 i--;
83193 }
83194
83195 if(right < left) {
83196 temp = right;
83197 right = left;
83198 left = temp;
83199 }
83200
83201 if(bottom < top) {
83202 temp = top;
83203 top = bottom;
83204 bottom = temp;
83205 }
83206
83207 // Reduce image size when zoomed in to save memory
83208 var extra = 0.5; // half the axis size
83209 left = Math.max(-extra * xa._length, left);
83210 right = Math.min((1 + extra) * xa._length, right);
83211 top = Math.max(-extra * ya._length, top);
83212 bottom = Math.min((1 + extra) * ya._length, bottom);
83213 var imageWidth = Math.round(right - left);
83214 var imageHeight = Math.round(bottom - top);
83215
83216 // if image is entirely off-screen, don't even draw it
83217 var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
83218 if(isOffScreen) {
83219 var noImage = plotGroup.selectAll('image').data([]);
83220 noImage.exit().remove();
83221 return;
83222 }
83223
83224 // Draw each pixel
83225 var canvas = document.createElement('canvas');
83226 canvas.width = imageWidth;
83227 canvas.height = imageHeight;
83228 var context = canvas.getContext('2d');
83229
83230 var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);};
83231 var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);};
83232
83233 var fmt = constants.colormodel[trace.colormodel].fmt;
83234 var c;
83235 for(i = 0; i < cd0.w; i++) {
83236 var ipx0 = ipx(i); var ipx1 = ipx(i + 1);
83237 if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue;
83238 for(var j = 0; j < cd0.h; j++) {
83239 var jpx0 = jpx(j); var jpx1 = jpx(j + 1);
83240 if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !z[j][i]) continue;
83241 c = trace._scaler(z[j][i]);
83242 if(c) {
83243 context.fillStyle = trace.colormodel + '(' + fmt(c).join(',') + ')';
83244 } else {
83245 // Return a transparent pixel
83246 context.fillStyle = 'rgba(0,0,0,0)';
83247 }
83248 context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0);
83249 }
83250 }
83251
83252 var image3 = plotGroup.selectAll('image')
83253 .data(cd);
83254
83255 image3.enter().append('svg:image').attr({
83256 xmlns: xmlnsNamespaces.svg,
83257 preserveAspectRatio: 'none'
83258 });
83259
83260 image3.attr({
83261 height: imageHeight,
83262 width: imageWidth,
83263 x: left,
83264 y: top,
83265 'xlink:href': canvas.toDataURL('image/png')
83266 });
83267 });
83268};
83269
83270},{"../../constants/xmlns_namespaces":159,"../../lib":178,"./constants":369,"d3":16}],375:[function(_dereq_,module,exports){
83271/**
83272* Copyright 2012-2020, Plotly, Inc.
83273* All rights reserved.
83274*
83275* This source code is licensed under the MIT license found in the
83276* LICENSE file in the root directory of this source tree.
83277*/
83278
83279'use strict';
83280
83281var d3 = _dereq_('d3');
83282
83283module.exports = function style(gd) {
83284 d3.select(gd).selectAll('.im image')
83285 .style('opacity', function(d) {
83286 return d.trace.opacity;
83287 });
83288};
83289
83290},{"d3":16}],376:[function(_dereq_,module,exports){
83291/**
83292* Copyright 2012-2020, Plotly, Inc.
83293* All rights reserved.
83294*
83295* This source code is licensed under the MIT license found in the
83296* LICENSE file in the root directory of this source tree.
83297*/
83298
83299'use strict';
83300
83301var baseAttrs = _dereq_('../../plots/attributes');
83302var domainAttrs = _dereq_('../../plots/domain').attributes;
83303var fontAttrs = _dereq_('../../plots/font_attributes');
83304var colorAttrs = _dereq_('../../components/color/attributes');
83305var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
83306var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
83307
83308var extendFlat = _dereq_('../../lib/extend').extendFlat;
83309
83310var textFontAttrs = fontAttrs({
83311 editType: 'plot',
83312 arrayOk: true,
83313 colorEditType: 'plot',
83314
83315});
83316
83317module.exports = {
83318 labels: {
83319 valType: 'data_array',
83320 editType: 'calc',
83321
83322 },
83323 // equivalent of x0 and dx, if label is missing
83324 label0: {
83325 valType: 'number',
83326
83327 dflt: 0,
83328 editType: 'calc',
83329
83330 },
83331 dlabel: {
83332 valType: 'number',
83333
83334 dflt: 1,
83335 editType: 'calc',
83336
83337 },
83338
83339 values: {
83340 valType: 'data_array',
83341 editType: 'calc',
83342
83343 },
83344
83345 marker: {
83346 colors: {
83347 valType: 'data_array', // TODO 'color_array' ?
83348 editType: 'calc',
83349
83350 },
83351
83352 line: {
83353 color: {
83354 valType: 'color',
83355
83356 dflt: colorAttrs.defaultLine,
83357 arrayOk: true,
83358 editType: 'style',
83359
83360 },
83361 width: {
83362 valType: 'number',
83363
83364 min: 0,
83365 dflt: 0,
83366 arrayOk: true,
83367 editType: 'style',
83368
83369 },
83370 editType: 'calc'
83371 },
83372 editType: 'calc'
83373 },
83374
83375 text: {
83376 valType: 'data_array',
83377 editType: 'plot',
83378
83379 },
83380 hovertext: {
83381 valType: 'string',
83382
83383 dflt: '',
83384 arrayOk: true,
83385 editType: 'style',
83386
83387 },
83388
83389// 'see eg:'
83390// 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif',
83391// '(this example involves a map too - may someday be a whole trace type',
83392// 'of its own. but the point is the size of the whole pie is important.)'
83393 scalegroup: {
83394 valType: 'string',
83395
83396 dflt: '',
83397 editType: 'calc',
83398
83399 },
83400
83401 // labels (legend is handled by plots.attributes.showlegend and layout.hiddenlabels)
83402 textinfo: {
83403 valType: 'flaglist',
83404
83405 flags: ['label', 'text', 'value', 'percent'],
83406 extras: ['none'],
83407 editType: 'calc',
83408
83409 },
83410 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
83411 flags: ['label', 'text', 'value', 'percent', 'name']
83412 }),
83413 hovertemplate: hovertemplateAttrs({}, {
83414 keys: ['label', 'color', 'value', 'percent', 'text']
83415 }),
83416 texttemplate: texttemplateAttrs({editType: 'plot'}, {
83417 keys: ['label', 'color', 'value', 'percent', 'text']
83418 }),
83419 textposition: {
83420 valType: 'enumerated',
83421
83422 values: ['inside', 'outside', 'auto', 'none'],
83423 dflt: 'auto',
83424 arrayOk: true,
83425 editType: 'plot',
83426
83427 },
83428 textfont: extendFlat({}, textFontAttrs, {
83429
83430 }),
83431 insidetextorientation: {
83432 valType: 'enumerated',
83433
83434 values: ['horizontal', 'radial', 'tangential', 'auto'],
83435 dflt: 'auto',
83436 editType: 'plot',
83437
83438 },
83439 insidetextfont: extendFlat({}, textFontAttrs, {
83440
83441 }),
83442 outsidetextfont: extendFlat({}, textFontAttrs, {
83443
83444 }),
83445 automargin: {
83446 valType: 'boolean',
83447 dflt: false,
83448
83449 editType: 'plot',
83450
83451 },
83452
83453 title: {
83454 text: {
83455 valType: 'string',
83456 dflt: '',
83457
83458 editType: 'plot',
83459
83460 },
83461 font: extendFlat({}, textFontAttrs, {
83462
83463 }),
83464 position: {
83465 valType: 'enumerated',
83466 values: [
83467 'top left', 'top center', 'top right',
83468 'middle center',
83469 'bottom left', 'bottom center', 'bottom right'
83470 ],
83471
83472 editType: 'plot',
83473
83474 },
83475
83476 editType: 'plot'
83477 },
83478
83479 // position and shape
83480 domain: domainAttrs({name: 'pie', trace: true, editType: 'calc'}),
83481
83482 hole: {
83483 valType: 'number',
83484
83485 min: 0,
83486 max: 1,
83487 dflt: 0,
83488 editType: 'calc',
83489
83490 },
83491
83492 // ordering and direction
83493 sort: {
83494 valType: 'boolean',
83495
83496 dflt: true,
83497 editType: 'calc',
83498
83499 },
83500 direction: {
83501 /**
83502 * there are two common conventions, both of which place the first
83503 * (largest, if sorted) slice with its left edge at 12 o'clock but
83504 * succeeding slices follow either cw or ccw from there.
83505 *
83506 * see http://visage.co/data-visualization-101-pie-charts/
83507 */
83508 valType: 'enumerated',
83509 values: ['clockwise', 'counterclockwise'],
83510
83511 dflt: 'counterclockwise',
83512 editType: 'calc',
83513
83514 },
83515 rotation: {
83516 valType: 'number',
83517
83518 min: -360,
83519 max: 360,
83520 dflt: 0,
83521 editType: 'calc',
83522
83523 },
83524
83525 pull: {
83526 valType: 'number',
83527
83528 min: 0,
83529 max: 1,
83530 dflt: 0,
83531 arrayOk: true,
83532 editType: 'calc',
83533
83534 },
83535
83536 _deprecated: {
83537 title: {
83538 valType: 'string',
83539 dflt: '',
83540
83541 editType: 'calc',
83542
83543 },
83544 titlefont: extendFlat({}, textFontAttrs, {
83545
83546 }),
83547 titleposition: {
83548 valType: 'enumerated',
83549 values: [
83550 'top left', 'top center', 'top right',
83551 'middle center',
83552 'bottom left', 'bottom center', 'bottom right'
83553 ],
83554
83555 editType: 'calc',
83556
83557 }
83558 }
83559};
83560
83561},{"../../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){
83562/**
83563* Copyright 2012-2020, Plotly, Inc.
83564* All rights reserved.
83565*
83566* This source code is licensed under the MIT license found in the
83567* LICENSE file in the root directory of this source tree.
83568*/
83569
83570'use strict';
83571
83572var plots = _dereq_('../../plots/plots');
83573
83574exports.name = 'pie';
83575
83576exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
83577 plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
83578};
83579
83580exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
83581 plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
83582};
83583
83584},{"../../plots/plots":256}],378:[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 isNumeric = _dereq_('fast-isnumeric');
83596var tinycolor = _dereq_('tinycolor2');
83597
83598var Color = _dereq_('../../components/color');
83599
83600var extendedColorWayList = {};
83601
83602function calc(gd, trace) {
83603 var cd = [];
83604
83605 var fullLayout = gd._fullLayout;
83606 var hiddenLabels = fullLayout.hiddenlabels || [];
83607
83608 var labels = trace.labels;
83609 var colors = trace.marker.colors || [];
83610 var vals = trace.values;
83611 var len = trace._length;
83612 var hasValues = trace._hasValues && len;
83613
83614 var i, pt;
83615
83616 if(trace.dlabel) {
83617 labels = new Array(len);
83618 for(i = 0; i < len; i++) {
83619 labels[i] = String(trace.label0 + i * trace.dlabel);
83620 }
83621 }
83622
83623 var allThisTraceLabels = {};
83624 var pullColor = makePullColorFn(fullLayout['_' + trace.type + 'colormap']);
83625 var vTotal = 0;
83626 var isAggregated = false;
83627
83628 for(i = 0; i < len; i++) {
83629 var v, label, hidden;
83630 if(hasValues) {
83631 v = vals[i];
83632 if(!isNumeric(v)) continue;
83633 v = +v;
83634 if(v < 0) continue;
83635 } else v = 1;
83636
83637 label = labels[i];
83638 if(label === undefined || label === '') label = i;
83639 label = String(label);
83640
83641 var thisLabelIndex = allThisTraceLabels[label];
83642 if(thisLabelIndex === undefined) {
83643 allThisTraceLabels[label] = cd.length;
83644
83645 hidden = hiddenLabels.indexOf(label) !== -1;
83646
83647 if(!hidden) vTotal += v;
83648
83649 cd.push({
83650 v: v,
83651 label: label,
83652 color: pullColor(colors[i], label),
83653 i: i,
83654 pts: [i],
83655 hidden: hidden
83656 });
83657 } else {
83658 isAggregated = true;
83659
83660 pt = cd[thisLabelIndex];
83661 pt.v += v;
83662 pt.pts.push(i);
83663 if(!pt.hidden) vTotal += v;
83664
83665 if(pt.color === false && colors[i]) {
83666 pt.color = pullColor(colors[i], label);
83667 }
83668 }
83669 }
83670
83671 var shouldSort = (trace.type === 'funnelarea') ? isAggregated : trace.sort;
83672 if(shouldSort) cd.sort(function(a, b) { return b.v - a.v; });
83673
83674 // include the sum of all values in the first point
83675 if(cd[0]) cd[0].vTotal = vTotal;
83676
83677 return cd;
83678}
83679
83680function makePullColorFn(colorMap) {
83681 return function pullColor(color, id) {
83682 if(!color) return false;
83683
83684 color = tinycolor(color);
83685 if(!color.isValid()) return false;
83686
83687 color = Color.addOpacity(color, color.getAlpha());
83688 if(!colorMap[id]) colorMap[id] = color;
83689
83690 return color;
83691 };
83692}
83693
83694/*
83695 * `calc` filled in (and collated) explicit colors.
83696 * Now we need to propagate these explicit colors to other traces,
83697 * and fill in default colors.
83698 * This is done after sorting, so we pick defaults
83699 * in the order slices will be displayed
83700 */
83701function crossTraceCalc(gd, plotinfo) { // TODO: should we name the second argument opts?
83702 var desiredType = (plotinfo || {}).type;
83703 if(!desiredType) desiredType = 'pie';
83704
83705 var fullLayout = gd._fullLayout;
83706 var calcdata = gd.calcdata;
83707 var colorWay = fullLayout[desiredType + 'colorway'];
83708 var colorMap = fullLayout['_' + desiredType + 'colormap'];
83709
83710 if(fullLayout['extend' + desiredType + 'colors']) {
83711 colorWay = generateExtendedColors(colorWay, extendedColorWayList);
83712 }
83713 var dfltColorCount = 0;
83714
83715 for(var i = 0; i < calcdata.length; i++) {
83716 var cd = calcdata[i];
83717 var traceType = cd[0].trace.type;
83718 if(traceType !== desiredType) continue;
83719
83720 for(var j = 0; j < cd.length; j++) {
83721 var pt = cd[j];
83722 if(pt.color === false) {
83723 // have we seen this label and assigned a color to it in a previous trace?
83724 if(colorMap[pt.label]) {
83725 pt.color = colorMap[pt.label];
83726 } else {
83727 colorMap[pt.label] = pt.color = colorWay[dfltColorCount % colorWay.length];
83728 dfltColorCount++;
83729 }
83730 }
83731 }
83732 }
83733}
83734
83735/**
83736 * pick a default color from the main default set, augmented by
83737 * itself lighter then darker before repeating
83738 */
83739function generateExtendedColors(colorList, extendedColorWays) {
83740 var i;
83741 var colorString = JSON.stringify(colorList);
83742 var colors = extendedColorWays[colorString];
83743 if(!colors) {
83744 colors = colorList.slice();
83745
83746 for(i = 0; i < colorList.length; i++) {
83747 colors.push(tinycolor(colorList[i]).lighten(20).toHexString());
83748 }
83749
83750 for(i = 0; i < colorList.length; i++) {
83751 colors.push(tinycolor(colorList[i]).darken(20).toHexString());
83752 }
83753 extendedColorWays[colorString] = colors;
83754 }
83755
83756 return colors;
83757}
83758
83759module.exports = {
83760 calc: calc,
83761 crossTraceCalc: crossTraceCalc,
83762
83763 makePullColorFn: makePullColorFn,
83764 generateExtendedColors: generateExtendedColors
83765};
83766
83767},{"../../components/color":52,"fast-isnumeric":18,"tinycolor2":35}],379:[function(_dereq_,module,exports){
83768/**
83769* Copyright 2012-2020, Plotly, Inc.
83770* All rights reserved.
83771*
83772* This source code is licensed under the MIT license found in the
83773* LICENSE file in the root directory of this source tree.
83774*/
83775
83776'use strict';
83777
83778var isNumeric = _dereq_('fast-isnumeric');
83779var Lib = _dereq_('../../lib');
83780var attributes = _dereq_('./attributes');
83781var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
83782var handleText = _dereq_('../bar/defaults').handleText;
83783
83784function handleLabelsAndValues(labels, values) {
83785 var hasLabels = Array.isArray(labels);
83786 var hasValues = Lib.isArrayOrTypedArray(values);
83787 var len = Math.min(
83788 hasLabels ? labels.length : Infinity,
83789 hasValues ? values.length : Infinity
83790 );
83791
83792 if(!isFinite(len)) len = 0;
83793
83794 if(len && hasValues) {
83795 var hasPositive;
83796 for(var i = 0; i < len; i++) {
83797 var v = values[i];
83798 if(isNumeric(v) && v > 0) {
83799 hasPositive = true;
83800 break;
83801 }
83802 }
83803 if(!hasPositive) len = 0;
83804 }
83805
83806 return {
83807 hasLabels: hasLabels,
83808 hasValues: hasValues,
83809 len: len
83810 };
83811}
83812
83813function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
83814 function coerce(attr, dflt) {
83815 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
83816 }
83817
83818 var labels = coerce('labels');
83819 var values = coerce('values');
83820
83821 var res = handleLabelsAndValues(labels, values);
83822 var len = res.len;
83823 traceOut._hasLabels = res.hasLabels;
83824 traceOut._hasValues = res.hasValues;
83825
83826 if(!traceOut._hasLabels &&
83827 traceOut._hasValues
83828 ) {
83829 coerce('label0');
83830 coerce('dlabel');
83831 }
83832
83833 if(!len) {
83834 traceOut.visible = false;
83835 return;
83836 }
83837 traceOut._length = len;
83838
83839 var lineWidth = coerce('marker.line.width');
83840 if(lineWidth) coerce('marker.line.color');
83841
83842 coerce('marker.colors');
83843
83844 coerce('scalegroup');
83845 // TODO: hole needs to be coerced to the same value within a scaleegroup
83846
83847 var textData = coerce('text');
83848 var textTemplate = coerce('texttemplate');
83849 var textInfo;
83850 if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
83851
83852 coerce('hovertext');
83853 coerce('hovertemplate');
83854
83855 if(textTemplate || (textInfo && textInfo !== 'none')) {
83856 var textposition = coerce('textposition');
83857 handleText(traceIn, traceOut, layout, coerce, textposition, {
83858 moduleHasSelected: false,
83859 moduleHasUnselected: false,
83860 moduleHasConstrain: false,
83861 moduleHasCliponaxis: false,
83862 moduleHasTextangle: false,
83863 moduleHasInsideanchor: false
83864 });
83865
83866 var hasBoth = Array.isArray(textposition) || textposition === 'auto';
83867 var hasOutside = hasBoth || textposition === 'outside';
83868 if(hasOutside) {
83869 coerce('automargin');
83870 }
83871
83872 if(textposition === 'inside' || textposition === 'auto' || Array.isArray(textposition)) {
83873 coerce('insidetextorientation');
83874 }
83875 }
83876
83877 handleDomainDefaults(traceOut, layout, coerce);
83878
83879 var hole = coerce('hole');
83880 var title = coerce('title.text');
83881 if(title) {
83882 var titlePosition = coerce('title.position', hole ? 'middle center' : 'top center');
83883 if(!hole && titlePosition === 'middle center') traceOut.title.position = 'top center';
83884 Lib.coerceFont(coerce, 'title.font', layout.font);
83885 }
83886
83887 coerce('sort');
83888 coerce('direction');
83889 coerce('rotation');
83890 coerce('pull');
83891}
83892
83893module.exports = {
83894 handleLabelsAndValues: handleLabelsAndValues,
83895 supplyDefaults: supplyDefaults
83896};
83897
83898},{"../../lib":178,"../../plots/domain":249,"../bar/defaults":283,"./attributes":376,"fast-isnumeric":18}],380:[function(_dereq_,module,exports){
83899/**
83900* Copyright 2012-2020, Plotly, Inc.
83901* All rights reserved.
83902*
83903* This source code is licensed under the MIT license found in the
83904* LICENSE file in the root directory of this source tree.
83905*/
83906
83907'use strict';
83908
83909var appendArrayMultiPointValues = _dereq_('../../components/fx/helpers').appendArrayMultiPointValues;
83910
83911// Note: like other eventData routines, this creates the data for hover/unhover/click events
83912// but it has a different API and goes through a totally different pathway.
83913// So to ensure it doesn't get misused, it's not attached to the Pie module.
83914module.exports = function eventData(pt, trace) {
83915 var out = {
83916 curveNumber: trace.index,
83917 pointNumbers: pt.pts,
83918 data: trace._input,
83919 fullData: trace,
83920 label: pt.label,
83921 color: pt.color,
83922 value: pt.v,
83923 percent: pt.percent,
83924 text: pt.text,
83925
83926 // pt.v (and pt.i below) for backward compatibility
83927 v: pt.v
83928 };
83929
83930 // Only include pointNumber if it's unambiguous
83931 if(pt.pts.length === 1) out.pointNumber = out.i = pt.pts[0];
83932
83933 // Add extra data arrays to the output
83934 // notice that this is the multi-point version ('s' on the end!)
83935 // so added data will be arrays matching the pointNumbers array.
83936 appendArrayMultiPointValues(out, trace, pt.pts);
83937
83938 // don't include obsolete fields in new funnelarea traces
83939 if(trace.type === 'funnelarea') {
83940 delete out.v;
83941 delete out.i;
83942 }
83943
83944 return out;
83945};
83946
83947},{"../../components/fx/helpers":88}],381:[function(_dereq_,module,exports){
83948/**
83949* Copyright 2012-2020, Plotly, Inc.
83950* All rights reserved.
83951*
83952* This source code is licensed under the MIT license found in the
83953* LICENSE file in the root directory of this source tree.
83954*/
83955
83956'use strict';
83957
83958var Lib = _dereq_('../../lib');
83959
83960exports.formatPiePercent = function formatPiePercent(v, separators) {
83961 var vRounded = (v * 100).toPrecision(3);
83962 if(vRounded.lastIndexOf('.') !== -1) {
83963 vRounded = vRounded.replace(/[.]?0+$/, '');
83964 }
83965 return Lib.numSeparate(vRounded, separators) + '%';
83966};
83967
83968exports.formatPieValue = function formatPieValue(v, separators) {
83969 var vRounded = v.toPrecision(10);
83970 if(vRounded.lastIndexOf('.') !== -1) {
83971 vRounded = vRounded.replace(/[.]?0+$/, '');
83972 }
83973 return Lib.numSeparate(vRounded, separators);
83974};
83975
83976exports.getFirstFilled = function getFirstFilled(array, indices) {
83977 if(!Array.isArray(array)) return;
83978 for(var i = 0; i < indices.length; i++) {
83979 var v = array[indices[i]];
83980 if(v || v === 0 || v === '') return v;
83981 }
83982};
83983
83984exports.castOption = function castOption(item, indices) {
83985 if(Array.isArray(item)) return exports.getFirstFilled(item, indices);
83986 else if(item) return item;
83987};
83988
83989},{"../../lib":178}],382:[function(_dereq_,module,exports){
83990/**
83991* Copyright 2012-2020, Plotly, Inc.
83992* All rights reserved.
83993*
83994* This source code is licensed under the MIT license found in the
83995* LICENSE file in the root directory of this source tree.
83996*/
83997
83998'use strict';
83999
84000module.exports = {
84001 attributes: _dereq_('./attributes'),
84002 supplyDefaults: _dereq_('./defaults').supplyDefaults,
84003 supplyLayoutDefaults: _dereq_('./layout_defaults'),
84004 layoutAttributes: _dereq_('./layout_attributes'),
84005
84006 calc: _dereq_('./calc').calc,
84007 crossTraceCalc: _dereq_('./calc').crossTraceCalc,
84008
84009 plot: _dereq_('./plot').plot,
84010 style: _dereq_('./style'),
84011 styleOne: _dereq_('./style_one'),
84012
84013 moduleType: 'trace',
84014 name: 'pie',
84015 basePlotModule: _dereq_('./base_plot'),
84016 categories: ['pie-like', 'pie', 'showLegend'],
84017 meta: {
84018
84019 }
84020};
84021
84022},{"./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){
84023/**
84024* Copyright 2012-2020, Plotly, Inc.
84025* All rights reserved.
84026*
84027* This source code is licensed under the MIT license found in the
84028* LICENSE file in the root directory of this source tree.
84029*/
84030
84031'use strict';
84032
84033module.exports = {
84034 hiddenlabels: {
84035 valType: 'data_array',
84036
84037 editType: 'calc',
84038
84039 },
84040 piecolorway: {
84041 valType: 'colorlist',
84042
84043 editType: 'calc',
84044
84045 },
84046 extendpiecolors: {
84047 valType: 'boolean',
84048 dflt: true,
84049
84050 editType: 'calc',
84051
84052 }
84053};
84054
84055},{}],384:[function(_dereq_,module,exports){
84056/**
84057* Copyright 2012-2020, Plotly, Inc.
84058* All rights reserved.
84059*
84060* This source code is licensed under the MIT license found in the
84061* LICENSE file in the root directory of this source tree.
84062*/
84063
84064'use strict';
84065
84066var Lib = _dereq_('../../lib');
84067
84068var layoutAttributes = _dereq_('./layout_attributes');
84069
84070module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
84071 function coerce(attr, dflt) {
84072 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
84073 }
84074
84075 coerce('hiddenlabels');
84076 coerce('piecolorway', layoutOut.colorway);
84077 coerce('extendpiecolors');
84078};
84079
84080},{"../../lib":178,"./layout_attributes":383}],385:[function(_dereq_,module,exports){
84081/**
84082* Copyright 2012-2020, Plotly, Inc.
84083* All rights reserved.
84084*
84085* This source code is licensed under the MIT license found in the
84086* LICENSE file in the root directory of this source tree.
84087*/
84088
84089'use strict';
84090
84091var d3 = _dereq_('d3');
84092
84093var Plots = _dereq_('../../plots/plots');
84094var Fx = _dereq_('../../components/fx');
84095var Color = _dereq_('../../components/color');
84096var Drawing = _dereq_('../../components/drawing');
84097var Lib = _dereq_('../../lib');
84098var svgTextUtils = _dereq_('../../lib/svg_text_utils');
84099var uniformText = _dereq_('../bar/uniform_text');
84100var recordMinTextSize = uniformText.recordMinTextSize;
84101var clearMinTextSize = uniformText.clearMinTextSize;
84102var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
84103
84104var helpers = _dereq_('./helpers');
84105var eventData = _dereq_('./event_data');
84106var isValidTextValue = _dereq_('../../lib').isValidTextValue;
84107
84108function plot(gd, cdModule) {
84109 var fullLayout = gd._fullLayout;
84110 var gs = fullLayout._size;
84111
84112 clearMinTextSize('pie', fullLayout);
84113
84114 prerenderTitles(cdModule, gd);
84115 layoutAreas(cdModule, gs);
84116
84117 var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function(cd) {
84118 var plotGroup = d3.select(this);
84119 var cd0 = cd[0];
84120 var trace = cd0.trace;
84121
84122 setCoords(cd);
84123
84124 // TODO: miter might look better but can sometimes cause problems
84125 // maybe miter with a small-ish stroke-miterlimit?
84126 plotGroup.attr('stroke-linejoin', 'round');
84127
84128 plotGroup.each(function() {
84129 var slices = d3.select(this).selectAll('g.slice').data(cd);
84130
84131 slices.enter().append('g')
84132 .classed('slice', true);
84133 slices.exit().remove();
84134
84135 var quadrants = [
84136 [[], []], // y<0: x<0, x>=0
84137 [[], []] // y>=0: x<0, x>=0
84138 ];
84139 var hasOutsideText = false;
84140
84141 slices.each(function(pt, i) {
84142 if(pt.hidden) {
84143 d3.select(this).selectAll('path,g').remove();
84144 return;
84145 }
84146
84147 // to have consistent event data compared to other traces
84148 pt.pointNumber = pt.i;
84149 pt.curveNumber = trace.index;
84150
84151 quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt);
84152
84153 var cx = cd0.cx;
84154 var cy = cd0.cy;
84155 var sliceTop = d3.select(this);
84156 var slicePath = sliceTop.selectAll('path.surface').data([pt]);
84157
84158 slicePath.enter().append('path')
84159 .classed('surface', true)
84160 .style({'pointer-events': 'all'});
84161
84162 sliceTop.call(attachFxHandlers, gd, cd);
84163
84164 if(trace.pull) {
84165 var pull = +helpers.castOption(trace.pull, pt.pts) || 0;
84166 if(pull > 0) {
84167 cx += pull * pt.pxmid[0];
84168 cy += pull * pt.pxmid[1];
84169 }
84170 }
84171
84172 pt.cxFinal = cx;
84173 pt.cyFinal = cy;
84174
84175 function arc(start, finish, cw, scale) {
84176 var dx = scale * (finish[0] - start[0]);
84177 var dy = scale * (finish[1] - start[1]);
84178
84179 return 'a' +
84180 (scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' +
84181 pt.largeArc + (cw ? ' 1 ' : ' 0 ') + dx + ',' + dy;
84182 }
84183
84184 var hole = trace.hole;
84185 if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical
84186 var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) +
84187 arc(pt.px0, pt.pxmid, true, 1) +
84188 arc(pt.pxmid, pt.px0, true, 1) + 'Z';
84189 if(hole) {
84190 slicePath.attr('d',
84191 'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) +
84192 arc(pt.px0, pt.pxmid, false, hole) +
84193 arc(pt.pxmid, pt.px0, false, hole) +
84194 'Z' + outerCircle);
84195 } else slicePath.attr('d', outerCircle);
84196 } else {
84197 var outerArc = arc(pt.px0, pt.px1, true, 1);
84198
84199 if(hole) {
84200 var rim = 1 - hole;
84201 slicePath.attr('d',
84202 'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) +
84203 arc(pt.px1, pt.px0, false, hole) +
84204 'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) +
84205 outerArc +
84206 'Z');
84207 } else {
84208 slicePath.attr('d',
84209 'M' + cx + ',' + cy +
84210 'l' + pt.px0[0] + ',' + pt.px0[1] +
84211 outerArc +
84212 'Z');
84213 }
84214 }
84215
84216 // add text
84217 formatSliceLabel(gd, pt, cd0);
84218 var textPosition = helpers.castOption(trace.textposition, pt.pts);
84219 var sliceTextGroup = sliceTop.selectAll('g.slicetext')
84220 .data(pt.text && (textPosition !== 'none') ? [0] : []);
84221
84222 sliceTextGroup.enter().append('g')
84223 .classed('slicetext', true);
84224 sliceTextGroup.exit().remove();
84225
84226 sliceTextGroup.each(function() {
84227 var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
84228 // prohibit tex interpretation until we can handle
84229 // tex and regular text together
84230 s.attr('data-notex', 1);
84231 });
84232
84233 var font = Lib.ensureUniformFontSize(gd, textPosition === 'outside' ?
84234 determineOutsideTextFont(trace, pt, fullLayout.font) :
84235 determineInsideTextFont(trace, pt, fullLayout.font)
84236 );
84237
84238 sliceText.text(pt.text)
84239 .attr({
84240 'class': 'slicetext',
84241 transform: '',
84242 'text-anchor': 'middle'
84243 })
84244 .call(Drawing.font, font)
84245 .call(svgTextUtils.convertToTspans, gd);
84246
84247 // position the text relative to the slice
84248 var textBB = Drawing.bBox(sliceText.node());
84249 var transform;
84250
84251 if(textPosition === 'outside') {
84252 transform = transformOutsideText(textBB, pt);
84253 } else {
84254 transform = transformInsideText(textBB, pt, cd0);
84255 if(textPosition === 'auto' && transform.scale < 1) {
84256 var newFont = Lib.ensureUniformFontSize(gd, trace.outsidetextfont);
84257
84258 sliceText.call(Drawing.font, newFont);
84259 textBB = Drawing.bBox(sliceText.node());
84260
84261 transform = transformOutsideText(textBB, pt);
84262 }
84263 }
84264
84265 var textPosAngle = transform.textPosAngle;
84266 var textXY = textPosAngle === undefined ? pt.pxmid : getCoords(cd0.r, textPosAngle);
84267 transform.targetX = cx + textXY[0] * transform.rCenter + (transform.x || 0);
84268 transform.targetY = cy + textXY[1] * transform.rCenter + (transform.y || 0);
84269 computeTransform(transform, textBB);
84270
84271 // save some stuff to use later ensure no labels overlap
84272 if(transform.outside) {
84273 var targetY = transform.targetY;
84274 pt.yLabelMin = targetY - textBB.height / 2;
84275 pt.yLabelMid = targetY;
84276 pt.yLabelMax = targetY + textBB.height / 2;
84277 pt.labelExtraX = 0;
84278 pt.labelExtraY = 0;
84279 hasOutsideText = true;
84280 }
84281
84282 transform.fontSize = font.size;
84283 recordMinTextSize(trace.type, transform, fullLayout);
84284 cd[i].transform = transform;
84285
84286 sliceText.attr('transform', Lib.getTextTransform(transform));
84287 });
84288 });
84289
84290 // add the title
84291 var titleTextGroup = d3.select(this).selectAll('g.titletext')
84292 .data(trace.title.text ? [0] : []);
84293
84294 titleTextGroup.enter().append('g')
84295 .classed('titletext', true);
84296 titleTextGroup.exit().remove();
84297
84298 titleTextGroup.each(function() {
84299 var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
84300 // prohibit tex interpretation as above
84301 s.attr('data-notex', 1);
84302 });
84303
84304 var txt = trace.title.text;
84305 if(trace._meta) {
84306 txt = Lib.templateString(txt, trace._meta);
84307 }
84308
84309 titleText.text(txt)
84310 .attr({
84311 'class': 'titletext',
84312 transform: '',
84313 'text-anchor': 'middle',
84314 })
84315 .call(Drawing.font, trace.title.font)
84316 .call(svgTextUtils.convertToTspans, gd);
84317
84318 var transform;
84319
84320 if(trace.title.position === 'middle center') {
84321 transform = positionTitleInside(cd0);
84322 } else {
84323 transform = positionTitleOutside(cd0, gs);
84324 }
84325
84326 titleText.attr('transform',
84327 'translate(' + transform.x + ',' + transform.y + ')' +
84328 (transform.scale < 1 ? ('scale(' + transform.scale + ')') : '') +
84329 'translate(' + transform.tx + ',' + transform.ty + ')');
84330 });
84331
84332 // now make sure no labels overlap (at least within one pie)
84333 if(hasOutsideText) scootLabels(quadrants, trace);
84334
84335 plotTextLines(slices, trace);
84336
84337 if(hasOutsideText && trace.automargin) {
84338 // TODO if we ever want to improve perf,
84339 // we could reuse the textBB computed above together
84340 // with the sliceText transform info
84341 var traceBbox = Drawing.bBox(plotGroup.node());
84342
84343 var domain = trace.domain;
84344 var vpw = gs.w * (domain.x[1] - domain.x[0]);
84345 var vph = gs.h * (domain.y[1] - domain.y[0]);
84346 var xgap = (0.5 * vpw - cd0.r) / gs.w;
84347 var ygap = (0.5 * vph - cd0.r) / gs.h;
84348
84349 Plots.autoMargin(gd, 'pie.' + trace.uid + '.automargin', {
84350 xl: domain.x[0] - xgap,
84351 xr: domain.x[1] + xgap,
84352 yb: domain.y[0] - ygap,
84353 yt: domain.y[1] + ygap,
84354 l: Math.max(cd0.cx - cd0.r - traceBbox.left, 0),
84355 r: Math.max(traceBbox.right - (cd0.cx + cd0.r), 0),
84356 b: Math.max(traceBbox.bottom - (cd0.cy + cd0.r), 0),
84357 t: Math.max(cd0.cy - cd0.r - traceBbox.top, 0),
84358 pad: 5
84359 });
84360 }
84361 });
84362 });
84363
84364 // This is for a bug in Chrome (as of 2015-07-22, and does not affect FF)
84365 // if insidetextfont and outsidetextfont are different sizes, sometimes the size
84366 // of an "em" gets taken from the wrong element at first so lines are
84367 // spaced wrong. You just have to tell it to try again later and it gets fixed.
84368 // I have no idea why we haven't seen this in other contexts. Also, sometimes
84369 // it gets the initial draw correct but on redraw it gets confused.
84370 setTimeout(function() {
84371 plotGroups.selectAll('tspan').each(function() {
84372 var s = d3.select(this);
84373 if(s.attr('dy')) s.attr('dy', s.attr('dy'));
84374 });
84375 }, 0);
84376}
84377
84378// TODO add support for transition
84379function plotTextLines(slices, trace) {
84380 slices.each(function(pt) {
84381 var sliceTop = d3.select(this);
84382
84383 if(!pt.labelExtraX && !pt.labelExtraY) {
84384 sliceTop.select('path.textline').remove();
84385 return;
84386 }
84387
84388 // first move the text to its new location
84389 var sliceText = sliceTop.select('g.slicetext text');
84390
84391 pt.transform.targetX += pt.labelExtraX;
84392 pt.transform.targetY += pt.labelExtraY;
84393
84394 sliceText.attr('transform', Lib.getTextTransform(pt.transform));
84395
84396 // then add a line to the new location
84397 var lineStartX = pt.cxFinal + pt.pxmid[0];
84398 var lineStartY = pt.cyFinal + pt.pxmid[1];
84399 var textLinePath = 'M' + lineStartX + ',' + lineStartY;
84400 var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4;
84401
84402 if(pt.labelExtraX) {
84403 var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0];
84404 var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]);
84405
84406 if(Math.abs(yFromX) > Math.abs(yNet)) {
84407 textLinePath +=
84408 'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet +
84409 'H' + (lineStartX + pt.labelExtraX + finalX);
84410 } else {
84411 textLinePath += 'l' + pt.labelExtraX + ',' + yFromX +
84412 'v' + (yNet - yFromX) +
84413 'h' + finalX;
84414 }
84415 } else {
84416 textLinePath +=
84417 'V' + (pt.yLabelMid + pt.labelExtraY) +
84418 'h' + finalX;
84419 }
84420
84421 Lib.ensureSingle(sliceTop, 'path', 'textline')
84422 .call(Color.stroke, trace.outsidetextfont.color)
84423 .attr({
84424 'stroke-width': Math.min(2, trace.outsidetextfont.size / 8),
84425 d: textLinePath,
84426 fill: 'none'
84427 });
84428 });
84429}
84430
84431function attachFxHandlers(sliceTop, gd, cd) {
84432 var cd0 = cd[0];
84433 var trace = cd0.trace;
84434 var cx = cd0.cx;
84435 var cy = cd0.cy;
84436
84437 // hover state vars
84438 // have we drawn a hover label, so it should be cleared later
84439 if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
84440 // have we emitted a hover event, so later an unhover event should be emitted
84441 // note that click events do not depend on this - you can still get them
84442 // with hovermode: false or if you were earlier dragging, then clicked
84443 // in the same slice that you moused up in
84444 if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;
84445
84446 sliceTop.on('mouseover', function(pt) {
84447 // in case fullLayout or fullData has changed without a replot
84448 var fullLayout2 = gd._fullLayout;
84449 var trace2 = gd._fullData[trace.index];
84450
84451 if(gd._dragging || fullLayout2.hovermode === false) return;
84452
84453 var hoverinfo = trace2.hoverinfo;
84454 if(Array.isArray(hoverinfo)) {
84455 // super hacky: we need to pull out the *first* hoverinfo from
84456 // pt.pts, then put it back into an array in a dummy trace
84457 // and call castHoverinfo on that.
84458 // TODO: do we want to have Fx.castHoverinfo somehow handle this?
84459 // it already takes an array for index, for 2D, so this seems tricky.
84460 hoverinfo = Fx.castHoverinfo({
84461 hoverinfo: [helpers.castOption(hoverinfo, pt.pts)],
84462 _module: trace._module
84463 }, fullLayout2, 0);
84464 }
84465
84466 if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name';
84467
84468 // in case we dragged over the pie from another subplot,
84469 // or if hover is turned off
84470 if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) {
84471 var rInscribed = pt.rInscribed || 0;
84472 var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed);
84473 var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed);
84474 var separators = fullLayout2.separators;
84475 var text = [];
84476
84477 if(hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label);
84478 pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts);
84479 if(hoverinfo && hoverinfo.indexOf('text') !== -1) {
84480 var tx = pt.text;
84481 if(Lib.isValidTextValue(tx)) text.push(tx);
84482 }
84483 pt.value = pt.v;
84484 pt.valueLabel = helpers.formatPieValue(pt.v, separators);
84485 if(hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel);
84486 pt.percent = pt.v / cd0.vTotal;
84487 pt.percentLabel = helpers.formatPiePercent(pt.percent, separators);
84488 if(hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel);
84489
84490 var hoverLabel = trace2.hoverlabel;
84491 var hoverFont = hoverLabel.font;
84492
84493 Fx.loneHover({
84494 trace: trace,
84495 x0: hoverCenterX - rInscribed * cd0.r,
84496 x1: hoverCenterX + rInscribed * cd0.r,
84497 y: hoverCenterY,
84498 text: text.join('<br>'),
84499 name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined,
84500 idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
84501 color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color,
84502 borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts),
84503 fontFamily: helpers.castOption(hoverFont.family, pt.pts),
84504 fontSize: helpers.castOption(hoverFont.size, pt.pts),
84505 fontColor: helpers.castOption(hoverFont.color, pt.pts),
84506 nameLength: helpers.castOption(hoverLabel.namelength, pt.pts),
84507 textAlign: helpers.castOption(hoverLabel.align, pt.pts),
84508 hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts),
84509 hovertemplateLabels: pt,
84510 eventData: [eventData(pt, trace2)]
84511 }, {
84512 container: fullLayout2._hoverlayer.node(),
84513 outerContainer: fullLayout2._paper.node(),
84514 gd: gd
84515 });
84516
84517 trace._hasHoverLabel = true;
84518 }
84519
84520 trace._hasHoverEvent = true;
84521 gd.emit('plotly_hover', {
84522 points: [eventData(pt, trace2)],
84523 event: d3.event
84524 });
84525 });
84526
84527 sliceTop.on('mouseout', function(evt) {
84528 var fullLayout2 = gd._fullLayout;
84529 var trace2 = gd._fullData[trace.index];
84530 var pt = d3.select(this).datum();
84531
84532 if(trace._hasHoverEvent) {
84533 evt.originalEvent = d3.event;
84534 gd.emit('plotly_unhover', {
84535 points: [eventData(pt, trace2)],
84536 event: d3.event
84537 });
84538 trace._hasHoverEvent = false;
84539 }
84540
84541 if(trace._hasHoverLabel) {
84542 Fx.loneUnhover(fullLayout2._hoverlayer.node());
84543 trace._hasHoverLabel = false;
84544 }
84545 });
84546
84547 sliceTop.on('click', function(pt) {
84548 // TODO: this does not support right-click. If we want to support it, we
84549 // would likely need to change pie to use dragElement instead of straight
84550 // mapbox event binding. Or perhaps better, make a simple wrapper with the
84551 // right mousedown, mousemove, and mouseup handlers just for a left/right click
84552 // mapbox would use this too.
84553 var fullLayout2 = gd._fullLayout;
84554 var trace2 = gd._fullData[trace.index];
84555
84556 if(gd._dragging || fullLayout2.hovermode === false) return;
84557
84558 gd._hoverdata = [eventData(pt, trace2)];
84559 Fx.click(gd, d3.event);
84560 });
84561}
84562
84563function determineOutsideTextFont(trace, pt, layoutFont) {
84564 var color =
84565 helpers.castOption(trace.outsidetextfont.color, pt.pts) ||
84566 helpers.castOption(trace.textfont.color, pt.pts) ||
84567 layoutFont.color;
84568
84569 var family =
84570 helpers.castOption(trace.outsidetextfont.family, pt.pts) ||
84571 helpers.castOption(trace.textfont.family, pt.pts) ||
84572 layoutFont.family;
84573
84574 var size =
84575 helpers.castOption(trace.outsidetextfont.size, pt.pts) ||
84576 helpers.castOption(trace.textfont.size, pt.pts) ||
84577 layoutFont.size;
84578
84579 return {
84580 color: color,
84581 family: family,
84582 size: size
84583 };
84584}
84585
84586function determineInsideTextFont(trace, pt, layoutFont) {
84587 var customColor = helpers.castOption(trace.insidetextfont.color, pt.pts);
84588 if(!customColor && trace._input.textfont) {
84589 // Why not simply using trace.textfont? Because if not set, it
84590 // defaults to layout.font which has a default color. But if
84591 // textfont.color and insidetextfont.color don't supply a value,
84592 // a contrasting color shall be used.
84593 customColor = helpers.castOption(trace._input.textfont.color, pt.pts);
84594 }
84595
84596 var family =
84597 helpers.castOption(trace.insidetextfont.family, pt.pts) ||
84598 helpers.castOption(trace.textfont.family, pt.pts) ||
84599 layoutFont.family;
84600
84601 var size =
84602 helpers.castOption(trace.insidetextfont.size, pt.pts) ||
84603 helpers.castOption(trace.textfont.size, pt.pts) ||
84604 layoutFont.size;
84605
84606 return {
84607 color: customColor || Color.contrast(pt.color),
84608 family: family,
84609 size: size
84610 };
84611}
84612
84613function prerenderTitles(cdModule, gd) {
84614 var cd0, trace;
84615
84616 // Determine the width and height of the title for each pie.
84617 for(var i = 0; i < cdModule.length; i++) {
84618 cd0 = cdModule[i][0];
84619 trace = cd0.trace;
84620
84621 if(trace.title.text) {
84622 var txt = trace.title.text;
84623 if(trace._meta) {
84624 txt = Lib.templateString(txt, trace._meta);
84625 }
84626
84627 var dummyTitle = Drawing.tester.append('text')
84628 .attr('data-notex', 1)
84629 .text(txt)
84630 .call(Drawing.font, trace.title.font)
84631 .call(svgTextUtils.convertToTspans, gd);
84632 var bBox = Drawing.bBox(dummyTitle.node(), true);
84633 cd0.titleBox = {
84634 width: bBox.width,
84635 height: bBox.height,
84636 };
84637 dummyTitle.remove();
84638 }
84639 }
84640}
84641
84642function transformInsideText(textBB, pt, cd0) {
84643 var r = cd0.r || pt.rpx1;
84644 var rInscribed = pt.rInscribed;
84645
84646 var isEmpty = pt.startangle === pt.stopangle;
84647 if(isEmpty) {
84648 return {
84649 rCenter: 1 - rInscribed,
84650 scale: 0,
84651 rotate: 0,
84652 textPosAngle: 0
84653 };
84654 }
84655
84656 var ring = pt.ring;
84657 var isCircle = (ring === 1) && (Math.abs(pt.startangle - pt.stopangle) === Math.PI * 2);
84658
84659 var halfAngle = pt.halfangle;
84660 var midAngle = pt.midangle;
84661
84662 var orientation = cd0.trace.insidetextorientation;
84663 var isHorizontal = orientation === 'horizontal';
84664 var isTangential = orientation === 'tangential';
84665 var isRadial = orientation === 'radial';
84666 var isAuto = orientation === 'auto';
84667
84668 var allTransforms = [];
84669 var newT;
84670
84671 if(!isAuto) {
84672 // max size if text is placed (horizontally) at the top or bottom of the arc
84673
84674 var considerCrossing = function(angle, key) {
84675 if(isCrossing(pt, angle)) {
84676 var dStart = Math.abs(angle - pt.startangle);
84677 var dStop = Math.abs(angle - pt.stopangle);
84678
84679 var closestEdge = dStart < dStop ? dStart : dStop;
84680
84681 if(key === 'tan') {
84682 newT = calcTanTransform(textBB, r, ring, closestEdge, 0);
84683 } else { // case of 'rad'
84684 newT = calcRadTransform(textBB, r, ring, closestEdge, Math.PI / 2);
84685 }
84686 newT.textPosAngle = angle;
84687
84688 allTransforms.push(newT);
84689 }
84690 };
84691
84692 // to cover all cases with trace.rotation added
84693 var i;
84694 if(isHorizontal || isTangential) {
84695 // top
84696 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * i, 'tan');
84697 // bottom
84698 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1), 'tan');
84699 }
84700 if(isHorizontal || isRadial) {
84701 // left
84702 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1.5), 'rad');
84703 // right
84704 for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 0.5), 'rad');
84705 }
84706 }
84707
84708 if(isCircle || isAuto || isHorizontal) {
84709 // max size text can be inserted inside without rotating it
84710 // this inscribes the text rectangle in a circle, which is then inscribed
84711 // in the slice, so it will be an underestimate, which some day we may want
84712 // to improve so this case can get more use
84713 var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height);
84714
84715 newT = {
84716 scale: rInscribed * r * 2 / textDiameter,
84717
84718 // and the center position and rotation in this case
84719 rCenter: 1 - rInscribed,
84720 rotate: 0
84721 };
84722
84723 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84724 if(newT.scale >= 1) return newT;
84725
84726 allTransforms.push(newT);
84727 }
84728
84729 if(isAuto || isRadial) {
84730 newT = calcRadTransform(textBB, r, ring, halfAngle, midAngle);
84731 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84732 allTransforms.push(newT);
84733 }
84734
84735 if(isAuto || isTangential) {
84736 newT = calcTanTransform(textBB, r, ring, halfAngle, midAngle);
84737 newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
84738 allTransforms.push(newT);
84739 }
84740
84741 var id = 0;
84742 var maxScale = 0;
84743 for(var k = 0; k < allTransforms.length; k++) {
84744 var s = allTransforms[k].scale;
84745 if(maxScale < s) {
84746 maxScale = s;
84747 id = k;
84748 }
84749
84750 if(!isAuto && maxScale >= 1) {
84751 // respect test order for non-auto options
84752 break;
84753 }
84754 }
84755 return allTransforms[id];
84756}
84757
84758function isCrossing(pt, angle) {
84759 var start = pt.startangle;
84760 var stop = pt.stopangle;
84761 return (
84762 (start > angle && angle > stop) ||
84763 (start < angle && angle < stop)
84764 );
84765}
84766
84767function calcRadTransform(textBB, r, ring, halfAngle, midAngle) {
84768 r = Math.max(0, r - 2 * TEXTPAD);
84769
84770 // max size if text is rotated radially
84771 var a = textBB.width / textBB.height;
84772 var s = calcMaxHalfSize(a, halfAngle, r, ring);
84773 return {
84774 scale: s * 2 / textBB.height,
84775 rCenter: calcRCenter(a, s / r),
84776 rotate: calcRotate(midAngle)
84777 };
84778}
84779
84780function calcTanTransform(textBB, r, ring, halfAngle, midAngle) {
84781 r = Math.max(0, r - 2 * TEXTPAD);
84782
84783 // max size if text is rotated tangentially
84784 var a = textBB.height / textBB.width;
84785 var s = calcMaxHalfSize(a, halfAngle, r, ring);
84786 return {
84787 scale: s * 2 / textBB.width,
84788 rCenter: calcRCenter(a, s / r),
84789 rotate: calcRotate(midAngle + Math.PI / 2)
84790 };
84791}
84792
84793function calcRCenter(a, b) {
84794 return Math.cos(b) - a * b;
84795}
84796
84797function calcRotate(t) {
84798 return (180 / Math.PI * t + 720) % 180 - 90;
84799}
84800
84801function calcMaxHalfSize(a, halfAngle, r, ring) {
84802 var q = a + 1 / (2 * Math.tan(halfAngle));
84803 return r * Math.min(
84804 1 / (Math.sqrt(q * q + 0.5) + q),
84805 ring / (Math.sqrt(a * a + ring / 2) + a)
84806 );
84807}
84808
84809function getInscribedRadiusFraction(pt, cd0) {
84810 if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole
84811
84812 return Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2);
84813}
84814
84815function transformOutsideText(textBB, pt) {
84816 var x = pt.pxmid[0];
84817 var y = pt.pxmid[1];
84818 var dx = textBB.width / 2;
84819 var dy = textBB.height / 2;
84820
84821 if(x < 0) dx *= -1;
84822 if(y < 0) dy *= -1;
84823
84824 return {
84825 scale: 1,
84826 rCenter: 1,
84827 rotate: 0,
84828 x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2,
84829 y: dy / (1 + x * x / (y * y)),
84830 outside: true
84831 };
84832}
84833
84834function positionTitleInside(cd0) {
84835 var textDiameter =
84836 Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height);
84837 return {
84838 x: cd0.cx,
84839 y: cd0.cy,
84840 scale: cd0.trace.hole * cd0.r * 2 / textDiameter,
84841 tx: 0,
84842 ty: - cd0.titleBox.height / 2 + cd0.trace.title.font.size
84843 };
84844}
84845
84846function positionTitleOutside(cd0, plotSize) {
84847 var scaleX = 1;
84848 var scaleY = 1;
84849 var maxPull;
84850
84851 var trace = cd0.trace;
84852 // position of the baseline point of the text box in the plot, before scaling.
84853 // we anchored the text in the middle, so the baseline is on the bottom middle
84854 // of the first line of text.
84855 var topMiddle = {
84856 x: cd0.cx,
84857 y: cd0.cy
84858 };
84859 // relative translation of the text box after scaling
84860 var translate = {
84861 tx: 0,
84862 ty: 0
84863 };
84864
84865 // we reason below as if the baseline is the top middle point of the text box.
84866 // so we must add the font size to approximate the y-coord. of the top.
84867 // note that this correction must happen after scaling.
84868 translate.ty += trace.title.font.size;
84869 maxPull = getMaxPull(trace);
84870
84871 if(trace.title.position.indexOf('top') !== -1) {
84872 topMiddle.y -= (1 + maxPull) * cd0.r;
84873 translate.ty -= cd0.titleBox.height;
84874 } else if(trace.title.position.indexOf('bottom') !== -1) {
84875 topMiddle.y += (1 + maxPull) * cd0.r;
84876 }
84877
84878 var rx = applyAspectRatio(cd0.r, cd0.trace.aspectratio);
84879
84880 var maxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) / 2;
84881 if(trace.title.position.indexOf('left') !== -1) {
84882 // we start the text at the left edge of the pie
84883 maxWidth = maxWidth + rx;
84884 topMiddle.x -= (1 + maxPull) * rx;
84885 translate.tx += cd0.titleBox.width / 2;
84886 } else if(trace.title.position.indexOf('center') !== -1) {
84887 maxWidth *= 2;
84888 } else if(trace.title.position.indexOf('right') !== -1) {
84889 maxWidth = maxWidth + rx;
84890 topMiddle.x += (1 + maxPull) * rx;
84891 translate.tx -= cd0.titleBox.width / 2;
84892 }
84893 scaleX = maxWidth / cd0.titleBox.width;
84894 scaleY = getTitleSpace(cd0, plotSize) / cd0.titleBox.height;
84895 return {
84896 x: topMiddle.x,
84897 y: topMiddle.y,
84898 scale: Math.min(scaleX, scaleY),
84899 tx: translate.tx,
84900 ty: translate.ty
84901 };
84902}
84903
84904function applyAspectRatio(x, aspectratio) {
84905 return x / ((aspectratio === undefined) ? 1 : aspectratio);
84906}
84907
84908function getTitleSpace(cd0, plotSize) {
84909 var trace = cd0.trace;
84910 var pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]);
84911 // use at most half of the plot for the title
84912 return Math.min(cd0.titleBox.height, pieBoxHeight / 2);
84913}
84914
84915function getMaxPull(trace) {
84916 var maxPull = trace.pull;
84917 if(!maxPull) return 0;
84918
84919 var j;
84920 if(Array.isArray(maxPull)) {
84921 maxPull = 0;
84922 for(j = 0; j < trace.pull.length; j++) {
84923 if(trace.pull[j] > maxPull) maxPull = trace.pull[j];
84924 }
84925 }
84926 return maxPull;
84927}
84928
84929function scootLabels(quadrants, trace) {
84930 var xHalf, yHalf, equatorFirst, farthestX, farthestY,
84931 xDiffSign, yDiffSign, thisQuad, oppositeQuad,
84932 wholeSide, i, thisQuadOutside, firstOppositeOutsidePt;
84933
84934 function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; }
84935 function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; }
84936
84937 function scootOneLabel(thisPt, prevPt) {
84938 if(!prevPt) prevPt = {};
84939
84940 var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin);
84941 var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax;
84942 var thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin;
84943 var thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]);
84944 var newExtraY = prevOuterY - thisInnerY;
84945
84946 var xBuffer, i, otherPt, otherOuterY, otherOuterX, newExtraX;
84947
84948 // make sure this label doesn't overlap other labels
84949 // this *only* has us move these labels vertically
84950 if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY;
84951
84952 // make sure this label doesn't overlap any slices
84953 if(!Array.isArray(trace.pull)) return; // this can only happen with array pulls
84954
84955 for(i = 0; i < wholeSide.length; i++) {
84956 otherPt = wholeSide[i];
84957
84958 // overlap can only happen if the other point is pulled more than this one
84959 if(otherPt === thisPt || (
84960 (helpers.castOption(trace.pull, thisPt.pts) || 0) >=
84961 (helpers.castOption(trace.pull, otherPt.pts) || 0))
84962 ) {
84963 continue;
84964 }
84965
84966 if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) {
84967 // closer to the equator - by construction all of these happen first
84968 // move the text vertically to get away from these slices
84969 otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]);
84970 newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY;
84971
84972 if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY;
84973 } else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) {
84974 // farther from the equator - happens after we've done all the
84975 // vertical moving we're going to do
84976 // move horizontally to get away from these more polar slices
84977
84978 // if we're moving horz. based on a slice that's several slices away from this one
84979 // then we need some extra space for the lines to labels between them
84980 xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt));
84981
84982 otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]);
84983 newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX;
84984
84985 if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX;
84986 }
84987 }
84988 }
84989
84990 for(yHalf = 0; yHalf < 2; yHalf++) {
84991 equatorFirst = yHalf ? topFirst : bottomFirst;
84992 farthestY = yHalf ? Math.max : Math.min;
84993 yDiffSign = yHalf ? 1 : -1;
84994
84995 for(xHalf = 0; xHalf < 2; xHalf++) {
84996 farthestX = xHalf ? Math.max : Math.min;
84997 xDiffSign = xHalf ? 1 : -1;
84998
84999 // first sort the array
85000 // note this is a copy of cd, so cd itself doesn't get sorted
85001 // but we can still modify points in place.
85002 thisQuad = quadrants[yHalf][xHalf];
85003 thisQuad.sort(equatorFirst);
85004
85005 oppositeQuad = quadrants[1 - yHalf][xHalf];
85006 wholeSide = oppositeQuad.concat(thisQuad);
85007
85008 thisQuadOutside = [];
85009 for(i = 0; i < thisQuad.length; i++) {
85010 if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]);
85011 }
85012
85013 firstOppositeOutsidePt = false;
85014 for(i = 0; yHalf && i < oppositeQuad.length; i++) {
85015 if(oppositeQuad[i].yLabelMid !== undefined) {
85016 firstOppositeOutsidePt = oppositeQuad[i];
85017 break;
85018 }
85019 }
85020
85021 // each needs to avoid the previous
85022 for(i = 0; i < thisQuadOutside.length; i++) {
85023 var prevPt = i && thisQuadOutside[i - 1];
85024 // bottom half needs to avoid the first label of the top half
85025 // top half we still need to call scootOneLabel on the first slice
85026 // so we can avoid other slices, but we don't pass a prevPt
85027 if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt;
85028 scootOneLabel(thisQuadOutside[i], prevPt);
85029 }
85030 }
85031 }
85032}
85033
85034function layoutAreas(cdModule, plotSize) {
85035 var scaleGroups = [];
85036
85037 // figure out the center and maximum radius
85038 for(var i = 0; i < cdModule.length; i++) {
85039 var cd0 = cdModule[i][0];
85040 var trace = cd0.trace;
85041
85042 var domain = trace.domain;
85043 var width = plotSize.w * (domain.x[1] - domain.x[0]);
85044 var height = plotSize.h * (domain.y[1] - domain.y[0]);
85045 // leave some space for the title, if it will be displayed outside
85046 if(trace.title.text && trace.title.position !== 'middle center') {
85047 height -= getTitleSpace(cd0, plotSize);
85048 }
85049
85050 var rx = width / 2;
85051 var ry = height / 2;
85052 if(trace.type === 'funnelarea' && !trace.scalegroup) {
85053 ry /= trace.aspectratio;
85054 }
85055
85056 cd0.r = Math.min(rx, ry) / (1 + getMaxPull(trace));
85057
85058 cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2;
85059 cd0.cy = plotSize.t + plotSize.h * (1 - trace.domain.y[0]) - height / 2;
85060 if(trace.title.text && trace.title.position.indexOf('bottom') !== -1) {
85061 cd0.cy -= getTitleSpace(cd0, plotSize);
85062 }
85063
85064 if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) {
85065 scaleGroups.push(trace.scalegroup);
85066 }
85067 }
85068
85069 groupScale(cdModule, scaleGroups);
85070}
85071
85072function groupScale(cdModule, scaleGroups) {
85073 var cd0, i, trace;
85074
85075 // scale those that are grouped
85076 for(var k = 0; k < scaleGroups.length; k++) {
85077 var min = Infinity;
85078 var g = scaleGroups[k];
85079
85080 for(i = 0; i < cdModule.length; i++) {
85081 cd0 = cdModule[i][0];
85082 trace = cd0.trace;
85083
85084 if(trace.scalegroup === g) {
85085 var area;
85086 if(trace.type === 'pie') {
85087 area = cd0.r * cd0.r;
85088 } else if(trace.type === 'funnelarea') {
85089 var rx, ry;
85090
85091 if(trace.aspectratio > 1) {
85092 rx = cd0.r;
85093 ry = rx / trace.aspectratio;
85094 } else {
85095 ry = cd0.r;
85096 rx = ry * trace.aspectratio;
85097 }
85098
85099 rx *= (1 + trace.baseratio) / 2;
85100
85101 area = rx * ry;
85102 }
85103
85104 min = Math.min(min, area / cd0.vTotal);
85105 }
85106 }
85107
85108 for(i = 0; i < cdModule.length; i++) {
85109 cd0 = cdModule[i][0];
85110 trace = cd0.trace;
85111 if(trace.scalegroup === g) {
85112 var v = min * cd0.vTotal;
85113 if(trace.type === 'funnelarea') {
85114 v /= (1 + trace.baseratio) / 2;
85115 v /= trace.aspectratio;
85116 }
85117
85118 cd0.r = Math.sqrt(v);
85119 }
85120 }
85121 }
85122}
85123
85124function setCoords(cd) {
85125 var cd0 = cd[0];
85126 var r = cd0.r;
85127 var trace = cd0.trace;
85128 var currentAngle = trace.rotation * Math.PI / 180;
85129 var angleFactor = 2 * Math.PI / cd0.vTotal;
85130 var firstPt = 'px0';
85131 var lastPt = 'px1';
85132
85133 var i, cdi, currentCoords;
85134
85135 if(trace.direction === 'counterclockwise') {
85136 for(i = 0; i < cd.length; i++) {
85137 if(!cd[i].hidden) break; // find the first non-hidden slice
85138 }
85139 if(i === cd.length) return; // all slices hidden
85140
85141 currentAngle += angleFactor * cd[i].v;
85142 angleFactor *= -1;
85143 firstPt = 'px1';
85144 lastPt = 'px0';
85145 }
85146
85147 currentCoords = getCoords(r, currentAngle);
85148
85149 for(i = 0; i < cd.length; i++) {
85150 cdi = cd[i];
85151 if(cdi.hidden) continue;
85152
85153 cdi[firstPt] = currentCoords;
85154
85155 cdi.startangle = currentAngle;
85156 currentAngle += angleFactor * cdi.v / 2;
85157 cdi.pxmid = getCoords(r, currentAngle);
85158 cdi.midangle = currentAngle;
85159 currentAngle += angleFactor * cdi.v / 2;
85160 currentCoords = getCoords(r, currentAngle);
85161 cdi.stopangle = currentAngle;
85162
85163 cdi[lastPt] = currentCoords;
85164
85165 cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0;
85166
85167 cdi.halfangle = Math.PI * Math.min(cdi.v / cd0.vTotal, 0.5);
85168 cdi.ring = 1 - trace.hole;
85169 cdi.rInscribed = getInscribedRadiusFraction(cdi, cd0);
85170 }
85171}
85172
85173function getCoords(r, angle) {
85174 return [r * Math.sin(angle), -r * Math.cos(angle)];
85175}
85176
85177function formatSliceLabel(gd, pt, cd0) {
85178 var fullLayout = gd._fullLayout;
85179 var trace = cd0.trace;
85180 // look for textemplate
85181 var texttemplate = trace.texttemplate;
85182
85183 // now insert text
85184 var textinfo = trace.textinfo;
85185 if(!texttemplate && textinfo && textinfo !== 'none') {
85186 var parts = textinfo.split('+');
85187 var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
85188 var hasLabel = hasFlag('label');
85189 var hasText = hasFlag('text');
85190 var hasValue = hasFlag('value');
85191 var hasPercent = hasFlag('percent');
85192
85193 var separators = fullLayout.separators;
85194 var text;
85195
85196 text = hasLabel ? [pt.label] : [];
85197 if(hasText) {
85198 var tx = helpers.getFirstFilled(trace.text, pt.pts);
85199 if(isValidTextValue(tx)) text.push(tx);
85200 }
85201 if(hasValue) text.push(helpers.formatPieValue(pt.v, separators));
85202 if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
85203 pt.text = text.join('<br>');
85204 }
85205
85206 function makeTemplateVariables(pt) {
85207 return {
85208 label: pt.label,
85209 value: pt.v,
85210 valueLabel: helpers.formatPieValue(pt.v, fullLayout.separators),
85211 percent: pt.v / cd0.vTotal,
85212 percentLabel: helpers.formatPiePercent(pt.v / cd0.vTotal, fullLayout.separators),
85213 color: pt.color,
85214 text: pt.text,
85215 customdata: Lib.castOption(trace, pt.i, 'customdata')
85216 };
85217 }
85218
85219 if(texttemplate) {
85220 var txt = Lib.castOption(trace, pt.i, 'texttemplate');
85221 if(!txt) {
85222 pt.text = '';
85223 } else {
85224 var obj = makeTemplateVariables(pt);
85225 var ptTx = helpers.getFirstFilled(trace.text, pt.pts);
85226 if(isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx;
85227 pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {});
85228 }
85229 }
85230}
85231
85232function computeTransform(
85233 transform, // inout
85234 textBB // in
85235) {
85236 var a = transform.rotate * Math.PI / 180;
85237 var cosA = Math.cos(a);
85238 var sinA = Math.sin(a);
85239 var midX = (textBB.left + textBB.right) / 2;
85240 var midY = (textBB.top + textBB.bottom) / 2;
85241 transform.textX = midX * cosA - midY * sinA;
85242 transform.textY = midX * sinA + midY * cosA;
85243 transform.noCenter = true;
85244}
85245
85246module.exports = {
85247 plot: plot,
85248 formatSliceLabel: formatSliceLabel,
85249 transformInsideText: transformInsideText,
85250 determineInsideTextFont: determineInsideTextFont,
85251 positionTitleOutside: positionTitleOutside,
85252 prerenderTitles: prerenderTitles,
85253 layoutAreas: layoutAreas,
85254 attachFxHandlers: attachFxHandlers,
85255 computeTransform: computeTransform
85256};
85257
85258},{"../../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){
85259/**
85260* Copyright 2012-2020, Plotly, Inc.
85261* All rights reserved.
85262*
85263* This source code is licensed under the MIT license found in the
85264* LICENSE file in the root directory of this source tree.
85265*/
85266
85267'use strict';
85268
85269var d3 = _dereq_('d3');
85270
85271var styleOne = _dereq_('./style_one');
85272var resizeText = _dereq_('../bar/uniform_text').resizeText;
85273
85274module.exports = function style(gd) {
85275 var s = gd._fullLayout._pielayer.selectAll('.trace');
85276 resizeText(gd, s, 'pie');
85277
85278 s.each(function(cd) {
85279 var cd0 = cd[0];
85280 var trace = cd0.trace;
85281 var traceSelection = d3.select(this);
85282
85283 traceSelection.style({opacity: trace.opacity});
85284
85285 traceSelection.selectAll('path.surface').each(function(pt) {
85286 d3.select(this).call(styleOne, pt, trace);
85287 });
85288 });
85289};
85290
85291},{"../bar/uniform_text":295,"./style_one":387,"d3":16}],387:[function(_dereq_,module,exports){
85292/**
85293* Copyright 2012-2020, Plotly, Inc.
85294* All rights reserved.
85295*
85296* This source code is licensed under the MIT license found in the
85297* LICENSE file in the root directory of this source tree.
85298*/
85299
85300'use strict';
85301
85302var Color = _dereq_('../../components/color');
85303var castOption = _dereq_('./helpers').castOption;
85304
85305module.exports = function styleOne(s, pt, trace) {
85306 var line = trace.marker.line;
85307 var lineColor = castOption(line.color, pt.pts) || Color.defaultLine;
85308 var lineWidth = castOption(line.width, pt.pts) || 0;
85309
85310 s.style('stroke-width', lineWidth)
85311 .call(Color.fill, pt.color)
85312 .call(Color.stroke, lineColor);
85313};
85314
85315},{"../../components/color":52,"./helpers":381}],388:[function(_dereq_,module,exports){
85316/**
85317* Copyright 2012-2020, Plotly, Inc.
85318* All rights reserved.
85319*
85320* This source code is licensed under the MIT license found in the
85321* LICENSE file in the root directory of this source tree.
85322*/
85323
85324
85325'use strict';
85326
85327var Lib = _dereq_('../../lib');
85328
85329
85330// arrayOk attributes, merge them into calcdata array
85331module.exports = function arraysToCalcdata(cd, trace) {
85332 // so each point knows which index it originally came from
85333 for(var i = 0; i < cd.length; i++) cd[i].i = i;
85334
85335 Lib.mergeArray(trace.text, cd, 'tx');
85336 Lib.mergeArray(trace.texttemplate, cd, 'txt');
85337 Lib.mergeArray(trace.hovertext, cd, 'htx');
85338 Lib.mergeArray(trace.customdata, cd, 'data');
85339 Lib.mergeArray(trace.textposition, cd, 'tp');
85340 if(trace.textfont) {
85341 Lib.mergeArrayCastPositive(trace.textfont.size, cd, 'ts');
85342 Lib.mergeArray(trace.textfont.color, cd, 'tc');
85343 Lib.mergeArray(trace.textfont.family, cd, 'tf');
85344 }
85345
85346 var marker = trace.marker;
85347 if(marker) {
85348 Lib.mergeArrayCastPositive(marker.size, cd, 'ms');
85349 Lib.mergeArrayCastPositive(marker.opacity, cd, 'mo');
85350 Lib.mergeArray(marker.symbol, cd, 'mx');
85351 Lib.mergeArray(marker.color, cd, 'mc');
85352
85353 var markerLine = marker.line;
85354 if(marker.line) {
85355 Lib.mergeArray(markerLine.color, cd, 'mlc');
85356 Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
85357 }
85358
85359 var markerGradient = marker.gradient;
85360 if(markerGradient && markerGradient.type !== 'none') {
85361 Lib.mergeArray(markerGradient.type, cd, 'mgt');
85362 Lib.mergeArray(markerGradient.color, cd, 'mgc');
85363 }
85364 }
85365};
85366
85367},{"../../lib":178}],389:[function(_dereq_,module,exports){
85368/**
85369* Copyright 2012-2020, Plotly, Inc.
85370* All rights reserved.
85371*
85372* This source code is licensed under the MIT license found in the
85373* LICENSE file in the root directory of this source tree.
85374*/
85375
85376'use strict';
85377
85378var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
85379var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
85380var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
85381var fontAttrs = _dereq_('../../plots/font_attributes');
85382var dash = _dereq_('../../components/drawing/attributes').dash;
85383
85384var Drawing = _dereq_('../../components/drawing');
85385var constants = _dereq_('./constants');
85386var extendFlat = _dereq_('../../lib/extend').extendFlat;
85387
85388module.exports = {
85389 x: {
85390 valType: 'data_array',
85391 editType: 'calc+clearAxisTypes',
85392 anim: true,
85393
85394 },
85395 x0: {
85396 valType: 'any',
85397 dflt: 0,
85398
85399 editType: 'calc+clearAxisTypes',
85400 anim: true,
85401
85402 },
85403 dx: {
85404 valType: 'number',
85405 dflt: 1,
85406
85407 editType: 'calc',
85408 anim: true,
85409
85410 },
85411 y: {
85412 valType: 'data_array',
85413 editType: 'calc+clearAxisTypes',
85414 anim: true,
85415
85416 },
85417 y0: {
85418 valType: 'any',
85419 dflt: 0,
85420
85421 editType: 'calc+clearAxisTypes',
85422 anim: true,
85423
85424 },
85425 dy: {
85426 valType: 'number',
85427 dflt: 1,
85428
85429 editType: 'calc',
85430 anim: true,
85431
85432 },
85433
85434 stackgroup: {
85435 valType: 'string',
85436
85437 dflt: '',
85438 editType: 'calc',
85439
85440 },
85441 orientation: {
85442 valType: 'enumerated',
85443
85444 values: ['v', 'h'],
85445 editType: 'calc',
85446
85447 },
85448 groupnorm: {
85449 valType: 'enumerated',
85450 values: ['', 'fraction', 'percent'],
85451 dflt: '',
85452
85453 editType: 'calc',
85454
85455 },
85456 stackgaps: {
85457 valType: 'enumerated',
85458 values: ['infer zero', 'interpolate'],
85459 dflt: 'infer zero',
85460
85461 editType: 'calc',
85462
85463 },
85464
85465 text: {
85466 valType: 'string',
85467
85468 dflt: '',
85469 arrayOk: true,
85470 editType: 'calc',
85471
85472 },
85473
85474 texttemplate: texttemplateAttrs({}, {
85475
85476 }),
85477 hovertext: {
85478 valType: 'string',
85479
85480 dflt: '',
85481 arrayOk: true,
85482 editType: 'style',
85483
85484 },
85485 mode: {
85486 valType: 'flaglist',
85487 flags: ['lines', 'markers', 'text'],
85488 extras: ['none'],
85489
85490 editType: 'calc',
85491
85492 },
85493 hoveron: {
85494 valType: 'flaglist',
85495 flags: ['points', 'fills'],
85496
85497 editType: 'style',
85498
85499 },
85500 hovertemplate: hovertemplateAttrs({}, {
85501 keys: constants.eventDataKeys
85502 }),
85503 line: {
85504 color: {
85505 valType: 'color',
85506
85507 editType: 'style',
85508 anim: true,
85509
85510 },
85511 width: {
85512 valType: 'number',
85513 min: 0,
85514 dflt: 2,
85515
85516 editType: 'style',
85517 anim: true,
85518
85519 },
85520 shape: {
85521 valType: 'enumerated',
85522 values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
85523 dflt: 'linear',
85524
85525 editType: 'plot',
85526
85527 },
85528 smoothing: {
85529 valType: 'number',
85530 min: 0,
85531 max: 1.3,
85532 dflt: 1,
85533
85534 editType: 'plot',
85535
85536 },
85537 dash: extendFlat({}, dash, {editType: 'style'}),
85538 simplify: {
85539 valType: 'boolean',
85540 dflt: true,
85541
85542 editType: 'plot',
85543
85544 },
85545 editType: 'plot'
85546 },
85547
85548 connectgaps: {
85549 valType: 'boolean',
85550 dflt: false,
85551
85552 editType: 'calc',
85553
85554 },
85555 cliponaxis: {
85556 valType: 'boolean',
85557 dflt: true,
85558
85559 editType: 'plot',
85560
85561 },
85562
85563 fill: {
85564 valType: 'enumerated',
85565 values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'],
85566
85567 editType: 'calc',
85568
85569 },
85570 fillcolor: {
85571 valType: 'color',
85572
85573 editType: 'style',
85574 anim: true,
85575
85576 },
85577 marker: extendFlat({
85578 symbol: {
85579 valType: 'enumerated',
85580 values: Drawing.symbolList,
85581 dflt: 'circle',
85582 arrayOk: true,
85583
85584 editType: 'style',
85585
85586 },
85587 opacity: {
85588 valType: 'number',
85589 min: 0,
85590 max: 1,
85591 arrayOk: true,
85592
85593 editType: 'style',
85594 anim: true,
85595
85596 },
85597 size: {
85598 valType: 'number',
85599 min: 0,
85600 dflt: 6,
85601 arrayOk: true,
85602
85603 editType: 'calc',
85604 anim: true,
85605
85606 },
85607 maxdisplayed: {
85608 valType: 'number',
85609 min: 0,
85610 dflt: 0,
85611
85612 editType: 'plot',
85613
85614 },
85615 sizeref: {
85616 valType: 'number',
85617 dflt: 1,
85618
85619 editType: 'calc',
85620
85621 },
85622 sizemin: {
85623 valType: 'number',
85624 min: 0,
85625 dflt: 0,
85626
85627 editType: 'calc',
85628
85629 },
85630 sizemode: {
85631 valType: 'enumerated',
85632 values: ['diameter', 'area'],
85633 dflt: 'diameter',
85634
85635 editType: 'calc',
85636
85637 },
85638
85639 line: extendFlat({
85640 width: {
85641 valType: 'number',
85642 min: 0,
85643 arrayOk: true,
85644
85645 editType: 'style',
85646 anim: true,
85647
85648 },
85649 editType: 'calc'
85650 },
85651 colorScaleAttrs('marker.line', {anim: true})
85652 ),
85653 gradient: {
85654 type: {
85655 valType: 'enumerated',
85656 values: ['radial', 'horizontal', 'vertical', 'none'],
85657 arrayOk: true,
85658 dflt: 'none',
85659
85660 editType: 'calc',
85661
85662 },
85663 color: {
85664 valType: 'color',
85665 arrayOk: true,
85666
85667 editType: 'calc',
85668
85669 },
85670 editType: 'calc'
85671 },
85672 editType: 'calc'
85673 },
85674 colorScaleAttrs('marker', {anim: true})
85675 ),
85676 selected: {
85677 marker: {
85678 opacity: {
85679 valType: 'number',
85680 min: 0,
85681 max: 1,
85682
85683 editType: 'style',
85684
85685 },
85686 color: {
85687 valType: 'color',
85688
85689 editType: 'style',
85690
85691 },
85692 size: {
85693 valType: 'number',
85694 min: 0,
85695
85696 editType: 'style',
85697
85698 },
85699 editType: 'style'
85700 },
85701 textfont: {
85702 color: {
85703 valType: 'color',
85704
85705 editType: 'style',
85706
85707 },
85708 editType: 'style'
85709 },
85710 editType: 'style'
85711 },
85712 unselected: {
85713 marker: {
85714 opacity: {
85715 valType: 'number',
85716 min: 0,
85717 max: 1,
85718
85719 editType: 'style',
85720
85721 },
85722 color: {
85723 valType: 'color',
85724
85725 editType: 'style',
85726
85727 },
85728 size: {
85729 valType: 'number',
85730 min: 0,
85731
85732 editType: 'style',
85733
85734 },
85735 editType: 'style'
85736 },
85737 textfont: {
85738 color: {
85739 valType: 'color',
85740
85741 editType: 'style',
85742
85743 },
85744 editType: 'style'
85745 },
85746 editType: 'style'
85747 },
85748
85749 textposition: {
85750 valType: 'enumerated',
85751 values: [
85752 'top left', 'top center', 'top right',
85753 'middle left', 'middle center', 'middle right',
85754 'bottom left', 'bottom center', 'bottom right'
85755 ],
85756 dflt: 'middle center',
85757 arrayOk: true,
85758
85759 editType: 'calc',
85760
85761 },
85762 textfont: fontAttrs({
85763 editType: 'calc',
85764 colorEditType: 'style',
85765 arrayOk: true,
85766
85767 }),
85768
85769 r: {
85770 valType: 'data_array',
85771 editType: 'calc',
85772
85773 },
85774 t: {
85775 valType: 'data_array',
85776 editType: 'calc',
85777
85778 }
85779};
85780
85781},{"../../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){
85782/**
85783* Copyright 2012-2020, Plotly, Inc.
85784* All rights reserved.
85785*
85786* This source code is licensed under the MIT license found in the
85787* LICENSE file in the root directory of this source tree.
85788*/
85789
85790'use strict';
85791
85792var isNumeric = _dereq_('fast-isnumeric');
85793var Lib = _dereq_('../../lib');
85794
85795var Axes = _dereq_('../../plots/cartesian/axes');
85796var BADNUM = _dereq_('../../constants/numerical').BADNUM;
85797
85798var subTypes = _dereq_('./subtypes');
85799var calcColorscale = _dereq_('./colorscale_calc');
85800var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
85801var calcSelection = _dereq_('./calc_selection');
85802
85803function calc(gd, trace) {
85804 var fullLayout = gd._fullLayout;
85805 var xa = Axes.getFromId(gd, trace.xaxis || 'x');
85806 var ya = Axes.getFromId(gd, trace.yaxis || 'y');
85807 var x = xa.makeCalcdata(trace, 'x');
85808 var y = ya.makeCalcdata(trace, 'y');
85809 var serieslen = trace._length;
85810 var cd = new Array(serieslen);
85811 var ids = trace.ids;
85812 var stackGroupOpts = getStackOpts(trace, fullLayout, xa, ya);
85813 var interpolateGaps = false;
85814 var isV, i, j, k, interpolate, vali;
85815
85816 setFirstScatter(fullLayout, trace);
85817
85818 var xAttr = 'x';
85819 var yAttr = 'y';
85820 var posAttr;
85821 if(stackGroupOpts) {
85822 Lib.pushUnique(stackGroupOpts.traceIndices, trace._expandedIndex);
85823 isV = stackGroupOpts.orientation === 'v';
85824
85825 // size, like we use for bar
85826 if(isV) {
85827 yAttr = 's';
85828 posAttr = 'x';
85829 } else {
85830 xAttr = 's';
85831 posAttr = 'y';
85832 }
85833 interpolate = stackGroupOpts.stackgaps === 'interpolate';
85834 } else {
85835 var ppad = calcMarkerSize(trace, serieslen);
85836 calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
85837 }
85838
85839 for(i = 0; i < serieslen; i++) {
85840 var cdi = cd[i] = {};
85841 var xValid = isNumeric(x[i]);
85842 var yValid = isNumeric(y[i]);
85843 if(xValid && yValid) {
85844 cdi[xAttr] = x[i];
85845 cdi[yAttr] = y[i];
85846 } else if(stackGroupOpts && (isV ? xValid : yValid)) {
85847 // if we're stacking we need to hold on to all valid positions
85848 // even with invalid sizes
85849
85850 cdi[posAttr] = isV ? x[i] : y[i];
85851 cdi.gap = true;
85852 if(interpolate) {
85853 cdi.s = BADNUM;
85854 interpolateGaps = true;
85855 } else {
85856 cdi.s = 0;
85857 }
85858 } else {
85859 cdi[xAttr] = cdi[yAttr] = BADNUM;
85860 }
85861
85862 if(ids) {
85863 cdi.id = String(ids[i]);
85864 }
85865 }
85866
85867 arraysToCalcdata(cd, trace);
85868 calcColorscale(gd, trace);
85869 calcSelection(cd, trace);
85870
85871 if(stackGroupOpts) {
85872 // remove bad positions and sort
85873 // note that original indices get added to cd in arraysToCalcdata
85874 i = 0;
85875 while(i < cd.length) {
85876 if(cd[i][posAttr] === BADNUM) {
85877 cd.splice(i, 1);
85878 } else i++;
85879 }
85880
85881 Lib.sort(cd, function(a, b) {
85882 return (a[posAttr] - b[posAttr]) || (a.i - b.i);
85883 });
85884
85885 if(interpolateGaps) {
85886 // first fill the beginning with constant from the first point
85887 i = 0;
85888 while(i < cd.length - 1 && cd[i].gap) {
85889 i++;
85890 }
85891 vali = cd[i].s;
85892 if(!vali) vali = cd[i].s = 0; // in case of no data AT ALL in this trace - use 0
85893 for(j = 0; j < i; j++) {
85894 cd[j].s = vali;
85895 }
85896 // then fill the end with constant from the last point
85897 k = cd.length - 1;
85898 while(k > i && cd[k].gap) {
85899 k--;
85900 }
85901 vali = cd[k].s;
85902 for(j = cd.length - 1; j > k; j--) {
85903 cd[j].s = vali;
85904 }
85905 // now interpolate internal gaps linearly
85906 while(i < k) {
85907 i++;
85908 if(cd[i].gap) {
85909 j = i + 1;
85910 while(cd[j].gap) {
85911 j++;
85912 }
85913 var pos0 = cd[i - 1][posAttr];
85914 var size0 = cd[i - 1].s;
85915 var m = (cd[j].s - size0) / (cd[j][posAttr] - pos0);
85916 while(i < j) {
85917 cd[i].s = size0 + (cd[i][posAttr] - pos0) * m;
85918 i++;
85919 }
85920 }
85921 }
85922 }
85923 }
85924
85925 return cd;
85926}
85927
85928function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) {
85929 var serieslen = trace._length;
85930 var fullLayout = gd._fullLayout;
85931 var xId = xa._id;
85932 var yId = ya._id;
85933 var firstScatter = fullLayout._firstScatter[firstScatterGroup(trace)] === trace.uid;
85934 var stackOrientation = (getStackOpts(trace, fullLayout, xa, ya) || {}).orientation;
85935 var fill = trace.fill;
85936
85937 // cancel minimum tick spacings (only applies to bars and boxes)
85938 xa._minDtick = 0;
85939 ya._minDtick = 0;
85940
85941 // check whether bounds should be tight, padded, extended to zero...
85942 // most cases both should be padded on both ends, so start with that.
85943 var xOptions = {padded: true};
85944 var yOptions = {padded: true};
85945
85946 if(ppad) {
85947 xOptions.ppad = yOptions.ppad = ppad;
85948 }
85949
85950 // TODO: text size
85951
85952 var openEnded = serieslen < 2 || (x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]);
85953
85954 if(openEnded && (
85955 (fill === 'tozerox') ||
85956 ((fill === 'tonextx') && (firstScatter || stackOrientation === 'h'))
85957 )) {
85958 // include zero (tight) and extremes (padded) if fill to zero
85959 // (unless the shape is closed, then it's just filling the shape regardless)
85960
85961 xOptions.tozero = true;
85962 } else if(!(trace.error_y || {}).visible && (
85963 // if no error bars, markers or text, or fill to y=0 remove x padding
85964
85965 (fill === 'tonexty' || fill === 'tozeroy') ||
85966 (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace))
85967 )) {
85968 xOptions.padded = false;
85969 xOptions.ppad = 0;
85970 }
85971
85972 if(openEnded && (
85973 (fill === 'tozeroy') ||
85974 ((fill === 'tonexty') && (firstScatter || stackOrientation === 'v'))
85975 )) {
85976 // now check for y - rather different logic, though still mostly padded both ends
85977 // include zero (tight) and extremes (padded) if fill to zero
85978 // (unless the shape is closed, then it's just filling the shape regardless)
85979
85980 yOptions.tozero = true;
85981 } else if(fill === 'tonextx' || fill === 'tozerox') {
85982 // tight y: any x fill
85983
85984 yOptions.padded = false;
85985 }
85986
85987 // N.B. asymmetric splom traces call this with blank {} xa or ya
85988 if(xId) trace._extremes[xId] = Axes.findExtremes(xa, x, xOptions);
85989 if(yId) trace._extremes[yId] = Axes.findExtremes(ya, y, yOptions);
85990}
85991
85992function calcMarkerSize(trace, serieslen) {
85993 if(!subTypes.hasMarkers(trace)) return;
85994
85995 // Treat size like x or y arrays --- Run d2c
85996 // this needs to go before ppad computation
85997 var marker = trace.marker;
85998 var sizeref = 1.6 * (trace.marker.sizeref || 1);
85999 var markerTrans;
86000
86001 if(trace.marker.sizemode === 'area') {
86002 markerTrans = function(v) {
86003 return Math.max(Math.sqrt((v || 0) / sizeref), 3);
86004 };
86005 } else {
86006 markerTrans = function(v) {
86007 return Math.max((v || 0) / sizeref, 3);
86008 };
86009 }
86010
86011 if(Lib.isArrayOrTypedArray(marker.size)) {
86012 // I tried auto-type but category and dates dont make much sense.
86013 var ax = {type: 'linear'};
86014 Axes.setConvert(ax);
86015
86016 var s = ax.makeCalcdata(trace.marker, 'size');
86017
86018 var sizeOut = new Array(serieslen);
86019 for(var i = 0; i < serieslen; i++) {
86020 sizeOut[i] = markerTrans(s[i]);
86021 }
86022 return sizeOut;
86023 } else {
86024 return markerTrans(marker.size);
86025 }
86026}
86027
86028/**
86029 * mark the first scatter trace for each subplot
86030 * note that scatter and scattergl each get their own first trace
86031 * note also that I'm doing this during calc rather than supplyDefaults
86032 * so I don't need to worry about transforms, but if we ever do
86033 * per-trace calc this will get confused.
86034 */
86035function setFirstScatter(fullLayout, trace) {
86036 var group = firstScatterGroup(trace);
86037 var firstScatter = fullLayout._firstScatter;
86038 if(!firstScatter[group]) firstScatter[group] = trace.uid;
86039}
86040
86041function firstScatterGroup(trace) {
86042 var stackGroup = trace.stackgroup;
86043 return trace.xaxis + trace.yaxis + trace.type +
86044 (stackGroup ? '-' + stackGroup : '');
86045}
86046
86047function getStackOpts(trace, fullLayout, xa, ya) {
86048 var stackGroup = trace.stackgroup;
86049 if(!stackGroup) return;
86050 var stackOpts = fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup];
86051 var stackAx = stackOpts.orientation === 'v' ? ya : xa;
86052 // Allow stacking only on numeric axes
86053 // calc is a little late to be figuring this out, but during supplyDefaults
86054 // we don't know the axis type yet
86055 if(stackAx.type === 'linear' || stackAx.type === 'log') return stackOpts;
86056}
86057
86058module.exports = {
86059 calc: calc,
86060 calcMarkerSize: calcMarkerSize,
86061 calcAxisExpansion: calcAxisExpansion,
86062 setFirstScatter: setFirstScatter,
86063 getStackOpts: getStackOpts
86064};
86065
86066},{"../../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){
86067/**
86068* Copyright 2012-2020, Plotly, Inc.
86069* All rights reserved.
86070*
86071* This source code is licensed under the MIT license found in the
86072* LICENSE file in the root directory of this source tree.
86073*/
86074
86075'use strict';
86076
86077var Lib = _dereq_('../../lib');
86078
86079module.exports = function calcSelection(cd, trace) {
86080 if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
86081 Lib.tagSelected(cd, trace);
86082 }
86083};
86084
86085},{"../../lib":178}],392:[function(_dereq_,module,exports){
86086/**
86087* Copyright 2012-2020, Plotly, Inc.
86088* All rights reserved.
86089*
86090* This source code is licensed under the MIT license found in the
86091* LICENSE file in the root directory of this source tree.
86092*/
86093
86094'use strict';
86095
86096var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
86097var calcColorscale = _dereq_('../../components/colorscale/calc');
86098
86099var subTypes = _dereq_('./subtypes');
86100
86101module.exports = function calcMarkerColorscale(gd, trace) {
86102 if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) {
86103 calcColorscale(gd, trace, {
86104 vals: trace.line.color,
86105 containerStr: 'line',
86106 cLetter: 'c'
86107 });
86108 }
86109
86110 if(subTypes.hasMarkers(trace)) {
86111 if(hasColorscale(trace, 'marker')) {
86112 calcColorscale(gd, trace, {
86113 vals: trace.marker.color,
86114 containerStr: 'marker',
86115 cLetter: 'c'
86116 });
86117 }
86118 if(hasColorscale(trace, 'marker.line')) {
86119 calcColorscale(gd, trace, {
86120 vals: trace.marker.line.color,
86121 containerStr: 'marker.line',
86122 cLetter: 'c'
86123 });
86124 }
86125 }
86126};
86127
86128},{"../../components/colorscale/calc":60,"../../components/colorscale/helpers":63,"./subtypes":413}],393:[function(_dereq_,module,exports){
86129/**
86130* Copyright 2012-2020, Plotly, Inc.
86131* All rights reserved.
86132*
86133* This source code is licensed under the MIT license found in the
86134* LICENSE file in the root directory of this source tree.
86135*/
86136
86137
86138'use strict';
86139
86140module.exports = {
86141 PTS_LINESONLY: 20,
86142
86143 // fixed parameters of clustering and clipping algorithms
86144
86145 // fraction of clustering tolerance "so close we don't even consider it a new point"
86146 minTolerance: 0.2,
86147 // how fast does clustering tolerance increase as you get away from the visible region
86148 toleranceGrowth: 10,
86149
86150 // number of viewport sizes away from the visible region
86151 // at which we clip all lines to the perimeter
86152 maxScreensAway: 20,
86153
86154 eventDataKeys: []
86155};
86156
86157},{}],394:[function(_dereq_,module,exports){
86158/**
86159* Copyright 2012-2020, Plotly, Inc.
86160* All rights reserved.
86161*
86162* This source code is licensed under the MIT license found in the
86163* LICENSE file in the root directory of this source tree.
86164*/
86165
86166
86167'use strict';
86168
86169var calc = _dereq_('./calc');
86170
86171/*
86172 * Scatter stacking & normalization calculations
86173 * runs per subplot, and can handle multiple stacking groups
86174 */
86175
86176module.exports = function crossTraceCalc(gd, plotinfo) {
86177 var xa = plotinfo.xaxis;
86178 var ya = plotinfo.yaxis;
86179 var subplot = xa._id + ya._id;
86180
86181 var subplotStackOpts = gd._fullLayout._scatterStackOpts[subplot];
86182 if(!subplotStackOpts) return;
86183
86184 var calcTraces = gd.calcdata;
86185
86186 var i, j, k, i2, cd, cd0, posj, sumj, norm;
86187 var groupOpts, interpolate, groupnorm, posAttr, valAttr;
86188 var hasAnyBlanks;
86189
86190 for(var stackGroup in subplotStackOpts) {
86191 groupOpts = subplotStackOpts[stackGroup];
86192 var indices = groupOpts.traceIndices;
86193
86194 // can get here with no indices if the stack axis is non-numeric
86195 if(!indices.length) continue;
86196
86197 interpolate = groupOpts.stackgaps === 'interpolate';
86198 groupnorm = groupOpts.groupnorm;
86199 if(groupOpts.orientation === 'v') {
86200 posAttr = 'x';
86201 valAttr = 'y';
86202 } else {
86203 posAttr = 'y';
86204 valAttr = 'x';
86205 }
86206 hasAnyBlanks = new Array(indices.length);
86207 for(i = 0; i < hasAnyBlanks.length; i++) {
86208 hasAnyBlanks[i] = false;
86209 }
86210
86211 // Collect the complete set of all positions across ALL traces.
86212 // Start with the first trace, then interleave items from later traces
86213 // as needed.
86214 // Fill in mising items as we go.
86215 cd0 = calcTraces[indices[0]];
86216 var allPositions = new Array(cd0.length);
86217 for(i = 0; i < cd0.length; i++) {
86218 allPositions[i] = cd0[i][posAttr];
86219 }
86220
86221 for(i = 1; i < indices.length; i++) {
86222 cd = calcTraces[indices[i]];
86223
86224 for(j = k = 0; j < cd.length; j++) {
86225 posj = cd[j][posAttr];
86226 for(; posj > allPositions[k] && k < allPositions.length; k++) {
86227 // the current trace is missing a position from some previous trace(s)
86228 insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
86229 j++;
86230 }
86231 if(posj !== allPositions[k]) {
86232 // previous trace(s) are missing a position from the current trace
86233 for(i2 = 0; i2 < i; i2++) {
86234 insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr);
86235 }
86236 allPositions.splice(k, 0, posj);
86237 }
86238 k++;
86239 }
86240 for(; k < allPositions.length; k++) {
86241 insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
86242 j++;
86243 }
86244 }
86245
86246 var serieslen = allPositions.length;
86247
86248 // stack (and normalize)!
86249 for(j = 0; j < cd0.length; j++) {
86250 sumj = cd0[j][valAttr] = cd0[j].s;
86251 for(i = 1; i < indices.length; i++) {
86252 cd = calcTraces[indices[i]];
86253 cd[0].trace._rawLength = cd[0].trace._length;
86254 cd[0].trace._length = serieslen;
86255 sumj += cd[j].s;
86256 cd[j][valAttr] = sumj;
86257 }
86258
86259 if(groupnorm) {
86260 norm = ((groupnorm === 'fraction') ? sumj : (sumj / 100)) || 1;
86261 for(i = 0; i < indices.length; i++) {
86262 var cdj = calcTraces[indices[i]][j];
86263 cdj[valAttr] /= norm;
86264 cdj.sNorm = cdj.s / norm;
86265 }
86266 }
86267 }
86268
86269 // autorange
86270 for(i = 0; i < indices.length; i++) {
86271 cd = calcTraces[indices[i]];
86272 var trace = cd[0].trace;
86273 var ppad = calc.calcMarkerSize(trace, trace._rawLength);
86274 var arrayPad = Array.isArray(ppad);
86275 if((ppad && hasAnyBlanks[i]) || arrayPad) {
86276 var ppadRaw = ppad;
86277 ppad = new Array(serieslen);
86278 for(j = 0; j < serieslen; j++) {
86279 ppad[j] = cd[j].gap ? 0 : (arrayPad ? ppadRaw[cd[j].i] : ppadRaw);
86280 }
86281 }
86282 var x = new Array(serieslen);
86283 var y = new Array(serieslen);
86284 for(j = 0; j < serieslen; j++) {
86285 x[j] = cd[j].x;
86286 y[j] = cd[j].y;
86287 }
86288 calc.calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
86289
86290 // while we're here (in a loop over all traces in the stack)
86291 // record the orientation, so hover can find it easily
86292 cd[0].t.orientation = groupOpts.orientation;
86293 }
86294 }
86295};
86296
86297function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) {
86298 hasAnyBlanks[traceIndex] = true;
86299 var newEntry = {
86300 i: null,
86301 gap: true,
86302 s: 0
86303 };
86304 newEntry[posAttr] = position;
86305 calcTrace.splice(index, 0, newEntry);
86306 // Even if we're not interpolating, if one trace has multiple
86307 // values at the same position and this trace only has one value there,
86308 // we just duplicate that one value rather than insert a zero.
86309 // We also make it look like a real point - because it's ambiguous which
86310 // one really is the real one!
86311 if(index && position === calcTrace[index - 1][posAttr]) {
86312 var prevEntry = calcTrace[index - 1];
86313 newEntry.s = prevEntry.s;
86314 // TODO is it going to cause any problems to have multiple
86315 // calcdata points with the same index?
86316 newEntry.i = prevEntry.i;
86317 newEntry.gap = prevEntry.gap;
86318 } else if(interpolate) {
86319 newEntry.s = getInterp(calcTrace, index, position, posAttr);
86320 }
86321 if(!index) {
86322 // t and trace need to stay on the first cd entry
86323 calcTrace[0].t = calcTrace[1].t;
86324 calcTrace[0].trace = calcTrace[1].trace;
86325 delete calcTrace[1].t;
86326 delete calcTrace[1].trace;
86327 }
86328}
86329
86330function getInterp(calcTrace, index, position, posAttr) {
86331 var pt0 = calcTrace[index - 1];
86332 var pt1 = calcTrace[index + 1];
86333 if(!pt1) return pt0.s;
86334 if(!pt0) return pt1.s;
86335 return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]);
86336}
86337
86338},{"./calc":390}],395:[function(_dereq_,module,exports){
86339/**
86340* Copyright 2012-2020, Plotly, Inc.
86341* All rights reserved.
86342*
86343* This source code is licensed under the MIT license found in the
86344* LICENSE file in the root directory of this source tree.
86345*/
86346
86347
86348'use strict';
86349
86350
86351// remove opacity for any trace that has a fill or is filled to
86352module.exports = function crossTraceDefaults(fullData) {
86353 for(var i = 0; i < fullData.length; i++) {
86354 var tracei = fullData[i];
86355 if(tracei.type !== 'scatter') continue;
86356
86357 var filli = tracei.fill;
86358 if(filli === 'none' || filli === 'toself') continue;
86359
86360 tracei.opacity = undefined;
86361
86362 if(filli === 'tonexty' || filli === 'tonextx') {
86363 for(var j = i - 1; j >= 0; j--) {
86364 var tracej = fullData[j];
86365
86366 if((tracej.type === 'scatter') &&
86367 (tracej.xaxis === tracei.xaxis) &&
86368 (tracej.yaxis === tracei.yaxis)) {
86369 tracej.opacity = undefined;
86370 break;
86371 }
86372 }
86373 }
86374 }
86375};
86376
86377},{}],396:[function(_dereq_,module,exports){
86378/**
86379* Copyright 2012-2020, Plotly, Inc.
86380* All rights reserved.
86381*
86382* This source code is licensed under the MIT license found in the
86383* LICENSE file in the root directory of this source tree.
86384*/
86385
86386'use strict';
86387
86388var Lib = _dereq_('../../lib');
86389var Registry = _dereq_('../../registry');
86390
86391var attributes = _dereq_('./attributes');
86392var constants = _dereq_('./constants');
86393var subTypes = _dereq_('./subtypes');
86394var handleXYDefaults = _dereq_('./xy_defaults');
86395var handleStackDefaults = _dereq_('./stack_defaults');
86396var handleMarkerDefaults = _dereq_('./marker_defaults');
86397var handleLineDefaults = _dereq_('./line_defaults');
86398var handleLineShapeDefaults = _dereq_('./line_shape_defaults');
86399var handleTextDefaults = _dereq_('./text_defaults');
86400var handleFillColorDefaults = _dereq_('./fillcolor_defaults');
86401
86402module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
86403 function coerce(attr, dflt) {
86404 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
86405 }
86406
86407 var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
86408 if(!len) traceOut.visible = false;
86409
86410 if(!traceOut.visible) return;
86411
86412 var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce);
86413
86414 var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ?
86415 'lines+markers' : 'lines';
86416 coerce('text');
86417 coerce('hovertext');
86418 coerce('mode', defaultMode);
86419
86420 if(subTypes.hasLines(traceOut)) {
86421 handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
86422 handleLineShapeDefaults(traceIn, traceOut, coerce);
86423 coerce('connectgaps');
86424 coerce('line.simplify');
86425 }
86426
86427 if(subTypes.hasMarkers(traceOut)) {
86428 handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
86429 }
86430
86431 if(subTypes.hasText(traceOut)) {
86432 coerce('texttemplate');
86433 handleTextDefaults(traceIn, traceOut, layout, coerce);
86434 }
86435
86436 var dfltHoverOn = [];
86437
86438 if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
86439 coerce('cliponaxis');
86440 coerce('marker.maxdisplayed');
86441 dfltHoverOn.push('points');
86442 }
86443
86444 // It's possible for this default to be changed by a later trace.
86445 // We handle that case in some hacky code inside handleStackDefaults.
86446 coerce('fill', stackGroupOpts ? stackGroupOpts.fillDflt : 'none');
86447 if(traceOut.fill !== 'none') {
86448 handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
86449 if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
86450 }
86451
86452 var lineColor = (traceOut.line || {}).color;
86453 var markerColor = (traceOut.marker || {}).color;
86454
86455 if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
86456 dfltHoverOn.push('fills');
86457 }
86458 coerce('hoveron', dfltHoverOn.join('+') || 'points');
86459 if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
86460 var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
86461 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'});
86462 errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'});
86463
86464 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
86465};
86466
86467},{"../../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){
86468/**
86469* Copyright 2012-2020, Plotly, Inc.
86470* All rights reserved.
86471*
86472* This source code is licensed under the MIT license found in the
86473* LICENSE file in the root directory of this source tree.
86474*/
86475
86476
86477'use strict';
86478
86479var Color = _dereq_('../../components/color');
86480var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
86481
86482module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) {
86483 var inheritColorFromMarker = false;
86484
86485 if(traceOut.marker) {
86486 // don't try to inherit a color array
86487 var markerColor = traceOut.marker.color;
86488 var markerLineColor = (traceOut.marker.line || {}).color;
86489
86490 if(markerColor && !isArrayOrTypedArray(markerColor)) {
86491 inheritColorFromMarker = markerColor;
86492 } else if(markerLineColor && !isArrayOrTypedArray(markerLineColor)) {
86493 inheritColorFromMarker = markerLineColor;
86494 }
86495 }
86496
86497 coerce('fillcolor', Color.addOpacity(
86498 (traceOut.line || {}).color ||
86499 inheritColorFromMarker ||
86500 defaultColor, 0.5
86501 ));
86502};
86503
86504},{"../../components/color":52,"../../lib":178}],398:[function(_dereq_,module,exports){
86505/**
86506* Copyright 2012-2020, Plotly, Inc.
86507* All rights reserved.
86508*
86509* This source code is licensed under the MIT license found in the
86510* LICENSE file in the root directory of this source tree.
86511*/
86512
86513'use strict';
86514
86515var Axes = _dereq_('../../plots/cartesian/axes');
86516
86517module.exports = function formatLabels(cdi, trace, fullLayout) {
86518 var labels = {};
86519
86520 var mockGd = {_fullLayout: fullLayout};
86521 var xa = Axes.getFromTrace(mockGd, trace, 'x');
86522 var ya = Axes.getFromTrace(mockGd, trace, 'y');
86523
86524 labels.xLabel = Axes.tickText(xa, cdi.x, true).text;
86525 labels.yLabel = Axes.tickText(ya, cdi.y, true).text;
86526
86527 return labels;
86528};
86529
86530},{"../../plots/cartesian/axes":222}],399:[function(_dereq_,module,exports){
86531/**
86532* Copyright 2012-2020, Plotly, Inc.
86533* All rights reserved.
86534*
86535* This source code is licensed under the MIT license found in the
86536* LICENSE file in the root directory of this source tree.
86537*/
86538
86539
86540'use strict';
86541
86542var Color = _dereq_('../../components/color');
86543var subtypes = _dereq_('./subtypes');
86544
86545
86546module.exports = function getTraceColor(trace, di) {
86547 var lc, tc;
86548
86549 // TODO: text modes
86550
86551 if(trace.mode === 'lines') {
86552 lc = trace.line.color;
86553 return (lc && Color.opacity(lc)) ?
86554 lc : trace.fillcolor;
86555 } else if(trace.mode === 'none') {
86556 return trace.fill ? trace.fillcolor : '';
86557 } else {
86558 var mc = di.mcc || (trace.marker || {}).color;
86559 var mlc = di.mlcc || ((trace.marker || {}).line || {}).color;
86560
86561 tc = (mc && Color.opacity(mc)) ? mc :
86562 (mlc && Color.opacity(mlc) &&
86563 (di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : '';
86564
86565 if(tc) {
86566 // make sure the points aren't TOO transparent
86567 if(Color.opacity(tc) < 0.3) {
86568 return Color.addOpacity(tc, 0.3);
86569 } else return tc;
86570 } else {
86571 lc = (trace.line || {}).color;
86572 return (lc && Color.opacity(lc) &&
86573 subtypes.hasLines(trace) && trace.line.width) ?
86574 lc : trace.fillcolor;
86575 }
86576 }
86577};
86578
86579},{"../../components/color":52,"./subtypes":413}],400:[function(_dereq_,module,exports){
86580/**
86581* Copyright 2012-2020, Plotly, Inc.
86582* All rights reserved.
86583*
86584* This source code is licensed under the MIT license found in the
86585* LICENSE file in the root directory of this source tree.
86586*/
86587
86588'use strict';
86589
86590var Lib = _dereq_('../../lib');
86591var Fx = _dereq_('../../components/fx');
86592var Registry = _dereq_('../../registry');
86593var getTraceColor = _dereq_('./get_trace_color');
86594var Color = _dereq_('../../components/color');
86595var fillText = Lib.fillText;
86596
86597module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
86598 var cd = pointData.cd;
86599 var trace = cd[0].trace;
86600 var xa = pointData.xa;
86601 var ya = pointData.ya;
86602 var xpx = xa.c2p(xval);
86603 var ypx = ya.c2p(yval);
86604 var pt = [xpx, ypx];
86605 var hoveron = trace.hoveron || '';
86606 var minRad = (trace.mode.indexOf('markers') !== -1) ? 3 : 0.5;
86607
86608 // look for points to hover on first, then take fills only if we
86609 // didn't find a point
86610 if(hoveron.indexOf('points') !== -1) {
86611 var dx = function(di) {
86612 // dx and dy are used in compare modes - here we want to always
86613 // prioritize the closest data point, at least as long as markers are
86614 // the same size or nonexistent, but still try to prioritize small markers too.
86615 var rad = Math.max(3, di.mrc || 0);
86616 var kink = 1 - 1 / rad;
86617 var dxRaw = Math.abs(xa.c2p(di.x) - xpx);
86618 var d = (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink);
86619 return d;
86620 };
86621 var dy = function(di) {
86622 var rad = Math.max(3, di.mrc || 0);
86623 var kink = 1 - 1 / rad;
86624 var dyRaw = Math.abs(ya.c2p(di.y) - ypx);
86625 return (dyRaw < rad) ? (kink * dyRaw / rad) : (dyRaw - rad + kink);
86626 };
86627 var dxy = function(di) {
86628 // scatter points: d.mrc is the calculated marker radius
86629 // adjust the distance so if you're inside the marker it
86630 // always will show up regardless of point size, but
86631 // prioritize smaller points
86632 var rad = Math.max(minRad, di.mrc || 0);
86633 var dx = xa.c2p(di.x) - xpx;
86634 var dy = ya.c2p(di.y) - ypx;
86635 return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - minRad / rad);
86636 };
86637 var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
86638
86639 Fx.getClosest(cd, distfn, pointData);
86640
86641 // skip the rest (for this trace) if we didn't find a close point
86642 if(pointData.index !== false) {
86643 // the closest data point
86644 var di = cd[pointData.index];
86645 var xc = xa.c2p(di.x, true);
86646 var yc = ya.c2p(di.y, true);
86647 var rad = di.mrc || 1;
86648
86649 // now we're done using the whole `calcdata` array, replace the
86650 // index with the original index (in case of inserted point from
86651 // stacked area)
86652 pointData.index = di.i;
86653
86654 var orientation = cd[0].t.orientation;
86655 // TODO: for scatter and bar, option to show (sub)totals and
86656 // raw data? Currently stacked and/or normalized bars just show
86657 // the normalized individual sizes, so that's what I'm doing here
86658 // for now.
86659 var sizeVal = orientation && (di.sNorm || di.s);
86660 var xLabelVal = (orientation === 'h') ? sizeVal : di.x;
86661 var yLabelVal = (orientation === 'v') ? sizeVal : di.y;
86662
86663 Lib.extendFlat(pointData, {
86664 color: getTraceColor(trace, di),
86665
86666 x0: xc - rad,
86667 x1: xc + rad,
86668 xLabelVal: xLabelVal,
86669
86670 y0: yc - rad,
86671 y1: yc + rad,
86672 yLabelVal: yLabelVal,
86673
86674 spikeDistance: dxy(di),
86675 hovertemplate: trace.hovertemplate
86676 });
86677
86678 fillText(di, trace, pointData);
86679 Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData);
86680
86681 return [pointData];
86682 }
86683 }
86684
86685 // even if hoveron is 'fills', only use it if we have polygons too
86686 if(hoveron.indexOf('fills') !== -1 && trace._polygons) {
86687 var polygons = trace._polygons;
86688 var polygonsIn = [];
86689 var inside = false;
86690 var xmin = Infinity;
86691 var xmax = -Infinity;
86692 var ymin = Infinity;
86693 var ymax = -Infinity;
86694
86695 var i, j, polygon, pts, xCross, x0, x1, y0, y1;
86696
86697 for(i = 0; i < polygons.length; i++) {
86698 polygon = polygons[i];
86699 // TODO: this is not going to work right for curved edges, it will
86700 // act as though they're straight. That's probably going to need
86701 // the elements themselves to capture the events. Worth it?
86702 if(polygon.contains(pt)) {
86703 inside = !inside;
86704 // TODO: need better than just the overall bounding box
86705 polygonsIn.push(polygon);
86706 ymin = Math.min(ymin, polygon.ymin);
86707 ymax = Math.max(ymax, polygon.ymax);
86708 }
86709 }
86710
86711 if(inside) {
86712 // constrain ymin/max to the visible plot, so the label goes
86713 // at the middle of the piece you can see
86714 ymin = Math.max(ymin, 0);
86715 ymax = Math.min(ymax, ya._length);
86716
86717 // find the overall left-most and right-most points of the
86718 // polygon(s) we're inside at their combined vertical midpoint.
86719 // This is where we will draw the hover label.
86720 // Note that this might not be the vertical midpoint of the
86721 // whole trace, if it's disjoint.
86722 var yAvg = (ymin + ymax) / 2;
86723 for(i = 0; i < polygonsIn.length; i++) {
86724 pts = polygonsIn[i].pts;
86725 for(j = 1; j < pts.length; j++) {
86726 y0 = pts[j - 1][1];
86727 y1 = pts[j][1];
86728 if((y0 > yAvg) !== (y1 >= yAvg)) {
86729 x0 = pts[j - 1][0];
86730 x1 = pts[j][0];
86731 if(y1 - y0) {
86732 xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0);
86733 xmin = Math.min(xmin, xCross);
86734 xmax = Math.max(xmax, xCross);
86735 }
86736 }
86737 }
86738 }
86739
86740 // constrain xmin/max to the visible plot now too
86741 xmin = Math.max(xmin, 0);
86742 xmax = Math.min(xmax, xa._length);
86743
86744 // get only fill or line color for the hover color
86745 var color = Color.defaultLine;
86746 if(Color.opacity(trace.fillcolor)) color = trace.fillcolor;
86747 else if(Color.opacity((trace.line || {}).color)) {
86748 color = trace.line.color;
86749 }
86750
86751 Lib.extendFlat(pointData, {
86752 // never let a 2D override 1D type as closest point
86753 // also: no spikeDistance, it's not allowed for fills
86754 distance: pointData.maxHoverDistance,
86755 x0: xmin,
86756 x1: xmax,
86757 y0: yAvg,
86758 y1: yAvg,
86759 color: color,
86760 hovertemplate: false
86761 });
86762
86763 delete pointData.index;
86764
86765 if(trace.text && !Array.isArray(trace.text)) {
86766 pointData.text = String(trace.text);
86767 } else pointData.text = trace.name;
86768
86769 return [pointData];
86770 }
86771 }
86772};
86773
86774},{"../../components/color":52,"../../components/fx":92,"../../lib":178,"../../registry":269,"./get_trace_color":399}],401:[function(_dereq_,module,exports){
86775/**
86776* Copyright 2012-2020, Plotly, Inc.
86777* All rights reserved.
86778*
86779* This source code is licensed under the MIT license found in the
86780* LICENSE file in the root directory of this source tree.
86781*/
86782
86783'use strict';
86784
86785var subtypes = _dereq_('./subtypes');
86786
86787module.exports = {
86788 hasLines: subtypes.hasLines,
86789 hasMarkers: subtypes.hasMarkers,
86790 hasText: subtypes.hasText,
86791 isBubble: subtypes.isBubble,
86792
86793 attributes: _dereq_('./attributes'),
86794 supplyDefaults: _dereq_('./defaults'),
86795 crossTraceDefaults: _dereq_('./cross_trace_defaults'),
86796 calc: _dereq_('./calc').calc,
86797 crossTraceCalc: _dereq_('./cross_trace_calc'),
86798 arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
86799 plot: _dereq_('./plot'),
86800 colorbar: _dereq_('./marker_colorbar'),
86801 formatLabels: _dereq_('./format_labels'),
86802 style: _dereq_('./style').style,
86803 styleOnSelect: _dereq_('./style').styleOnSelect,
86804 hoverPoints: _dereq_('./hover'),
86805 selectPoints: _dereq_('./select'),
86806 animatable: true,
86807
86808 moduleType: 'trace',
86809 name: 'scatter',
86810 basePlotModule: _dereq_('../../plots/cartesian'),
86811 categories: [
86812 'cartesian', 'svg', 'symbols', 'errorBarsOK', 'showLegend', 'scatter-like',
86813 'zoomScale'
86814 ],
86815 meta: {
86816
86817 }
86818};
86819
86820},{"../../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){
86821/**
86822* Copyright 2012-2020, Plotly, Inc.
86823* All rights reserved.
86824*
86825* This source code is licensed under the MIT license found in the
86826* LICENSE file in the root directory of this source tree.
86827*/
86828
86829'use strict';
86830
86831var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
86832var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
86833var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
86834
86835module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
86836 var markerColor = (traceIn.marker || {}).color;
86837
86838 coerce('line.color', defaultColor);
86839
86840 if(hasColorscale(traceIn, 'line')) {
86841 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'});
86842 } else {
86843 var lineColorDflt = (isArrayOrTypedArray(markerColor) ? false : markerColor) || defaultColor;
86844 coerce('line.color', lineColorDflt);
86845 }
86846
86847 coerce('line.width');
86848 if(!(opts || {}).noDash) coerce('line.dash');
86849};
86850
86851},{"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63,"../../lib":178}],403:[function(_dereq_,module,exports){
86852/**
86853* Copyright 2012-2020, Plotly, Inc.
86854* All rights reserved.
86855*
86856* This source code is licensed under the MIT license found in the
86857* LICENSE file in the root directory of this source tree.
86858*/
86859
86860
86861'use strict';
86862
86863var numConstants = _dereq_('../../constants/numerical');
86864var BADNUM = numConstants.BADNUM;
86865var LOG_CLIP = numConstants.LOG_CLIP;
86866var LOG_CLIP_PLUS = LOG_CLIP + 0.5;
86867var LOG_CLIP_MINUS = LOG_CLIP - 0.5;
86868var Lib = _dereq_('../../lib');
86869var segmentsIntersect = Lib.segmentsIntersect;
86870var constrain = Lib.constrain;
86871var constants = _dereq_('./constants');
86872
86873
86874module.exports = function linePoints(d, opts) {
86875 var xa = opts.xaxis;
86876 var ya = opts.yaxis;
86877 var xLog = xa.type === 'log';
86878 var yLog = ya.type === 'log';
86879 var xLen = xa._length;
86880 var yLen = ya._length;
86881 var connectGaps = opts.connectGaps;
86882 var baseTolerance = opts.baseTolerance;
86883 var shape = opts.shape;
86884 var linear = shape === 'linear';
86885 var fill = opts.fill && opts.fill !== 'none';
86886 var segments = [];
86887 var minTolerance = constants.minTolerance;
86888 var len = d.length;
86889 var pts = new Array(len);
86890 var pti = 0;
86891
86892 var i;
86893
86894 // pt variables are pixel coordinates [x,y] of one point
86895 // these four are the outputs of clustering on a line
86896 var clusterStartPt, clusterEndPt, clusterHighPt, clusterLowPt;
86897
86898 // "this" is the next point we're considering adding to the cluster
86899 var thisPt;
86900
86901 // did we encounter the high point first, then a low point, or vice versa?
86902 var clusterHighFirst;
86903
86904 // the first two points in the cluster determine its unit vector
86905 // so the second is always in the "High" direction
86906 var clusterUnitVector;
86907
86908 // the pixel delta from clusterStartPt
86909 var thisVector;
86910
86911 // val variables are (signed) pixel distances along the cluster vector
86912 var clusterRefDist, clusterHighVal, clusterLowVal, thisVal;
86913
86914 // deviation variables are (signed) pixel distances normal to the cluster vector
86915 var clusterMinDeviation, clusterMaxDeviation, thisDeviation;
86916
86917 // turn one calcdata point into pixel coordinates
86918 function getPt(index) {
86919 var di = d[index];
86920 if(!di) return false;
86921 var x = opts.linearized ? xa.l2p(di.x) : xa.c2p(di.x);
86922 var y = opts.linearized ? ya.l2p(di.y) : ya.c2p(di.y);
86923
86924 // if non-positive log values, set them VERY far off-screen
86925 // so the line looks essentially straight from the previous point.
86926 if(x === BADNUM) {
86927 if(xLog) x = xa.c2p(di.x, true);
86928 if(x === BADNUM) return false;
86929 // If BOTH were bad log values, make the line follow a constant
86930 // exponent rather than a constant slope
86931 if(yLog && y === BADNUM) {
86932 x *= Math.abs(xa._m * yLen * (xa._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS) /
86933 (ya._m * xLen * (ya._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS)));
86934 }
86935 x *= 1000;
86936 }
86937 if(y === BADNUM) {
86938 if(yLog) y = ya.c2p(di.y, true);
86939 if(y === BADNUM) return false;
86940 y *= 1000;
86941 }
86942 return [x, y];
86943 }
86944
86945 function crossesViewport(xFrac0, yFrac0, xFrac1, yFrac1) {
86946 var dx = xFrac1 - xFrac0;
86947 var dy = yFrac1 - yFrac0;
86948 var dx0 = 0.5 - xFrac0;
86949 var dy0 = 0.5 - yFrac0;
86950 var norm2 = dx * dx + dy * dy;
86951 var dot = dx * dx0 + dy * dy0;
86952 if(dot > 0 && dot < norm2) {
86953 var cross = dx0 * dy - dy0 * dx;
86954 if(cross * cross < norm2) return true;
86955 }
86956 }
86957
86958 var latestXFrac, latestYFrac;
86959 // if we're off-screen, increase tolerance over baseTolerance
86960 function getTolerance(pt, nextPt) {
86961 var xFrac = pt[0] / xLen;
86962 var yFrac = pt[1] / yLen;
86963 var offScreenFraction = Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1);
86964 if(offScreenFraction && (latestXFrac !== undefined) &&
86965 crossesViewport(xFrac, yFrac, latestXFrac, latestYFrac)
86966 ) {
86967 offScreenFraction = 0;
86968 }
86969 if(offScreenFraction && nextPt &&
86970 crossesViewport(xFrac, yFrac, nextPt[0] / xLen, nextPt[1] / yLen)
86971 ) {
86972 offScreenFraction = 0;
86973 }
86974
86975 return (1 + constants.toleranceGrowth * offScreenFraction) * baseTolerance;
86976 }
86977
86978 function ptDist(pt1, pt2) {
86979 var dx = pt1[0] - pt2[0];
86980 var dy = pt1[1] - pt2[1];
86981 return Math.sqrt(dx * dx + dy * dy);
86982 }
86983
86984 // last bit of filtering: clip paths that are VERY far off-screen
86985 // so we don't get near the browser's hard limit (+/- 2^29 px in Chrome and FF)
86986
86987 var maxScreensAway = constants.maxScreensAway;
86988
86989 // find the intersections between the segment from pt1 to pt2
86990 // and the large rectangle maxScreensAway around the viewport
86991 // if one of pt1 and pt2 is inside and the other outside, there
86992 // will be only one intersection.
86993 // if both are outside there will be 0 or 2 intersections
86994 // (or 1 if it's right at a corner - we'll treat that like 0)
86995 // returns an array of intersection pts
86996 var xEdge0 = -xLen * maxScreensAway;
86997 var xEdge1 = xLen * (1 + maxScreensAway);
86998 var yEdge0 = -yLen * maxScreensAway;
86999 var yEdge1 = yLen * (1 + maxScreensAway);
87000 var edges = [
87001 [xEdge0, yEdge0, xEdge1, yEdge0],
87002 [xEdge1, yEdge0, xEdge1, yEdge1],
87003 [xEdge1, yEdge1, xEdge0, yEdge1],
87004 [xEdge0, yEdge1, xEdge0, yEdge0]
87005 ];
87006 var xEdge, yEdge, lastXEdge, lastYEdge, lastFarPt, edgePt;
87007
87008 // for linear line shape, edge intersections should be linearly interpolated
87009 // spline uses this too, which isn't precisely correct but is actually pretty
87010 // good, because Catmull-Rom weights far-away points less in creating the curvature
87011 function getLinearEdgeIntersections(pt1, pt2) {
87012 var out = [];
87013 var ptCount = 0;
87014 for(var i = 0; i < 4; i++) {
87015 var edge = edges[i];
87016 var ptInt = segmentsIntersect(
87017 pt1[0], pt1[1], pt2[0], pt2[1],
87018 edge[0], edge[1], edge[2], edge[3]
87019 );
87020 if(ptInt && (!ptCount ||
87021 Math.abs(ptInt.x - out[0][0]) > 1 ||
87022 Math.abs(ptInt.y - out[0][1]) > 1
87023 )) {
87024 ptInt = [ptInt.x, ptInt.y];
87025 // if we have 2 intersections, make sure the closest one to pt1 comes first
87026 if(ptCount && ptDist(ptInt, pt1) < ptDist(out[0], pt1)) out.unshift(ptInt);
87027 else out.push(ptInt);
87028 ptCount++;
87029 }
87030 }
87031 return out;
87032 }
87033
87034 function onlyConstrainedPoint(pt) {
87035 if(pt[0] < xEdge0 || pt[0] > xEdge1 || pt[1] < yEdge0 || pt[1] > yEdge1) {
87036 return [constrain(pt[0], xEdge0, xEdge1), constrain(pt[1], yEdge0, yEdge1)];
87037 }
87038 }
87039
87040 function sameEdge(pt1, pt2) {
87041 if(pt1[0] === pt2[0] && (pt1[0] === xEdge0 || pt1[0] === xEdge1)) return true;
87042 if(pt1[1] === pt2[1] && (pt1[1] === yEdge0 || pt1[1] === yEdge1)) return true;
87043 }
87044
87045 // for line shapes hv and vh, movement in the two dimensions is decoupled,
87046 // so all we need to do is constrain each dimension independently
87047 function getHVEdgeIntersections(pt1, pt2) {
87048 var out = [];
87049 var ptInt1 = onlyConstrainedPoint(pt1);
87050 var ptInt2 = onlyConstrainedPoint(pt2);
87051 if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
87052
87053 if(ptInt1) out.push(ptInt1);
87054 if(ptInt2) out.push(ptInt2);
87055 return out;
87056 }
87057
87058 // hvh and vhv we sometimes have to move one of the intersection points
87059 // out BEYOND the clipping rect, by a maximum of a factor of 2, so that
87060 // the midpoint line is drawn in the right place
87061 function getABAEdgeIntersections(dim, limit0, limit1) {
87062 return function(pt1, pt2) {
87063 var ptInt1 = onlyConstrainedPoint(pt1);
87064 var ptInt2 = onlyConstrainedPoint(pt2);
87065
87066 var out = [];
87067 if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
87068
87069 if(ptInt1) out.push(ptInt1);
87070 if(ptInt2) out.push(ptInt2);
87071
87072 var midShift = 2 * Lib.constrain((pt1[dim] + pt2[dim]) / 2, limit0, limit1) -
87073 ((ptInt1 || pt1)[dim] + (ptInt2 || pt2)[dim]);
87074 if(midShift) {
87075 var ptToAlter;
87076 if(ptInt1 && ptInt2) {
87077 ptToAlter = (midShift > 0 === ptInt1[dim] > ptInt2[dim]) ? ptInt1 : ptInt2;
87078 } else ptToAlter = ptInt1 || ptInt2;
87079
87080 ptToAlter[dim] += midShift;
87081 }
87082
87083 return out;
87084 };
87085 }
87086
87087 var getEdgeIntersections;
87088 if(shape === 'linear' || shape === 'spline') {
87089 getEdgeIntersections = getLinearEdgeIntersections;
87090 } else if(shape === 'hv' || shape === 'vh') {
87091 getEdgeIntersections = getHVEdgeIntersections;
87092 } else if(shape === 'hvh') getEdgeIntersections = getABAEdgeIntersections(0, xEdge0, xEdge1);
87093 else if(shape === 'vhv') getEdgeIntersections = getABAEdgeIntersections(1, yEdge0, yEdge1);
87094
87095 // a segment pt1->pt2 entirely outside the nearby region:
87096 // find the corner it gets closest to touching
87097 function getClosestCorner(pt1, pt2) {
87098 var dx = pt2[0] - pt1[0];
87099 var m = (pt2[1] - pt1[1]) / dx;
87100 var b = (pt1[1] * pt2[0] - pt2[1] * pt1[0]) / dx;
87101
87102 if(b > 0) return [m > 0 ? xEdge0 : xEdge1, yEdge1];
87103 else return [m > 0 ? xEdge1 : xEdge0, yEdge0];
87104 }
87105
87106 function updateEdge(pt) {
87107 var x = pt[0];
87108 var y = pt[1];
87109 var xSame = x === pts[pti - 1][0];
87110 var ySame = y === pts[pti - 1][1];
87111 // duplicate point?
87112 if(xSame && ySame) return;
87113 if(pti > 1) {
87114 // backtracking along an edge?
87115 var xSame2 = x === pts[pti - 2][0];
87116 var ySame2 = y === pts[pti - 2][1];
87117 if(xSame && (x === xEdge0 || x === xEdge1) && xSame2) {
87118 if(ySame2) pti--; // backtracking exactly - drop prev pt and don't add
87119 else pts[pti - 1] = pt; // not exact: replace the prev pt
87120 } else if(ySame && (y === yEdge0 || y === yEdge1) && ySame2) {
87121 if(xSame2) pti--;
87122 else pts[pti - 1] = pt;
87123 } else pts[pti++] = pt;
87124 } else pts[pti++] = pt;
87125 }
87126
87127 function updateEdgesForReentry(pt) {
87128 // if we're outside the nearby region and going back in,
87129 // we may need to loop around a corner point
87130 if(pts[pti - 1][0] !== pt[0] && pts[pti - 1][1] !== pt[1]) {
87131 updateEdge([lastXEdge, lastYEdge]);
87132 }
87133 updateEdge(pt);
87134 lastFarPt = null;
87135 lastXEdge = lastYEdge = 0;
87136 }
87137
87138 function addPt(pt) {
87139 latestXFrac = pt[0] / xLen;
87140 latestYFrac = pt[1] / yLen;
87141 // Are we more than maxScreensAway off-screen any direction?
87142 // if so, clip to this box, but in such a way that on-screen
87143 // drawing is unchanged
87144 xEdge = (pt[0] < xEdge0) ? xEdge0 : (pt[0] > xEdge1) ? xEdge1 : 0;
87145 yEdge = (pt[1] < yEdge0) ? yEdge0 : (pt[1] > yEdge1) ? yEdge1 : 0;
87146 if(xEdge || yEdge) {
87147 if(!pti) {
87148 // to get fills right - if first point is far, push it toward the
87149 // screen in whichever direction(s) are far
87150
87151 pts[pti++] = [xEdge || pt[0], yEdge || pt[1]];
87152 } else if(lastFarPt) {
87153 // both this point and the last are outside the nearby region
87154 // check if we're crossing the nearby region
87155 var intersections = getEdgeIntersections(lastFarPt, pt);
87156 if(intersections.length > 1) {
87157 updateEdgesForReentry(intersections[0]);
87158 pts[pti++] = intersections[1];
87159 }
87160 } else {
87161 // we're leaving the nearby region - add the point where we left it
87162
87163 edgePt = getEdgeIntersections(pts[pti - 1], pt)[0];
87164 pts[pti++] = edgePt;
87165 }
87166
87167 var lastPt = pts[pti - 1];
87168 if(xEdge && yEdge && (lastPt[0] !== xEdge || lastPt[1] !== yEdge)) {
87169 // we've gone out beyond a new corner: add the corner too
87170 // so that the next point will take the right winding
87171 if(lastFarPt) {
87172 if(lastXEdge !== xEdge && lastYEdge !== yEdge) {
87173 if(lastXEdge && lastYEdge) {
87174 // we've gone around to an opposite corner - we
87175 // need to add the correct extra corner
87176 // in order to get the right winding
87177 updateEdge(getClosestCorner(lastFarPt, pt));
87178 } else {
87179 // we're coming from a far edge - the extra corner
87180 // we need is determined uniquely by the sectors
87181 updateEdge([lastXEdge || xEdge, lastYEdge || yEdge]);
87182 }
87183 } else if(lastXEdge && lastYEdge) {
87184 updateEdge([lastXEdge, lastYEdge]);
87185 }
87186 }
87187 updateEdge([xEdge, yEdge]);
87188 } else if((lastXEdge - xEdge) && (lastYEdge - yEdge)) {
87189 // we're coming from an edge or far corner to an edge - again the
87190 // extra corner we need is uniquely determined by the sectors
87191 updateEdge([xEdge || lastXEdge, yEdge || lastYEdge]);
87192 }
87193 lastFarPt = pt;
87194 lastXEdge = xEdge;
87195 lastYEdge = yEdge;
87196 } else {
87197 if(lastFarPt) {
87198 // this point is in range but the previous wasn't: add its entry pt first
87199 updateEdgesForReentry(getEdgeIntersections(lastFarPt, pt)[0]);
87200 }
87201
87202 pts[pti++] = pt;
87203 }
87204 }
87205
87206 // loop over ALL points in this trace
87207 for(i = 0; i < len; i++) {
87208 clusterStartPt = getPt(i);
87209 if(!clusterStartPt) continue;
87210
87211 pti = 0;
87212 lastFarPt = null;
87213 addPt(clusterStartPt);
87214
87215 // loop over one segment of the trace
87216 for(i++; i < len; i++) {
87217 clusterHighPt = getPt(i);
87218 if(!clusterHighPt) {
87219 if(connectGaps) continue;
87220 else break;
87221 }
87222
87223 // can't decimate if nonlinear line shape
87224 // TODO: we *could* decimate [hv]{2,3} shapes if we restricted clusters to horz or vert again
87225 // but spline would be verrry awkward to decimate
87226 if(!linear || !opts.simplify) {
87227 addPt(clusterHighPt);
87228 continue;
87229 }
87230
87231 var nextPt = getPt(i + 1);
87232
87233 clusterRefDist = ptDist(clusterHighPt, clusterStartPt);
87234
87235 // #3147 - always include the very first and last points for fills
87236 if(!(fill && (pti === 0 || pti === len - 1)) &&
87237 clusterRefDist < getTolerance(clusterHighPt, nextPt) * minTolerance) continue;
87238
87239 clusterUnitVector = [
87240 (clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist,
87241 (clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist
87242 ];
87243
87244 clusterLowPt = clusterStartPt;
87245 clusterHighVal = clusterRefDist;
87246 clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0;
87247 clusterHighFirst = false;
87248 clusterEndPt = clusterHighPt;
87249
87250 // loop over one cluster of points that collapse onto one line
87251 for(i++; i < d.length; i++) {
87252 thisPt = nextPt;
87253 nextPt = getPt(i + 1);
87254 if(!thisPt) {
87255 if(connectGaps) continue;
87256 else break;
87257 }
87258 thisVector = [
87259 thisPt[0] - clusterStartPt[0],
87260 thisPt[1] - clusterStartPt[1]
87261 ];
87262 // cross product (or dot with normal to the cluster vector)
87263 thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0];
87264 clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation);
87265 clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation);
87266
87267 if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt, nextPt)) break;
87268
87269 clusterEndPt = thisPt;
87270 thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1];
87271
87272 if(thisVal > clusterHighVal) {
87273 clusterHighVal = thisVal;
87274 clusterHighPt = thisPt;
87275 clusterHighFirst = false;
87276 } else if(thisVal < clusterLowVal) {
87277 clusterLowVal = thisVal;
87278 clusterLowPt = thisPt;
87279 clusterHighFirst = true;
87280 }
87281 }
87282
87283 // insert this cluster into pts
87284 // we've already inserted the start pt, now check if we have high and low pts
87285 if(clusterHighFirst) {
87286 addPt(clusterHighPt);
87287 if(clusterEndPt !== clusterLowPt) addPt(clusterLowPt);
87288 } else {
87289 if(clusterLowPt !== clusterStartPt) addPt(clusterLowPt);
87290 if(clusterEndPt !== clusterHighPt) addPt(clusterHighPt);
87291 }
87292 // and finally insert the end pt
87293 addPt(clusterEndPt);
87294
87295 // have we reached the end of this segment?
87296 if(i >= d.length || !thisPt) break;
87297
87298 // otherwise we have an out-of-cluster point to insert as next clusterStartPt
87299 addPt(thisPt);
87300 clusterStartPt = thisPt;
87301 }
87302
87303 // to get fills right - repeat what we did at the start
87304 if(lastFarPt) updateEdge([lastXEdge || lastFarPt[0], lastYEdge || lastFarPt[1]]);
87305
87306 segments.push(pts.slice(0, pti));
87307 }
87308
87309 return segments;
87310};
87311
87312},{"../../constants/numerical":158,"../../lib":178,"./constants":393}],404:[function(_dereq_,module,exports){
87313/**
87314* Copyright 2012-2020, Plotly, Inc.
87315* All rights reserved.
87316*
87317* This source code is licensed under the MIT license found in the
87318* LICENSE file in the root directory of this source tree.
87319*/
87320
87321
87322'use strict';
87323
87324
87325// common to 'scatter' and 'scatterternary'
87326module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) {
87327 var shape = coerce('line.shape');
87328 if(shape === 'spline') coerce('line.smoothing');
87329};
87330
87331},{}],405:[function(_dereq_,module,exports){
87332/**
87333* Copyright 2012-2020, Plotly, Inc.
87334* All rights reserved.
87335*
87336* This source code is licensed under the MIT license found in the
87337* LICENSE file in the root directory of this source tree.
87338*/
87339
87340'use strict';
87341
87342var LINKEDFILLS = {tonextx: 1, tonexty: 1, tonext: 1};
87343
87344module.exports = function linkTraces(gd, plotinfo, cdscatter) {
87345 var trace, i, group, prevtrace, groupIndex;
87346
87347 // first sort traces to keep stacks & filled-together groups together
87348 var groupIndices = {};
87349 var needsSort = false;
87350 var prevGroupIndex = -1;
87351 var nextGroupIndex = 0;
87352 var prevUnstackedGroupIndex = -1;
87353 for(i = 0; i < cdscatter.length; i++) {
87354 trace = cdscatter[i][0].trace;
87355 group = trace.stackgroup || '';
87356 if(group) {
87357 if(group in groupIndices) {
87358 groupIndex = groupIndices[group];
87359 } else {
87360 groupIndex = groupIndices[group] = nextGroupIndex;
87361 nextGroupIndex++;
87362 }
87363 } else if(trace.fill in LINKEDFILLS && prevUnstackedGroupIndex >= 0) {
87364 groupIndex = prevUnstackedGroupIndex;
87365 } else {
87366 groupIndex = prevUnstackedGroupIndex = nextGroupIndex;
87367 nextGroupIndex++;
87368 }
87369
87370 if(groupIndex < prevGroupIndex) needsSort = true;
87371 trace._groupIndex = prevGroupIndex = groupIndex;
87372 }
87373
87374 var cdscatterSorted = cdscatter.slice();
87375 if(needsSort) {
87376 cdscatterSorted.sort(function(a, b) {
87377 var traceA = a[0].trace;
87378 var traceB = b[0].trace;
87379 return (traceA._groupIndex - traceB._groupIndex) ||
87380 (traceA.index - traceB.index);
87381 });
87382 }
87383
87384 // now link traces to each other
87385 var prevtraces = {};
87386 for(i = 0; i < cdscatterSorted.length; i++) {
87387 trace = cdscatterSorted[i][0].trace;
87388 group = trace.stackgroup || '';
87389
87390 // Note: The check which ensures all cdscatter here are for the same axis and
87391 // are either cartesian or scatterternary has been removed. This code assumes
87392 // the passed scattertraces have been filtered to the proper plot types and
87393 // the proper subplots.
87394 if(trace.visible === true) {
87395 trace._nexttrace = null;
87396
87397 if(trace.fill in LINKEDFILLS) {
87398 prevtrace = prevtraces[group];
87399 trace._prevtrace = prevtrace || null;
87400
87401 if(prevtrace) {
87402 prevtrace._nexttrace = trace;
87403 }
87404 }
87405
87406 trace._ownfill = (trace.fill && (
87407 trace.fill.substr(0, 6) === 'tozero' ||
87408 trace.fill === 'toself' ||
87409 (trace.fill.substr(0, 2) === 'to' && !trace._prevtrace)
87410 ));
87411
87412 prevtraces[group] = trace;
87413 } else {
87414 trace._prevtrace = trace._nexttrace = trace._ownfill = null;
87415 }
87416 }
87417
87418 return cdscatterSorted;
87419};
87420
87421},{}],406:[function(_dereq_,module,exports){
87422/**
87423* Copyright 2012-2020, Plotly, Inc.
87424* All rights reserved.
87425*
87426* This source code is licensed under the MIT license found in the
87427* LICENSE file in the root directory of this source tree.
87428*/
87429
87430
87431'use strict';
87432
87433var isNumeric = _dereq_('fast-isnumeric');
87434
87435
87436// used in the drawing step for 'scatter' and 'scattegeo' and
87437// in the convert step for 'scatter3d'
87438module.exports = function makeBubbleSizeFn(trace) {
87439 var marker = trace.marker;
87440 var sizeRef = marker.sizeref || 1;
87441 var sizeMin = marker.sizemin || 0;
87442
87443 // for bubble charts, allow scaling the provided value linearly
87444 // and by area or diameter.
87445 // Note this only applies to the array-value sizes
87446
87447 var baseFn = (marker.sizemode === 'area') ?
87448 function(v) { return Math.sqrt(v / sizeRef); } :
87449 function(v) { return v / sizeRef; };
87450
87451 // TODO add support for position/negative bubbles?
87452 // TODO add 'sizeoffset' attribute?
87453 return function(v) {
87454 var baseSize = baseFn(v / 2);
87455
87456 // don't show non-numeric and negative sizes
87457 return (isNumeric(baseSize) && (baseSize > 0)) ?
87458 Math.max(baseSize, sizeMin) :
87459 0;
87460 };
87461};
87462
87463},{"fast-isnumeric":18}],407:[function(_dereq_,module,exports){
87464/**
87465* Copyright 2012-2020, Plotly, Inc.
87466* All rights reserved.
87467*
87468* This source code is licensed under the MIT license found in the
87469* LICENSE file in the root directory of this source tree.
87470*/
87471
87472
87473'use strict';
87474
87475module.exports = {
87476 container: 'marker',
87477 min: 'cmin',
87478 max: 'cmax'
87479};
87480
87481},{}],408:[function(_dereq_,module,exports){
87482/**
87483* Copyright 2012-2020, Plotly, Inc.
87484* All rights reserved.
87485*
87486* This source code is licensed under the MIT license found in the
87487* LICENSE file in the root directory of this source tree.
87488*/
87489
87490'use strict';
87491
87492var Color = _dereq_('../../components/color');
87493var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
87494var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
87495
87496var subTypes = _dereq_('./subtypes');
87497
87498/*
87499 * opts: object of flags to control features not all marker users support
87500 * noLine: caller does not support marker lines
87501 * gradient: caller supports gradients
87502 * noSelect: caller does not support selected/unselected attribute containers
87503 */
87504module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
87505 var isBubble = subTypes.isBubble(traceIn);
87506 var lineColor = (traceIn.line || {}).color;
87507 var defaultMLC;
87508
87509 opts = opts || {};
87510
87511 // marker.color inherit from line.color (even if line.color is an array)
87512 if(lineColor) defaultColor = lineColor;
87513
87514 coerce('marker.symbol');
87515 coerce('marker.opacity', isBubble ? 0.7 : 1);
87516 coerce('marker.size');
87517
87518 coerce('marker.color', defaultColor);
87519 if(hasColorscale(traceIn, 'marker')) {
87520 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
87521 }
87522
87523 if(!opts.noSelect) {
87524 coerce('selected.marker.color');
87525 coerce('unselected.marker.color');
87526 coerce('selected.marker.size');
87527 coerce('unselected.marker.size');
87528 }
87529
87530 if(!opts.noLine) {
87531 // if there's a line with a different color than the marker, use
87532 // that line color as the default marker line color
87533 // (except when it's an array)
87534 // mostly this is for transparent markers to behave nicely
87535 if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) {
87536 defaultMLC = lineColor;
87537 } else if(isBubble) defaultMLC = Color.background;
87538 else defaultMLC = Color.defaultLine;
87539
87540 coerce('marker.line.color', defaultMLC);
87541 if(hasColorscale(traceIn, 'marker.line')) {
87542 colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'});
87543 }
87544
87545 coerce('marker.line.width', isBubble ? 1 : 0);
87546 }
87547
87548 if(isBubble) {
87549 coerce('marker.sizeref');
87550 coerce('marker.sizemin');
87551 coerce('marker.sizemode');
87552 }
87553
87554 if(opts.gradient) {
87555 var gradientType = coerce('marker.gradient.type');
87556 if(gradientType !== 'none') {
87557 coerce('marker.gradient.color');
87558 }
87559 }
87560};
87561
87562},{"../../components/color":52,"../../components/colorscale/defaults":62,"../../components/colorscale/helpers":63,"./subtypes":413}],409:[function(_dereq_,module,exports){
87563/**
87564* Copyright 2012-2020, Plotly, Inc.
87565* All rights reserved.
87566*
87567* This source code is licensed under the MIT license found in the
87568* LICENSE file in the root directory of this source tree.
87569*/
87570
87571
87572'use strict';
87573
87574var d3 = _dereq_('d3');
87575
87576var Registry = _dereq_('../../registry');
87577var Lib = _dereq_('../../lib');
87578var ensureSingle = Lib.ensureSingle;
87579var identity = Lib.identity;
87580var Drawing = _dereq_('../../components/drawing');
87581
87582var subTypes = _dereq_('./subtypes');
87583var linePoints = _dereq_('./line_points');
87584var linkTraces = _dereq_('./link_traces');
87585var polygonTester = _dereq_('../../lib/polygon').tester;
87586
87587module.exports = function plot(gd, plotinfo, cdscatter, scatterLayer, transitionOpts, makeOnCompleteCallback) {
87588 var join, onComplete;
87589
87590 // If transition config is provided, then it is only a partial replot and traces not
87591 // updated are removed.
87592 var isFullReplot = !transitionOpts;
87593 var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
87594
87595 // Link traces so the z-order of fill layers is correct
87596 var cdscatterSorted = linkTraces(gd, plotinfo, cdscatter);
87597
87598 join = scatterLayer.selectAll('g.trace')
87599 .data(cdscatterSorted, function(d) { return d[0].trace.uid; });
87600
87601 // Append new traces:
87602 join.enter().append('g')
87603 .attr('class', function(d) {
87604 return 'trace scatter trace' + d[0].trace.uid;
87605 })
87606 .style('stroke-miterlimit', 2);
87607 join.order();
87608
87609 createFills(gd, join, plotinfo);
87610
87611 if(hasTransition) {
87612 if(makeOnCompleteCallback) {
87613 // If it was passed a callback to register completion, make a callback. If
87614 // this is created, then it must be executed on completion, otherwise the
87615 // pos-transition redraw will not execute:
87616 onComplete = makeOnCompleteCallback();
87617 }
87618
87619 var transition = d3.transition()
87620 .duration(transitionOpts.duration)
87621 .ease(transitionOpts.easing)
87622 .each('end', function() {
87623 onComplete && onComplete();
87624 })
87625 .each('interrupt', function() {
87626 onComplete && onComplete();
87627 });
87628
87629 transition.each(function() {
87630 // Must run the selection again since otherwise enters/updates get grouped together
87631 // and these get executed out of order. Except we need them in order!
87632 scatterLayer.selectAll('g.trace').each(function(d, i) {
87633 plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
87634 });
87635 });
87636 } else {
87637 join.each(function(d, i) {
87638 plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
87639 });
87640 }
87641
87642 if(isFullReplot) {
87643 join.exit().remove();
87644 }
87645
87646 // remove paths that didn't get used
87647 scatterLayer.selectAll('path:not([d])').remove();
87648};
87649
87650function createFills(gd, traceJoin, plotinfo) {
87651 traceJoin.each(function(d) {
87652 var fills = ensureSingle(d3.select(this), 'g', 'fills');
87653 Drawing.setClipUrl(fills, plotinfo.layerClipId, gd);
87654
87655 var trace = d[0].trace;
87656
87657 var fillData = [];
87658 if(trace._ownfill) fillData.push('_ownFill');
87659 if(trace._nexttrace) fillData.push('_nextFill');
87660
87661 var fillJoin = fills.selectAll('g').data(fillData, identity);
87662
87663 fillJoin.enter().append('g');
87664
87665 fillJoin.exit()
87666 .each(function(d) { trace[d] = null; })
87667 .remove();
87668
87669 fillJoin.order().each(function(d) {
87670 // make a path element inside the fill group, just so
87671 // we can give it its own data later on and the group can
87672 // keep its simple '_*Fill' data
87673 trace[d] = ensureSingle(d3.select(this), 'path', 'js-fill');
87674 });
87675 });
87676}
87677
87678function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) {
87679 var i;
87680
87681 // Since this has been reorganized and we're executing this on individual traces,
87682 // we need to pass it the full list of cdscatter as well as this trace's index (idx)
87683 // since it does an internal n^2 loop over comparisons with other traces:
87684 selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll);
87685
87686 var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
87687
87688 function transition(selection) {
87689 return hasTransition ? selection.transition() : selection;
87690 }
87691
87692 var xa = plotinfo.xaxis;
87693 var ya = plotinfo.yaxis;
87694
87695 var trace = cdscatter[0].trace;
87696 var line = trace.line;
87697 var tr = d3.select(element);
87698
87699 var errorBarGroup = ensureSingle(tr, 'g', 'errorbars');
87700 var lines = ensureSingle(tr, 'g', 'lines');
87701 var points = ensureSingle(tr, 'g', 'points');
87702 var text = ensureSingle(tr, 'g', 'text');
87703
87704 // error bars are at the bottom
87705 Registry.getComponentMethod('errorbars', 'plot')(gd, errorBarGroup, plotinfo, transitionOpts);
87706
87707 if(trace.visible !== true) return;
87708
87709 transition(tr).style('opacity', trace.opacity);
87710
87711 // BUILD LINES AND FILLS
87712 var ownFillEl3, tonext;
87713 var ownFillDir = trace.fill.charAt(trace.fill.length - 1);
87714 if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '';
87715
87716 // store node for tweaking by selectPoints
87717 cdscatter[0][plotinfo.isRangePlot ? 'nodeRangePlot3' : 'node3'] = tr;
87718
87719 var prevRevpath = '';
87720 var prevPolygons = [];
87721 var prevtrace = trace._prevtrace;
87722
87723 if(prevtrace) {
87724 prevRevpath = prevtrace._prevRevpath || '';
87725 tonext = prevtrace._nextFill;
87726 prevPolygons = prevtrace._polygons;
87727 }
87728
87729 var thispath;
87730 var thisrevpath;
87731 // fullpath is all paths for this curve, joined together straight
87732 // across gaps, for filling
87733 var fullpath = '';
87734 // revpath is fullpath reversed, for fill-to-next
87735 var revpath = '';
87736 // functions for converting a point array to a path
87737 var pathfn, revpathbase, revpathfn;
87738 // variables used before and after the data join
87739 var pt0, lastSegment, pt1, thisPolygons;
87740
87741 // initialize line join data / method
87742 var segments = [];
87743 var makeUpdate = Lib.noop;
87744
87745 ownFillEl3 = trace._ownFill;
87746
87747 if(subTypes.hasLines(trace) || trace.fill !== 'none') {
87748 if(tonext) {
87749 // This tells .style which trace to use for fill information:
87750 tonext.datum(cdscatter);
87751 }
87752
87753 if(['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) {
87754 pathfn = Drawing.steps(line.shape);
87755 revpathbase = Drawing.steps(
87756 line.shape.split('').reverse().join('')
87757 );
87758 } else if(line.shape === 'spline') {
87759 pathfn = revpathbase = function(pts) {
87760 var pLast = pts[pts.length - 1];
87761 if(pts.length > 1 && pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) {
87762 // identical start and end points: treat it as a
87763 // closed curve so we don't get a kink
87764 return Drawing.smoothclosed(pts.slice(1), line.smoothing);
87765 } else {
87766 return Drawing.smoothopen(pts, line.smoothing);
87767 }
87768 };
87769 } else {
87770 pathfn = revpathbase = function(pts) {
87771 return 'M' + pts.join('L');
87772 };
87773 }
87774
87775 revpathfn = function(pts) {
87776 // note: this is destructive (reverses pts in place) so can't use pts after this
87777 return revpathbase(pts.reverse());
87778 };
87779
87780 segments = linePoints(cdscatter, {
87781 xaxis: xa,
87782 yaxis: ya,
87783 connectGaps: trace.connectgaps,
87784 baseTolerance: Math.max(line.width || 1, 3) / 4,
87785 shape: line.shape,
87786 simplify: line.simplify,
87787 fill: trace.fill
87788 });
87789
87790 // since we already have the pixel segments here, use them to make
87791 // polygons for hover on fill
87792 // TODO: can we skip this if hoveron!=fills? That would mean we
87793 // need to redraw when you change hoveron...
87794 thisPolygons = trace._polygons = new Array(segments.length);
87795 for(i = 0; i < segments.length; i++) {
87796 trace._polygons[i] = polygonTester(segments[i]);
87797 }
87798
87799 if(segments.length) {
87800 pt0 = segments[0][0];
87801 lastSegment = segments[segments.length - 1];
87802 pt1 = lastSegment[lastSegment.length - 1];
87803 }
87804
87805 makeUpdate = function(isEnter) {
87806 return function(pts) {
87807 thispath = pathfn(pts);
87808 thisrevpath = revpathfn(pts);
87809 if(!fullpath) {
87810 fullpath = thispath;
87811 revpath = thisrevpath;
87812 } else if(ownFillDir) {
87813 fullpath += 'L' + thispath.substr(1);
87814 revpath = thisrevpath + ('L' + revpath.substr(1));
87815 } else {
87816 fullpath += 'Z' + thispath;
87817 revpath = thisrevpath + 'Z' + revpath;
87818 }
87819
87820 if(subTypes.hasLines(trace) && pts.length > 1) {
87821 var el = d3.select(this);
87822
87823 // This makes the coloring work correctly:
87824 el.datum(cdscatter);
87825
87826 if(isEnter) {
87827 transition(el.style('opacity', 0)
87828 .attr('d', thispath)
87829 .call(Drawing.lineGroupStyle))
87830 .style('opacity', 1);
87831 } else {
87832 var sel = transition(el);
87833 sel.attr('d', thispath);
87834 Drawing.singleLineStyle(cdscatter, sel);
87835 }
87836 }
87837 };
87838 };
87839 }
87840
87841 var lineJoin = lines.selectAll('.js-line').data(segments);
87842
87843 transition(lineJoin.exit())
87844 .style('opacity', 0)
87845 .remove();
87846
87847 lineJoin.each(makeUpdate(false));
87848
87849 lineJoin.enter().append('path')
87850 .classed('js-line', true)
87851 .style('vector-effect', 'non-scaling-stroke')
87852 .call(Drawing.lineGroupStyle)
87853 .each(makeUpdate(true));
87854
87855 Drawing.setClipUrl(lineJoin, plotinfo.layerClipId, gd);
87856
87857 function clearFill(selection) {
87858 transition(selection).attr('d', 'M0,0Z');
87859 }
87860
87861 if(segments.length) {
87862 if(ownFillEl3) {
87863 ownFillEl3.datum(cdscatter);
87864 if(pt0 && pt1) {
87865 if(ownFillDir) {
87866 if(ownFillDir === 'y') {
87867 pt0[1] = pt1[1] = ya.c2p(0, true);
87868 } else if(ownFillDir === 'x') {
87869 pt0[0] = pt1[0] = xa.c2p(0, true);
87870 }
87871
87872 // fill to zero: full trace path, plus extension of
87873 // the endpoints to the appropriate axis
87874 // For the sake of animations, wrap the points around so that
87875 // the points on the axes are the first two points. Otherwise
87876 // animations get a little crazy if the number of points changes.
87877 transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
87878 .call(Drawing.singleFillStyle);
87879 } else {
87880 // fill to self: just join the path to itself
87881 transition(ownFillEl3).attr('d', fullpath + 'Z')
87882 .call(Drawing.singleFillStyle);
87883 }
87884 }
87885 } else if(tonext) {
87886 if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) {
87887 // fill to next: full trace path, plus the previous path reversed
87888 if(trace.fill === 'tonext') {
87889 // tonext: for use by concentric shapes, like manually constructed
87890 // contours, we just add the two paths closed on themselves.
87891 // This makes strange results if one path is *not* entirely
87892 // inside the other, but then that is a strange usage.
87893 transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
87894 .call(Drawing.singleFillStyle);
87895 } else {
87896 // tonextx/y: for now just connect endpoints with lines. This is
87897 // the correct behavior if the endpoints are at the same value of
87898 // y/x, but if they *aren't*, we should ideally do more complicated
87899 // things depending on whether the new endpoint projects onto the
87900 // existing curve or off the end of it
87901 transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
87902 .call(Drawing.singleFillStyle);
87903 }
87904 trace._polygons = trace._polygons.concat(prevPolygons);
87905 } else {
87906 clearFill(tonext);
87907 trace._polygons = null;
87908 }
87909 }
87910 trace._prevRevpath = revpath;
87911 trace._prevPolygons = thisPolygons;
87912 } else {
87913 if(ownFillEl3) clearFill(ownFillEl3);
87914 else if(tonext) clearFill(tonext);
87915 trace._polygons = trace._prevRevpath = trace._prevPolygons = null;
87916 }
87917
87918
87919 function visFilter(d) {
87920 return d.filter(function(v) { return !v.gap && v.vis; });
87921 }
87922
87923 function visFilterWithGaps(d) {
87924 return d.filter(function(v) { return v.vis; });
87925 }
87926
87927 function gapFilter(d) {
87928 return d.filter(function(v) { return !v.gap; });
87929 }
87930
87931 function keyFunc(d) {
87932 return d.id;
87933 }
87934
87935 // Returns a function if the trace is keyed, otherwise returns undefined
87936 function getKeyFunc(trace) {
87937 if(trace.ids) {
87938 return keyFunc;
87939 }
87940 }
87941
87942 function hideFilter() {
87943 return false;
87944 }
87945
87946 function makePoints(points, text, cdscatter) {
87947 var join, selection, hasNode;
87948
87949 var trace = cdscatter[0].trace;
87950 var showMarkers = subTypes.hasMarkers(trace);
87951 var showText = subTypes.hasText(trace);
87952
87953 var keyFunc = getKeyFunc(trace);
87954 var markerFilter = hideFilter;
87955 var textFilter = hideFilter;
87956
87957 if(showMarkers || showText) {
87958 var showFilter = identity;
87959 // if we're stacking, "infer zero" gap mode gets markers in the
87960 // gap points - because we've inferred a zero there - but other
87961 // modes (currently "interpolate", later "interrupt" hopefully)
87962 // we don't draw generated markers
87963 var stackGroup = trace.stackgroup;
87964 var isInferZero = stackGroup && (
87965 gd._fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup].stackgaps === 'infer zero');
87966 if(trace.marker.maxdisplayed || trace._needsCull) {
87967 showFilter = isInferZero ? visFilterWithGaps : visFilter;
87968 } else if(stackGroup && !isInferZero) {
87969 showFilter = gapFilter;
87970 }
87971
87972 if(showMarkers) markerFilter = showFilter;
87973 if(showText) textFilter = showFilter;
87974 }
87975
87976 // marker points
87977
87978 selection = points.selectAll('path.point');
87979
87980 join = selection.data(markerFilter, keyFunc);
87981
87982 var enter = join.enter().append('path')
87983 .classed('point', true);
87984
87985 if(hasTransition) {
87986 enter
87987 .call(Drawing.pointStyle, trace, gd)
87988 .call(Drawing.translatePoints, xa, ya)
87989 .style('opacity', 0)
87990 .transition()
87991 .style('opacity', 1);
87992 }
87993
87994 join.order();
87995
87996 var styleFns;
87997 if(showMarkers) {
87998 styleFns = Drawing.makePointStyleFns(trace);
87999 }
88000
88001 join.each(function(d) {
88002 var el = d3.select(this);
88003 var sel = transition(el);
88004 hasNode = Drawing.translatePoint(d, sel, xa, ya);
88005
88006 if(hasNode) {
88007 Drawing.singlePointStyle(d, sel, trace, styleFns, gd);
88008
88009 if(plotinfo.layerClipId) {
88010 Drawing.hideOutsideRangePoint(d, sel, xa, ya, trace.xcalendar, trace.ycalendar);
88011 }
88012
88013 if(trace.customdata) {
88014 el.classed('plotly-customdata', d.data !== null && d.data !== undefined);
88015 }
88016 } else {
88017 sel.remove();
88018 }
88019 });
88020
88021 if(hasTransition) {
88022 join.exit().transition()
88023 .style('opacity', 0)
88024 .remove();
88025 } else {
88026 join.exit().remove();
88027 }
88028
88029 // text points
88030 selection = text.selectAll('g');
88031 join = selection.data(textFilter, keyFunc);
88032
88033 // each text needs to go in its own 'g' in case
88034 // it gets converted to mathjax
88035 join.enter().append('g').classed('textpoint', true).append('text');
88036
88037 join.order();
88038
88039 join.each(function(d) {
88040 var g = d3.select(this);
88041 var sel = transition(g.select('text'));
88042 hasNode = Drawing.translatePoint(d, sel, xa, ya);
88043
88044 if(hasNode) {
88045 if(plotinfo.layerClipId) {
88046 Drawing.hideOutsideRangePoint(d, g, xa, ya, trace.xcalendar, trace.ycalendar);
88047 }
88048 } else {
88049 g.remove();
88050 }
88051 });
88052
88053 join.selectAll('text')
88054 .call(Drawing.textPointStyle, trace, gd)
88055 .each(function(d) {
88056 // This just *has* to be totally custom becuase of SVG text positioning :(
88057 // It's obviously copied from translatePoint; we just can't use that
88058 var x = xa.c2p(d.x);
88059 var y = ya.c2p(d.y);
88060
88061 d3.select(this).selectAll('tspan.line').each(function() {
88062 transition(d3.select(this)).attr({x: x, y: y});
88063 });
88064 });
88065
88066 join.exit().remove();
88067 }
88068
88069 points.datum(cdscatter);
88070 text.datum(cdscatter);
88071 makePoints(points, text, cdscatter);
88072
88073 // lastly, clip points groups of `cliponaxis !== false` traces
88074 // on `plotinfo._hasClipOnAxisFalse === true` subplots
88075 var hasClipOnAxisFalse = trace.cliponaxis === false;
88076 var clipUrl = hasClipOnAxisFalse ? null : plotinfo.layerClipId;
88077 Drawing.setClipUrl(points, clipUrl, gd);
88078 Drawing.setClipUrl(text, clipUrl, gd);
88079}
88080
88081function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {
88082 var xa = plotinfo.xaxis;
88083 var ya = plotinfo.yaxis;
88084 var xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c));
88085 var yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c));
88086
88087 var trace = cdscatter[0].trace;
88088 if(!subTypes.hasMarkers(trace)) return;
88089 // if marker.maxdisplayed is used, select a maximum of
88090 // mnum markers to show, from the set that are in the viewport
88091 var mnum = trace.marker.maxdisplayed;
88092
88093 // TODO: remove some as we get away from the viewport?
88094 if(mnum === 0) return;
88095
88096 var cd = cdscatter.filter(function(v) {
88097 return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1];
88098 });
88099 var inc = Math.ceil(cd.length / mnum);
88100 var tnum = 0;
88101 cdscatterAll.forEach(function(cdj, j) {
88102 var tracei = cdj[0].trace;
88103 if(subTypes.hasMarkers(tracei) &&
88104 tracei.marker.maxdisplayed > 0 && j < idx) {
88105 tnum++;
88106 }
88107 });
88108
88109 // if multiple traces use maxdisplayed, stagger which markers we
88110 // display this formula offsets successive traces by 1/3 of the
88111 // increment, adding an extra small amount after each triplet so
88112 // it's not quite periodic
88113 var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1);
88114
88115 // for error bars: save in cd which markers to show
88116 // so we don't have to repeat this
88117 cdscatter.forEach(function(v) { delete v.vis; });
88118 cd.forEach(function(v, i) {
88119 if(Math.round((i + i0) % inc) === 0) v.vis = true;
88120 });
88121}
88122
88123},{"../../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){
88124/**
88125* Copyright 2012-2020, Plotly, Inc.
88126* All rights reserved.
88127*
88128* This source code is licensed under the MIT license found in the
88129* LICENSE file in the root directory of this source tree.
88130*/
88131
88132
88133'use strict';
88134
88135var subtypes = _dereq_('./subtypes');
88136
88137module.exports = function selectPoints(searchInfo, selectionTester) {
88138 var cd = searchInfo.cd;
88139 var xa = searchInfo.xaxis;
88140 var ya = searchInfo.yaxis;
88141 var selection = [];
88142 var trace = cd[0].trace;
88143 var i;
88144 var di;
88145 var x;
88146 var y;
88147
88148 var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
88149 if(hasOnlyLines) return [];
88150
88151 if(selectionTester === false) { // clear selection
88152 for(i = 0; i < cd.length; i++) {
88153 cd[i].selected = 0;
88154 }
88155 } else {
88156 for(i = 0; i < cd.length; i++) {
88157 di = cd[i];
88158 x = xa.c2p(di.x);
88159 y = ya.c2p(di.y);
88160
88161 if((di.i !== null) && selectionTester.contains([x, y], false, i, searchInfo)) {
88162 selection.push({
88163 pointNumber: di.i,
88164 x: xa.c2d(di.x),
88165 y: ya.c2d(di.y)
88166 });
88167 di.selected = 1;
88168 } else {
88169 di.selected = 0;
88170 }
88171 }
88172 }
88173
88174 return selection;
88175};
88176
88177},{"./subtypes":413}],411:[function(_dereq_,module,exports){
88178/**
88179* Copyright 2012-2020, Plotly, Inc.
88180* All rights reserved.
88181*
88182* This source code is licensed under the MIT license found in the
88183* LICENSE file in the root directory of this source tree.
88184*/
88185
88186'use strict';
88187
88188var perStackAttrs = ['orientation', 'groupnorm', 'stackgaps'];
88189
88190module.exports = function handleStackDefaults(traceIn, traceOut, layout, coerce) {
88191 var stackOpts = layout._scatterStackOpts;
88192
88193 var stackGroup = coerce('stackgroup');
88194 if(stackGroup) {
88195 // use independent stacking options per subplot
88196 var subplot = traceOut.xaxis + traceOut.yaxis;
88197 var subplotStackOpts = stackOpts[subplot];
88198 if(!subplotStackOpts) subplotStackOpts = stackOpts[subplot] = {};
88199
88200 var groupOpts = subplotStackOpts[stackGroup];
88201 var firstTrace = false;
88202 if(groupOpts) {
88203 groupOpts.traces.push(traceOut);
88204 } else {
88205 groupOpts = subplotStackOpts[stackGroup] = {
88206 // keep track of trace indices for use during stacking calculations
88207 // this will be filled in during `calc` and used during `crossTraceCalc`
88208 // so it's OK if we don't recreate it during a non-calc edit
88209 traceIndices: [],
88210 // Hold on to the whole set of prior traces
88211 // First one is most important, so we can clear defaults
88212 // there if we find explicit values only in later traces.
88213 // We're only going to *use* the values stored in groupOpts,
88214 // but for the editor and validate we want things self-consistent
88215 // The full set of traces is used only to fix `fill` default if
88216 // we find `orientation: 'h'` beyond the first trace
88217 traces: [traceOut]
88218 };
88219 firstTrace = true;
88220 }
88221 // TODO: how is this going to work with groupby transforms?
88222 // in principle it should be OK I guess, as long as explicit group styles
88223 // don't override explicit base-trace styles?
88224
88225 var dflts = {
88226 orientation: (traceOut.x && !traceOut.y) ? 'h' : 'v'
88227 };
88228
88229 for(var i = 0; i < perStackAttrs.length; i++) {
88230 var attr = perStackAttrs[i];
88231 var attrFound = attr + 'Found';
88232 if(!groupOpts[attrFound]) {
88233 var traceHasAttr = traceIn[attr] !== undefined;
88234 var isOrientation = attr === 'orientation';
88235 if(traceHasAttr || firstTrace) {
88236 groupOpts[attr] = coerce(attr, dflts[attr]);
88237
88238 if(isOrientation) {
88239 groupOpts.fillDflt = groupOpts[attr] === 'h' ?
88240 'tonextx' : 'tonexty';
88241 }
88242
88243 if(traceHasAttr) {
88244 // Note: this will show a value here even if it's invalid
88245 // in which case it will revert to default.
88246 groupOpts[attrFound] = true;
88247
88248 // Note: only one trace in the stack will get a _fullData
88249 // entry for a given stack-wide attribute. If no traces
88250 // (or the first trace) specify that attribute, the
88251 // first trace will get it. If the first trace does NOT
88252 // specify it but some later trace does, then it gets
88253 // removed from the first trace and only included in the
88254 // one that specified it. This is mostly important for
88255 // editors (that want to see the full values to know
88256 // what settings are available) and Plotly.react diffing.
88257 // Editors may want to use fullLayout._scatterStackOpts
88258 // directly and make these settings available from all
88259 // traces in the stack... then set the new value into
88260 // the first trace, and clear all later traces.
88261 if(!firstTrace) {
88262 delete groupOpts.traces[0][attr];
88263
88264 // orientation can affect default fill of previous traces
88265 if(isOrientation) {
88266 for(var j = 0; j < groupOpts.traces.length - 1; j++) {
88267 var trace2 = groupOpts.traces[j];
88268 if(trace2._input.fill !== trace2.fill) {
88269 trace2.fill = groupOpts.fillDflt;
88270 }
88271 }
88272 }
88273 }
88274 }
88275 }
88276 }
88277 }
88278 return groupOpts;
88279 }
88280};
88281
88282},{}],412:[function(_dereq_,module,exports){
88283/**
88284* Copyright 2012-2020, Plotly, Inc.
88285* All rights reserved.
88286*
88287* This source code is licensed under the MIT license found in the
88288* LICENSE file in the root directory of this source tree.
88289*/
88290
88291
88292'use strict';
88293
88294var d3 = _dereq_('d3');
88295var Drawing = _dereq_('../../components/drawing');
88296var Registry = _dereq_('../../registry');
88297
88298function style(gd) {
88299 var s = d3.select(gd).selectAll('g.trace.scatter');
88300
88301 s.style('opacity', function(d) {
88302 return d[0].trace.opacity;
88303 });
88304
88305 s.selectAll('g.points').each(function(d) {
88306 var sel = d3.select(this);
88307 var trace = d.trace || d[0].trace;
88308 stylePoints(sel, trace, gd);
88309 });
88310
88311 s.selectAll('g.text').each(function(d) {
88312 var sel = d3.select(this);
88313 var trace = d.trace || d[0].trace;
88314 styleText(sel, trace, gd);
88315 });
88316
88317 s.selectAll('g.trace path.js-line')
88318 .call(Drawing.lineGroupStyle);
88319
88320 s.selectAll('g.trace path.js-fill')
88321 .call(Drawing.fillGroupStyle);
88322
88323 Registry.getComponentMethod('errorbars', 'style')(s);
88324}
88325
88326function stylePoints(sel, trace, gd) {
88327 Drawing.pointStyle(sel.selectAll('path.point'), trace, gd);
88328}
88329
88330function styleText(sel, trace, gd) {
88331 Drawing.textPointStyle(sel.selectAll('text'), trace, gd);
88332}
88333
88334function styleOnSelect(gd, cd, sel) {
88335 var trace = cd[0].trace;
88336
88337 if(trace.selectedpoints) {
88338 Drawing.selectedPointStyle(sel.selectAll('path.point'), trace);
88339 Drawing.selectedTextStyle(sel.selectAll('text'), trace);
88340 } else {
88341 stylePoints(sel, trace, gd);
88342 styleText(sel, trace, gd);
88343 }
88344}
88345
88346module.exports = {
88347 style: style,
88348 stylePoints: stylePoints,
88349 styleText: styleText,
88350 styleOnSelect: styleOnSelect
88351};
88352
88353},{"../../components/drawing":74,"../../registry":269,"d3":16}],413:[function(_dereq_,module,exports){
88354/**
88355* Copyright 2012-2020, Plotly, Inc.
88356* All rights reserved.
88357*
88358* This source code is licensed under the MIT license found in the
88359* LICENSE file in the root directory of this source tree.
88360*/
88361
88362
88363'use strict';
88364
88365var Lib = _dereq_('../../lib');
88366
88367module.exports = {
88368 hasLines: function(trace) {
88369 return trace.visible && trace.mode &&
88370 trace.mode.indexOf('lines') !== -1;
88371 },
88372
88373 hasMarkers: function(trace) {
88374 return trace.visible && (
88375 (trace.mode && trace.mode.indexOf('markers') !== -1) ||
88376 // until splom implements 'mode'
88377 trace.type === 'splom'
88378 );
88379 },
88380
88381 hasText: function(trace) {
88382 return trace.visible && trace.mode &&
88383 trace.mode.indexOf('text') !== -1;
88384 },
88385
88386 isBubble: function(trace) {
88387 return Lib.isPlainObject(trace.marker) &&
88388 Lib.isArrayOrTypedArray(trace.marker.size);
88389 }
88390};
88391
88392},{"../../lib":178}],414:[function(_dereq_,module,exports){
88393/**
88394* Copyright 2012-2020, Plotly, Inc.
88395* All rights reserved.
88396*
88397* This source code is licensed under the MIT license found in the
88398* LICENSE file in the root directory of this source tree.
88399*/
88400
88401
88402'use strict';
88403
88404var Lib = _dereq_('../../lib');
88405
88406/*
88407 * opts: object of flags to control features not all text users support
88408 * noSelect: caller does not support selected/unselected attribute containers
88409 */
88410module.exports = function(traceIn, traceOut, layout, coerce, opts) {
88411 opts = opts || {};
88412
88413 coerce('textposition');
88414 Lib.coerceFont(coerce, 'textfont', layout.font);
88415
88416 if(!opts.noSelect) {
88417 coerce('selected.textfont.color');
88418 coerce('unselected.textfont.color');
88419 }
88420};
88421
88422},{"../../lib":178}],415:[function(_dereq_,module,exports){
88423/**
88424* Copyright 2012-2020, Plotly, Inc.
88425* All rights reserved.
88426*
88427* This source code is licensed under the MIT license found in the
88428* LICENSE file in the root directory of this source tree.
88429*/
88430
88431'use strict';
88432
88433var Lib = _dereq_('../../lib');
88434var Registry = _dereq_('../../registry');
88435
88436module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
88437 var x = coerce('x');
88438 var y = coerce('y');
88439 var len;
88440
88441 var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
88442 handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
88443
88444 if(x) {
88445 var xlen = Lib.minRowLength(x);
88446 if(y) {
88447 len = Math.min(xlen, Lib.minRowLength(y));
88448 } else {
88449 len = xlen;
88450 coerce('y0');
88451 coerce('dy');
88452 }
88453 } else {
88454 if(!y) return 0;
88455
88456 len = Lib.minRowLength(y);
88457 coerce('x0');
88458 coerce('dx');
88459 }
88460
88461 traceOut._length = len;
88462
88463 return len;
88464};
88465
88466},{"../../lib":178,"../../registry":269}],416:[function(_dereq_,module,exports){
88467/**
88468* Copyright 2012-2020, Plotly, Inc.
88469* All rights reserved.
88470*
88471* This source code is licensed under the MIT license found in the
88472* LICENSE file in the root directory of this source tree.
88473*/
88474
88475'use strict';
88476
88477var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
88478var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
88479var scatterAttrs = _dereq_('../scatter/attributes');
88480var baseAttrs = _dereq_('../../plots/attributes');
88481var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
88482var dash = _dereq_('../../components/drawing/attributes').dash;
88483
88484var extendFlat = _dereq_('../../lib/extend').extendFlat;
88485
88486var scatterMarkerAttrs = scatterAttrs.marker;
88487var scatterLineAttrs = scatterAttrs.line;
88488var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
88489
88490module.exports = {
88491 a: {
88492 valType: 'data_array',
88493 editType: 'calc',
88494
88495 },
88496 b: {
88497 valType: 'data_array',
88498 editType: 'calc',
88499
88500 },
88501 c: {
88502 valType: 'data_array',
88503 editType: 'calc',
88504
88505 },
88506 sum: {
88507 valType: 'number',
88508
88509 dflt: 0,
88510 min: 0,
88511 editType: 'calc',
88512
88513 },
88514 mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}),
88515 text: extendFlat({}, scatterAttrs.text, {
88516
88517 }),
88518 texttemplate: texttemplateAttrs({editType: 'plot'}, {
88519 keys: ['a', 'b', 'c', 'text']
88520 }),
88521 hovertext: extendFlat({}, scatterAttrs.hovertext, {
88522
88523 }),
88524 line: {
88525 color: scatterLineAttrs.color,
88526 width: scatterLineAttrs.width,
88527 dash: dash,
88528 shape: extendFlat({}, scatterLineAttrs.shape,
88529 {values: ['linear', 'spline']}),
88530 smoothing: scatterLineAttrs.smoothing,
88531 editType: 'calc'
88532 },
88533 connectgaps: scatterAttrs.connectgaps,
88534 cliponaxis: scatterAttrs.cliponaxis,
88535 fill: extendFlat({}, scatterAttrs.fill, {
88536 values: ['none', 'toself', 'tonext'],
88537 dflt: 'none',
88538
88539 }),
88540 fillcolor: scatterAttrs.fillcolor,
88541 marker: extendFlat({
88542 symbol: scatterMarkerAttrs.symbol,
88543 opacity: scatterMarkerAttrs.opacity,
88544 maxdisplayed: scatterMarkerAttrs.maxdisplayed,
88545 size: scatterMarkerAttrs.size,
88546 sizeref: scatterMarkerAttrs.sizeref,
88547 sizemin: scatterMarkerAttrs.sizemin,
88548 sizemode: scatterMarkerAttrs.sizemode,
88549 line: extendFlat({
88550 width: scatterMarkerLineAttrs.width,
88551 editType: 'calc'
88552 },
88553 colorScaleAttrs('marker.line')
88554 ),
88555 gradient: scatterMarkerAttrs.gradient,
88556 editType: 'calc'
88557 },
88558 colorScaleAttrs('marker')
88559 ),
88560
88561 textfont: scatterAttrs.textfont,
88562 textposition: scatterAttrs.textposition,
88563
88564 selected: scatterAttrs.selected,
88565 unselected: scatterAttrs.unselected,
88566
88567 hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
88568 flags: ['a', 'b', 'c', 'text', 'name']
88569 }),
88570 hoveron: scatterAttrs.hoveron,
88571 hovertemplate: hovertemplateAttrs(),
88572};
88573
88574},{"../../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){
88575/**
88576* Copyright 2012-2020, Plotly, Inc.
88577* All rights reserved.
88578*
88579* This source code is licensed under the MIT license found in the
88580* LICENSE file in the root directory of this source tree.
88581*/
88582
88583
88584'use strict';
88585
88586var isNumeric = _dereq_('fast-isnumeric');
88587
88588var calcColorscale = _dereq_('../scatter/colorscale_calc');
88589var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
88590var calcSelection = _dereq_('../scatter/calc_selection');
88591var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
88592
88593var dataArrays = ['a', 'b', 'c'];
88594var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']};
88595
88596module.exports = function calc(gd, trace) {
88597 var ternary = gd._fullLayout[trace.subplot];
88598 var displaySum = ternary.sum;
88599 var normSum = trace.sum || displaySum;
88600 var arrays = {a: trace.a, b: trace.b, c: trace.c};
88601
88602 var i, j, dataArray, newArray, fillArray1, fillArray2;
88603
88604 // fill in one missing component
88605 for(i = 0; i < dataArrays.length; i++) {
88606 dataArray = dataArrays[i];
88607 if(arrays[dataArray]) continue;
88608
88609 fillArray1 = arrays[arraysToFill[dataArray][0]];
88610 fillArray2 = arrays[arraysToFill[dataArray][1]];
88611 newArray = new Array(fillArray1.length);
88612 for(j = 0; j < fillArray1.length; j++) {
88613 newArray[j] = normSum - fillArray1[j] - fillArray2[j];
88614 }
88615 arrays[dataArray] = newArray;
88616 }
88617
88618 // make the calcdata array
88619 var serieslen = trace._length;
88620 var cd = new Array(serieslen);
88621 var a, b, c, norm, x, y;
88622 for(i = 0; i < serieslen; i++) {
88623 a = arrays.a[i];
88624 b = arrays.b[i];
88625 c = arrays.c[i];
88626 if(isNumeric(a) && isNumeric(b) && isNumeric(c)) {
88627 a = +a;
88628 b = +b;
88629 c = +c;
88630 norm = displaySum / (a + b + c);
88631 if(norm !== 1) {
88632 a *= norm;
88633 b *= norm;
88634 c *= norm;
88635 }
88636 // map a, b, c onto x and y where the full scale of y
88637 // is [0, sum], and x is [-sum, sum]
88638 // TODO: this makes `a` always the top, `b` the bottom left,
88639 // and `c` the bottom right. Do we want options to rearrange
88640 // these?
88641 y = a;
88642 x = c - b;
88643 cd[i] = {x: x, y: y, a: a, b: b, c: c};
88644 } else cd[i] = {x: false, y: false};
88645 }
88646
88647 calcMarkerSize(trace, serieslen);
88648 calcColorscale(gd, trace);
88649 arraysToCalcdata(cd, trace);
88650 calcSelection(cd, trace);
88651
88652 return cd;
88653};
88654
88655},{"../scatter/arrays_to_calcdata":388,"../scatter/calc":390,"../scatter/calc_selection":391,"../scatter/colorscale_calc":392,"fast-isnumeric":18}],418:[function(_dereq_,module,exports){
88656/**
88657* Copyright 2012-2020, Plotly, Inc.
88658* All rights reserved.
88659*
88660* This source code is licensed under the MIT license found in the
88661* LICENSE file in the root directory of this source tree.
88662*/
88663
88664
88665'use strict';
88666
88667var Lib = _dereq_('../../lib');
88668
88669var constants = _dereq_('../scatter/constants');
88670var subTypes = _dereq_('../scatter/subtypes');
88671var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
88672var handleLineDefaults = _dereq_('../scatter/line_defaults');
88673var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
88674var handleTextDefaults = _dereq_('../scatter/text_defaults');
88675var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
88676
88677var attributes = _dereq_('./attributes');
88678
88679
88680module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
88681 function coerce(attr, dflt) {
88682 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
88683 }
88684
88685 var a = coerce('a');
88686 var b = coerce('b');
88687 var c = coerce('c');
88688 var len;
88689
88690 // allow any one array to be missing, len is the minimum length of those
88691 // present. Note that after coerce data_array's are either Arrays (which
88692 // are truthy even if empty) or undefined. As in scatter, an empty array
88693 // is different from undefined, because it can signify that this data is
88694 // not known yet but expected in the future
88695 if(a) {
88696 len = a.length;
88697 if(b) {
88698 len = Math.min(len, b.length);
88699 if(c) len = Math.min(len, c.length);
88700 } else if(c) len = Math.min(len, c.length);
88701 else len = 0;
88702 } else if(b && c) {
88703 len = Math.min(b.length, c.length);
88704 }
88705
88706 if(!len) {
88707 traceOut.visible = false;
88708 return;
88709 }
88710
88711 traceOut._length = len;
88712
88713 coerce('sum');
88714
88715 coerce('text');
88716 coerce('hovertext');
88717 if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
88718
88719 var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
88720 coerce('mode', defaultMode);
88721
88722 if(subTypes.hasLines(traceOut)) {
88723 handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
88724 handleLineShapeDefaults(traceIn, traceOut, coerce);
88725 coerce('connectgaps');
88726 }
88727
88728 if(subTypes.hasMarkers(traceOut)) {
88729 handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
88730 }
88731
88732 if(subTypes.hasText(traceOut)) {
88733 coerce('texttemplate');
88734 handleTextDefaults(traceIn, traceOut, layout, coerce);
88735 }
88736
88737 var dfltHoverOn = [];
88738
88739 if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
88740 coerce('cliponaxis');
88741 coerce('marker.maxdisplayed');
88742 dfltHoverOn.push('points');
88743 }
88744
88745 coerce('fill');
88746 if(traceOut.fill !== 'none') {
88747 handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
88748 if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
88749 }
88750
88751 if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
88752 dfltHoverOn.push('fills');
88753 }
88754 coerce('hoveron', dfltHoverOn.join('+') || 'points');
88755
88756 Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
88757};
88758
88759},{"../../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){
88760/**
88761* Copyright 2012-2020, Plotly, Inc.
88762* All rights reserved.
88763*
88764* This source code is licensed under the MIT license found in the
88765* LICENSE file in the root directory of this source tree.
88766*/
88767
88768'use strict';
88769
88770module.exports = function eventData(out, pt, trace, cd, pointNumber) {
88771 if(pt.xa) out.xaxis = pt.xa;
88772 if(pt.ya) out.yaxis = pt.ya;
88773
88774 if(cd[pointNumber]) {
88775 var cdi = cd[pointNumber];
88776
88777 // N.B. These are the normalized coordinates.
88778 out.a = cdi.a;
88779 out.b = cdi.b;
88780 out.c = cdi.c;
88781 } else {
88782 // for fill-hover only
88783 out.a = pt.a;
88784 out.b = pt.b;
88785 out.c = pt.c;
88786 }
88787
88788 return out;
88789};
88790
88791},{}],420:[function(_dereq_,module,exports){
88792/**
88793* Copyright 2012-2020, Plotly, Inc.
88794* All rights reserved.
88795*
88796* This source code is licensed under the MIT license found in the
88797* LICENSE file in the root directory of this source tree.
88798*/
88799
88800'use strict';
88801
88802var Axes = _dereq_('../../plots/cartesian/axes');
88803
88804module.exports = function formatLabels(cdi, trace, fullLayout) {
88805 var labels = {};
88806
88807 var subplot = fullLayout[trace.subplot]._subplot;
88808 labels.aLabel = Axes.tickText(subplot.aaxis, cdi.a, true).text;
88809 labels.bLabel = Axes.tickText(subplot.baxis, cdi.b, true).text;
88810 labels.cLabel = Axes.tickText(subplot.caxis, cdi.c, true).text;
88811
88812 return labels;
88813};
88814
88815},{"../../plots/cartesian/axes":222}],421:[function(_dereq_,module,exports){
88816/**
88817* Copyright 2012-2020, Plotly, Inc.
88818* All rights reserved.
88819*
88820* This source code is licensed under the MIT license found in the
88821* LICENSE file in the root directory of this source tree.
88822*/
88823
88824'use strict';
88825
88826var scatterHover = _dereq_('../scatter/hover');
88827
88828module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
88829 var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
88830 if(!scatterPointData || scatterPointData[0].index === false) return;
88831
88832 var newPointData = scatterPointData[0];
88833
88834 // if hovering on a fill, we don't show any point data so the label is
88835 // unchanged from what scatter gives us - except that it needs to
88836 // be constrained to the trianglular plot area, not just the rectangular
88837 // area defined by the synthetic x and y axes
88838 // TODO: in some cases the vertical middle of the shape is not within
88839 // the triangular viewport at all, so the label can become disconnected
88840 // from the shape entirely. But calculating what portion of the shape
88841 // is actually visible, as constrained by the diagonal axis lines, is not
88842 // so easy and anyway we lost the information we would have needed to do
88843 // this inside scatterHover.
88844 if(newPointData.index === undefined) {
88845 var yFracUp = 1 - (newPointData.y0 / pointData.ya._length);
88846 var xLen = pointData.xa._length;
88847 var xMin = xLen * yFracUp / 2;
88848 var xMax = xLen - xMin;
88849 newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin);
88850 newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin);
88851 return scatterPointData;
88852 }
88853
88854 var cdi = newPointData.cd[newPointData.index];
88855 var trace = newPointData.trace;
88856 var subplot = newPointData.subplot;
88857
88858 newPointData.a = cdi.a;
88859 newPointData.b = cdi.b;
88860 newPointData.c = cdi.c;
88861
88862 newPointData.xLabelVal = undefined;
88863 newPointData.yLabelVal = undefined;
88864
88865 var fullLayout = {};
88866 fullLayout[trace.subplot] = {_subplot: subplot};
88867 var labels = trace._module.formatLabels(cdi, trace, fullLayout);
88868 newPointData.aLabel = labels.aLabel;
88869 newPointData.bLabel = labels.bLabel;
88870 newPointData.cLabel = labels.cLabel;
88871
88872 var hoverinfo = cdi.hi || trace.hoverinfo;
88873 var text = [];
88874 function textPart(ax, val) {
88875 text.push(ax._hovertitle + ': ' + val);
88876 }
88877 if(!trace.hovertemplate) {
88878 var parts = hoverinfo.split('+');
88879 if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'c'];
88880 if(parts.indexOf('a') !== -1) textPart(subplot.aaxis, newPointData.aLabel);
88881 if(parts.indexOf('b') !== -1) textPart(subplot.baxis, newPointData.bLabel);
88882 if(parts.indexOf('c') !== -1) textPart(subplot.caxis, newPointData.cLabel);
88883 }
88884 newPointData.extraText = text.join('<br>');
88885 newPointData.hovertemplate = trace.hovertemplate;
88886 return scatterPointData;
88887};
88888
88889},{"../scatter/hover":400}],422:[function(_dereq_,module,exports){
88890/**
88891* Copyright 2012-2020, Plotly, Inc.
88892* All rights reserved.
88893*
88894* This source code is licensed under the MIT license found in the
88895* LICENSE file in the root directory of this source tree.
88896*/
88897
88898'use strict';
88899
88900module.exports = {
88901 attributes: _dereq_('./attributes'),
88902 supplyDefaults: _dereq_('./defaults'),
88903 colorbar: _dereq_('../scatter/marker_colorbar'),
88904 formatLabels: _dereq_('./format_labels'),
88905 calc: _dereq_('./calc'),
88906 plot: _dereq_('./plot'),
88907 style: _dereq_('../scatter/style').style,
88908 styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
88909 hoverPoints: _dereq_('./hover'),
88910 selectPoints: _dereq_('../scatter/select'),
88911 eventData: _dereq_('./event_data'),
88912
88913 moduleType: 'trace',
88914 name: 'scatterternary',
88915 basePlotModule: _dereq_('../../plots/ternary'),
88916 categories: ['ternary', 'symbols', 'showLegend', 'scatter-like'],
88917 meta: {
88918
88919
88920 }
88921};
88922
88923},{"../../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){
88924/**
88925* Copyright 2012-2020, Plotly, Inc.
88926* All rights reserved.
88927*
88928* This source code is licensed under the MIT license found in the
88929* LICENSE file in the root directory of this source tree.
88930*/
88931
88932
88933'use strict';
88934
88935var scatterPlot = _dereq_('../scatter/plot');
88936
88937module.exports = function plot(gd, ternary, moduleCalcData) {
88938 var plotContainer = ternary.plotContainer;
88939
88940 // remove all nodes inside the scatter layer
88941 plotContainer.select('.scatterlayer').selectAll('*').remove();
88942
88943 // mimic cartesian plotinfo
88944 var plotinfo = {
88945 xaxis: ternary.xaxis,
88946 yaxis: ternary.yaxis,
88947 plot: plotContainer,
88948 layerClipId: ternary._hasClipOnAxisFalse ? ternary.clipIdRelative : null
88949 };
88950
88951 var scatterLayer = ternary.layers.frontplot.select('g.scatterlayer');
88952
88953 scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer);
88954};
88955
88956},{"../scatter/plot":409}],424:[function(_dereq_,module,exports){
88957/**
88958* Copyright 2012-2020, Plotly, Inc.
88959* All rights reserved.
88960*
88961* This source code is licensed under the MIT license found in the
88962* LICENSE file in the root directory of this source tree.
88963*/
88964
88965'use strict';
88966
88967var boxAttrs = _dereq_('../box/attributes');
88968var extendFlat = _dereq_('../../lib/extend').extendFlat;
88969
88970module.exports = {
88971 y: boxAttrs.y,
88972 x: boxAttrs.x,
88973 x0: boxAttrs.x0,
88974 y0: boxAttrs.y0,
88975 name: extendFlat({}, boxAttrs.name, {
88976
88977 }),
88978 orientation: extendFlat({}, boxAttrs.orientation, {
88979
88980 }),
88981
88982 bandwidth: {
88983 valType: 'number',
88984 min: 0,
88985
88986 editType: 'calc',
88987
88988 },
88989
88990 scalegroup: {
88991 valType: 'string',
88992
88993 dflt: '',
88994 editType: 'calc',
88995
88996 },
88997 scalemode: {
88998 valType: 'enumerated',
88999 values: ['width', 'count'],
89000 dflt: 'width',
89001
89002 editType: 'calc',
89003
89004 },
89005
89006 spanmode: {
89007 valType: 'enumerated',
89008 values: ['soft', 'hard', 'manual'],
89009 dflt: 'soft',
89010
89011 editType: 'calc',
89012
89013 },
89014 span: {
89015 valType: 'info_array',
89016 items: [
89017 {valType: 'any', editType: 'calc'},
89018 {valType: 'any', editType: 'calc'}
89019 ],
89020
89021 editType: 'calc',
89022
89023 },
89024
89025 line: {
89026 color: {
89027 valType: 'color',
89028
89029 editType: 'style',
89030
89031 },
89032 width: {
89033 valType: 'number',
89034
89035 min: 0,
89036 dflt: 2,
89037 editType: 'style',
89038
89039 },
89040 editType: 'plot'
89041 },
89042 fillcolor: boxAttrs.fillcolor,
89043
89044 points: extendFlat({}, boxAttrs.boxpoints, {
89045
89046 }),
89047 jitter: extendFlat({}, boxAttrs.jitter, {
89048
89049 }),
89050 pointpos: extendFlat({}, boxAttrs.pointpos, {
89051
89052 }),
89053
89054 width: extendFlat({}, boxAttrs.width, {
89055
89056 }),
89057
89058 marker: boxAttrs.marker,
89059 text: boxAttrs.text,
89060 hovertext: boxAttrs.hovertext,
89061 hovertemplate: boxAttrs.hovertemplate,
89062
89063 box: {
89064 visible: {
89065 valType: 'boolean',
89066 dflt: false,
89067
89068 editType: 'plot',
89069
89070 },
89071 width: {
89072 valType: 'number',
89073 min: 0,
89074 max: 1,
89075 dflt: 0.25,
89076
89077 editType: 'plot',
89078
89079 },
89080 fillcolor: {
89081 valType: 'color',
89082
89083 editType: 'style',
89084
89085 },
89086 line: {
89087 color: {
89088 valType: 'color',
89089
89090 editType: 'style',
89091
89092 },
89093 width: {
89094 valType: 'number',
89095 min: 0,
89096
89097 editType: 'style',
89098
89099 },
89100 editType: 'style'
89101 },
89102 editType: 'plot'
89103 },
89104
89105 meanline: {
89106 visible: {
89107 valType: 'boolean',
89108 dflt: false,
89109
89110 editType: 'plot',
89111
89112 },
89113 color: {
89114 valType: 'color',
89115
89116 editType: 'style',
89117
89118 },
89119 width: {
89120 valType: 'number',
89121 min: 0,
89122
89123 editType: 'style',
89124
89125 },
89126 editType: 'plot'
89127 },
89128
89129 side: {
89130 valType: 'enumerated',
89131 values: ['both', 'positive', 'negative'],
89132 dflt: 'both',
89133
89134 editType: 'calc',
89135
89136 },
89137
89138 offsetgroup: boxAttrs.offsetgroup,
89139 alignmentgroup: boxAttrs.alignmentgroup,
89140
89141 selected: boxAttrs.selected,
89142 unselected: boxAttrs.unselected,
89143
89144 hoveron: {
89145 valType: 'flaglist',
89146 flags: ['violins', 'points', 'kde'],
89147 dflt: 'violins+points+kde',
89148 extras: ['all'],
89149
89150 editType: 'style',
89151
89152 }
89153};
89154
89155},{"../../lib/extend":173,"../box/attributes":296}],425:[function(_dereq_,module,exports){
89156/**
89157* Copyright 2012-2020, Plotly, Inc.
89158* All rights reserved.
89159*
89160* This source code is licensed under the MIT license found in the
89161* LICENSE file in the root directory of this source tree.
89162*/
89163
89164'use strict';
89165
89166var Lib = _dereq_('../../lib');
89167var Axes = _dereq_('../../plots/cartesian/axes');
89168var boxCalc = _dereq_('../box/calc');
89169var helpers = _dereq_('./helpers');
89170var BADNUM = _dereq_('../../constants/numerical').BADNUM;
89171
89172module.exports = function calc(gd, trace) {
89173 var cd = boxCalc(gd, trace);
89174
89175 if(cd[0].t.empty) return cd;
89176
89177 var fullLayout = gd._fullLayout;
89178 var valAxis = Axes.getFromId(
89179 gd,
89180 trace[trace.orientation === 'h' ? 'xaxis' : 'yaxis']
89181 );
89182
89183 var spanMin = Infinity;
89184 var spanMax = -Infinity;
89185 var maxKDE = 0;
89186 var maxCount = 0;
89187
89188 for(var i = 0; i < cd.length; i++) {
89189 var cdi = cd[i];
89190 var vals = cdi.pts.map(helpers.extractVal);
89191
89192 var bandwidth = cdi.bandwidth = calcBandwidth(trace, cdi, vals);
89193 var span = cdi.span = calcSpan(trace, cdi, valAxis, bandwidth);
89194
89195 if(cdi.min === cdi.max && bandwidth === 0) {
89196 // if span is zero and bandwidth is zero, we want a violin with zero width
89197 span = cdi.span = [cdi.min, cdi.max];
89198 cdi.density = [{v: 1, t: span[0]}];
89199 cdi.bandwidth = bandwidth;
89200 maxKDE = Math.max(maxKDE, 1);
89201 } else {
89202 // step that well covers the bandwidth and is multiple of span distance
89203 var dist = span[1] - span[0];
89204 var n = Math.ceil(dist / (bandwidth / 3));
89205 var step = dist / n;
89206
89207 if(!isFinite(step) || !isFinite(n)) {
89208 Lib.error('Something went wrong with computing the violin span');
89209 cd[0].t.empty = true;
89210 return cd;
89211 }
89212
89213 var kde = helpers.makeKDE(cdi, trace, vals);
89214 cdi.density = new Array(n);
89215
89216 for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) {
89217 var v = kde(t);
89218 cdi.density[k] = {v: v, t: t};
89219 maxKDE = Math.max(maxKDE, v);
89220 }
89221 }
89222
89223 maxCount = Math.max(maxCount, vals.length);
89224 spanMin = Math.min(spanMin, span[0]);
89225 spanMax = Math.max(spanMax, span[1]);
89226 }
89227
89228 var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true});
89229 trace._extremes[valAxis._id] = extremes;
89230
89231 if(trace.width) {
89232 cd[0].t.maxKDE = maxKDE;
89233 } else {
89234 var violinScaleGroupStats = fullLayout._violinScaleGroupStats;
89235 var scaleGroup = trace.scalegroup;
89236 var groupStats = violinScaleGroupStats[scaleGroup];
89237
89238 if(groupStats) {
89239 groupStats.maxKDE = Math.max(groupStats.maxKDE, maxKDE);
89240 groupStats.maxCount = Math.max(groupStats.maxCount, maxCount);
89241 } else {
89242 violinScaleGroupStats[scaleGroup] = {
89243 maxKDE: maxKDE,
89244 maxCount: maxCount
89245 };
89246 }
89247 }
89248
89249 cd[0].t.labels.kde = Lib._(gd, 'kde:');
89250
89251 return cd;
89252};
89253
89254// Default to Silveman's rule of thumb
89255// - https://stats.stackexchange.com/a/6671
89256// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator
89257// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py
89258function silvermanRule(len, ssd, iqr) {
89259 var a = Math.min(ssd, iqr / 1.349);
89260 return 1.059 * a * Math.pow(len, -0.2);
89261}
89262
89263function calcBandwidth(trace, cdi, vals) {
89264 var span = cdi.max - cdi.min;
89265
89266 // If span is zero
89267 if(!span) {
89268 if(trace.bandwidth) {
89269 return trace.bandwidth;
89270 } else {
89271 // if span is zero and no bandwidth is specified
89272 // it returns zero bandwidth which is a special case
89273 return 0;
89274 }
89275 }
89276
89277 // Limit how small the bandwidth can be.
89278 //
89279 // Silverman's rule of thumb can be "very" small
89280 // when IQR does a poor job at describing the spread
89281 // of the distribution.
89282 // We also want to limit custom bandwidths
89283 // to not blow up kde computations.
89284
89285 if(trace.bandwidth) {
89286 return Math.max(trace.bandwidth, span / 1e4);
89287 } else {
89288 var len = vals.length;
89289 var ssd = Lib.stdev(vals, len - 1, cdi.mean);
89290 return Math.max(
89291 silvermanRule(len, ssd, cdi.q3 - cdi.q1),
89292 span / 100
89293 );
89294 }
89295}
89296
89297function calcSpan(trace, cdi, valAxis, bandwidth) {
89298 var spanmode = trace.spanmode;
89299 var spanIn = trace.span || [];
89300 var spanTight = [cdi.min, cdi.max];
89301 var spanLoose = [cdi.min - 2 * bandwidth, cdi.max + 2 * bandwidth];
89302 var spanOut;
89303
89304 function calcSpanItem(index) {
89305 var s = spanIn[index];
89306 var sc = valAxis.type === 'multicategory' ?
89307 valAxis.r2c(s) :
89308 valAxis.d2c(s, 0, trace[cdi.valLetter + 'calendar']);
89309 return sc === BADNUM ? spanLoose[index] : sc;
89310 }
89311
89312 if(spanmode === 'soft') {
89313 spanOut = spanLoose;
89314 } else if(spanmode === 'hard') {
89315 spanOut = spanTight;
89316 } else {
89317 spanOut = [calcSpanItem(0), calcSpanItem(1)];
89318 }
89319
89320 // to reuse the equal-range-item block
89321 var dummyAx = {
89322 type: 'linear',
89323 range: spanOut
89324 };
89325 Axes.setConvert(dummyAx);
89326 dummyAx.cleanRange();
89327
89328 return spanOut;
89329}
89330
89331},{"../../constants/numerical":158,"../../lib":178,"../../plots/cartesian/axes":222,"../box/calc":297,"./helpers":428}],426:[function(_dereq_,module,exports){
89332/**
89333* Copyright 2012-2020, Plotly, Inc.
89334* All rights reserved.
89335*
89336* This source code is licensed under the MIT license found in the
89337* LICENSE file in the root directory of this source tree.
89338*/
89339
89340'use strict';
89341
89342var setPositionOffset = _dereq_('../box/cross_trace_calc').setPositionOffset;
89343var orientations = ['v', 'h'];
89344
89345module.exports = function crossTraceCalc(gd, plotinfo) {
89346 var calcdata = gd.calcdata;
89347 var xa = plotinfo.xaxis;
89348 var ya = plotinfo.yaxis;
89349
89350 for(var i = 0; i < orientations.length; i++) {
89351 var orientation = orientations[i];
89352 var posAxis = orientation === 'h' ? ya : xa;
89353 var violinList = [];
89354
89355 for(var j = 0; j < calcdata.length; j++) {
89356 var cd = calcdata[j];
89357 var t = cd[0].t;
89358 var trace = cd[0].trace;
89359
89360 if(trace.visible === true && trace.type === 'violin' &&
89361 !t.empty &&
89362 trace.orientation === orientation &&
89363 trace.xaxis === xa._id &&
89364 trace.yaxis === ya._id
89365 ) {
89366 violinList.push(j);
89367 }
89368 }
89369
89370 setPositionOffset('violin', gd, violinList, posAxis);
89371 }
89372};
89373
89374},{"../box/cross_trace_calc":298}],427:[function(_dereq_,module,exports){
89375/**
89376* Copyright 2012-2020, Plotly, Inc.
89377* All rights reserved.
89378*
89379* This source code is licensed under the MIT license found in the
89380* LICENSE file in the root directory of this source tree.
89381*/
89382
89383'use strict';
89384
89385var Lib = _dereq_('../../lib');
89386var Color = _dereq_('../../components/color');
89387
89388var boxDefaults = _dereq_('../box/defaults');
89389var attributes = _dereq_('./attributes');
89390
89391module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
89392 function coerce(attr, dflt) {
89393 return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
89394 }
89395 function coerce2(attr, dflt) {
89396 return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
89397 }
89398
89399 boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout);
89400 if(traceOut.visible === false) return;
89401
89402 coerce('bandwidth');
89403 coerce('side');
89404
89405 var width = coerce('width');
89406 if(!width) {
89407 coerce('scalegroup', traceOut.name);
89408 coerce('scalemode');
89409 }
89410
89411 var span = coerce('span');
89412 var spanmodeDflt;
89413 if(Array.isArray(span)) spanmodeDflt = 'manual';
89414 coerce('spanmode', spanmodeDflt);
89415
89416 var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor);
89417 var lineWidth = coerce('line.width');
89418 var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
89419
89420 boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''});
89421
89422 var boxWidth = coerce2('box.width');
89423 var boxFillColor = coerce2('box.fillcolor', fillColor);
89424 var boxLineColor = coerce2('box.line.color', lineColor);
89425 var boxLineWidth = coerce2('box.line.width', lineWidth);
89426 var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth));
89427 if(!boxVisible) traceOut.box = {visible: false};
89428
89429 var meanLineColor = coerce2('meanline.color', lineColor);
89430 var meanLineWidth = coerce2('meanline.width', lineWidth);
89431 var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
89432 if(!meanLineVisible) traceOut.meanline = {visible: false};
89433};
89434
89435},{"../../components/color":52,"../../lib":178,"../box/defaults":299,"./attributes":424}],428:[function(_dereq_,module,exports){
89436/**
89437* Copyright 2012-2020, Plotly, Inc.
89438* All rights reserved.
89439*
89440* This source code is licensed under the MIT license found in the
89441* LICENSE file in the root directory of this source tree.
89442*/
89443
89444'use strict';
89445
89446var Lib = _dereq_('../../lib');
89447
89448// Maybe add kernels more down the road,
89449// but note that the default `spanmode: 'soft'` bounds might have
89450// to become kernel-dependent
89451var kernels = {
89452 gaussian: function(v) {
89453 return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * v * v);
89454 }
89455};
89456
89457exports.makeKDE = function(calcItem, trace, vals) {
89458 var len = vals.length;
89459 var kernel = kernels.gaussian;
89460 var bandwidth = calcItem.bandwidth;
89461 var factor = 1 / (len * bandwidth);
89462
89463 // don't use Lib.aggNums to skip isNumeric checks
89464 return function(x) {
89465 var sum = 0;
89466 for(var i = 0; i < len; i++) {
89467 sum += kernel((x - vals[i]) / bandwidth);
89468 }
89469 return factor * sum;
89470 };
89471};
89472
89473exports.getPositionOnKdePath = function(calcItem, trace, valuePx) {
89474 var posLetter, valLetter;
89475
89476 if(trace.orientation === 'h') {
89477 posLetter = 'y';
89478 valLetter = 'x';
89479 } else {
89480 posLetter = 'x';
89481 valLetter = 'y';
89482 }
89483
89484 var pointOnPath = Lib.findPointOnPath(
89485 calcItem.path,
89486 valuePx,
89487 valLetter,
89488 {pathLength: calcItem.pathLength}
89489 );
89490
89491 var posCenterPx = calcItem.posCenterPx;
89492 var posOnPath0 = pointOnPath[posLetter];
89493 var posOnPath1 = trace.side === 'both' ?
89494 2 * posCenterPx - posOnPath0 :
89495 posCenterPx;
89496
89497 return [posOnPath0, posOnPath1];
89498};
89499
89500exports.getKdeValue = function(calcItem, trace, valueDist) {
89501 var vals = calcItem.pts.map(exports.extractVal);
89502 var kde = exports.makeKDE(calcItem, trace, vals);
89503 return kde(valueDist) / calcItem.posDensityScale;
89504};
89505
89506exports.extractVal = function(o) { return o.v; };
89507
89508},{"../../lib":178}],429:[function(_dereq_,module,exports){
89509/**
89510* Copyright 2012-2020, Plotly, Inc.
89511* All rights reserved.
89512*
89513* This source code is licensed under the MIT license found in the
89514* LICENSE file in the root directory of this source tree.
89515*/
89516
89517'use strict';
89518
89519var Lib = _dereq_('../../lib');
89520var Axes = _dereq_('../../plots/cartesian/axes');
89521var boxHoverPoints = _dereq_('../box/hover');
89522var helpers = _dereq_('./helpers');
89523
89524module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) {
89525 var cd = pointData.cd;
89526 var trace = cd[0].trace;
89527 var hoveron = trace.hoveron;
89528 var hasHoveronViolins = hoveron.indexOf('violins') !== -1;
89529 var hasHoveronKDE = hoveron.indexOf('kde') !== -1;
89530 var closeData = [];
89531 var closePtData;
89532 var violinLineAttrs;
89533
89534 if(hasHoveronViolins || hasHoveronKDE) {
89535 var closeBoxData = boxHoverPoints.hoverOnBoxes(pointData, xval, yval, hovermode);
89536
89537 if(hasHoveronKDE && closeBoxData.length > 0) {
89538 var xa = pointData.xa;
89539 var ya = pointData.ya;
89540 var pLetter, vLetter, pAxis, vAxis, vVal;
89541
89542 if(trace.orientation === 'h') {
89543 vVal = xval;
89544 pLetter = 'y';
89545 pAxis = ya;
89546 vLetter = 'x';
89547 vAxis = xa;
89548 } else {
89549 vVal = yval;
89550 pLetter = 'x';
89551 pAxis = xa;
89552 vLetter = 'y';
89553 vAxis = ya;
89554 }
89555
89556 var di = cd[pointData.index];
89557
89558 if(vVal >= di.span[0] && vVal <= di.span[1]) {
89559 var kdePointData = Lib.extendFlat({}, pointData);
89560 var vValPx = vAxis.c2p(vVal, true);
89561 var kdeVal = helpers.getKdeValue(di, trace, vVal);
89562 var pOnPath = helpers.getPositionOnKdePath(di, trace, vValPx);
89563 var paOffset = pAxis._offset;
89564 var paLength = pAxis._length;
89565
89566 kdePointData[pLetter + '0'] = pOnPath[0];
89567 kdePointData[pLetter + '1'] = pOnPath[1];
89568 kdePointData[vLetter + '0'] = kdePointData[vLetter + '1'] = vValPx;
89569 kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3);
89570
89571 // move the spike to the KDE point
89572 kdePointData.spikeDistance = closeBoxData[0].spikeDistance;
89573 var spikePosAttr = pLetter + 'Spike';
89574 kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr];
89575 closeBoxData[0].spikeDistance = undefined;
89576 closeBoxData[0][spikePosAttr] = undefined;
89577
89578 // no hovertemplate support yet
89579 kdePointData.hovertemplate = false;
89580
89581 closeData.push(kdePointData);
89582
89583 violinLineAttrs = {stroke: pointData.color};
89584 violinLineAttrs[pLetter + '1'] = Lib.constrain(paOffset + pOnPath[0], paOffset, paOffset + paLength);
89585 violinLineAttrs[pLetter + '2'] = Lib.constrain(paOffset + pOnPath[1], paOffset, paOffset + paLength);
89586 violinLineAttrs[vLetter + '1'] = violinLineAttrs[vLetter + '2'] = vAxis._offset + vValPx;
89587 }
89588 }
89589
89590 if(hasHoveronViolins) {
89591 closeData = closeData.concat(closeBoxData);
89592 }
89593 }
89594
89595 if(hoveron.indexOf('points') !== -1) {
89596 closePtData = boxHoverPoints.hoverOnPoints(pointData, xval, yval);
89597 }
89598
89599 // update violin line (if any)
89600 var violinLine = hoverLayer.selectAll('.violinline-' + trace.uid)
89601 .data(violinLineAttrs ? [0] : []);
89602 violinLine.enter().append('line')
89603 .classed('violinline-' + trace.uid, true)
89604 .attr('stroke-width', 1.5);
89605 violinLine.exit().remove();
89606 violinLine.attr(violinLineAttrs);
89607
89608 // same combine logic as box hoverPoints
89609 if(hovermode === 'closest') {
89610 if(closePtData) return [closePtData];
89611 return closeData;
89612 }
89613 if(closePtData) {
89614 closeData.push(closePtData);
89615 return closeData;
89616 }
89617 return closeData;
89618};
89619
89620},{"../../lib":178,"../../plots/cartesian/axes":222,"../box/hover":301,"./helpers":428}],430:[function(_dereq_,module,exports){
89621/**
89622* Copyright 2012-2020, Plotly, Inc.
89623* All rights reserved.
89624*
89625* This source code is licensed under the MIT license found in the
89626* LICENSE file in the root directory of this source tree.
89627*/
89628
89629'use strict';
89630
89631module.exports = {
89632 attributes: _dereq_('./attributes'),
89633 layoutAttributes: _dereq_('./layout_attributes'),
89634 supplyDefaults: _dereq_('./defaults'),
89635 crossTraceDefaults: _dereq_('../box/defaults').crossTraceDefaults,
89636 supplyLayoutDefaults: _dereq_('./layout_defaults'),
89637 calc: _dereq_('./calc'),
89638 crossTraceCalc: _dereq_('./cross_trace_calc'),
89639 plot: _dereq_('./plot'),
89640 style: _dereq_('./style'),
89641 styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
89642 hoverPoints: _dereq_('./hover'),
89643 selectPoints: _dereq_('../box/select'),
89644
89645 moduleType: 'trace',
89646 name: 'violin',
89647 basePlotModule: _dereq_('../../plots/cartesian'),
89648 categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'violinLayout', 'zoomScale'],
89649 meta: {
89650
89651 }
89652};
89653
89654},{"../../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){
89655/**
89656* Copyright 2012-2020, Plotly, Inc.
89657* All rights reserved.
89658*
89659* This source code is licensed under the MIT license found in the
89660* LICENSE file in the root directory of this source tree.
89661*/
89662
89663'use strict';
89664
89665var boxLayoutAttrs = _dereq_('../box/layout_attributes');
89666var extendFlat = _dereq_('../../lib').extendFlat;
89667
89668module.exports = {
89669 violinmode: extendFlat({}, boxLayoutAttrs.boxmode, {
89670
89671 }),
89672 violingap: extendFlat({}, boxLayoutAttrs.boxgap, {
89673
89674 }),
89675 violingroupgap: extendFlat({}, boxLayoutAttrs.boxgroupgap, {
89676
89677 })
89678};
89679
89680},{"../../lib":178,"../box/layout_attributes":303}],432:[function(_dereq_,module,exports){
89681/**
89682* Copyright 2012-2020, Plotly, Inc.
89683* All rights reserved.
89684*
89685* This source code is licensed under the MIT license found in the
89686* LICENSE file in the root directory of this source tree.
89687*/
89688
89689'use strict';
89690
89691var Lib = _dereq_('../../lib');
89692var layoutAttributes = _dereq_('./layout_attributes');
89693var boxLayoutDefaults = _dereq_('../box/layout_defaults');
89694
89695module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
89696 function coerce(attr, dflt) {
89697 return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
89698 }
89699 boxLayoutDefaults._supply(layoutIn, layoutOut, fullData, coerce, 'violin');
89700};
89701
89702},{"../../lib":178,"../box/layout_defaults":304,"./layout_attributes":431}],433:[function(_dereq_,module,exports){
89703/**
89704* Copyright 2012-2020, Plotly, Inc.
89705* All rights reserved.
89706*
89707* This source code is licensed under the MIT license found in the
89708* LICENSE file in the root directory of this source tree.
89709*/
89710
89711'use strict';
89712
89713var d3 = _dereq_('d3');
89714var Lib = _dereq_('../../lib');
89715var Drawing = _dereq_('../../components/drawing');
89716
89717var boxPlot = _dereq_('../box/plot');
89718var linePoints = _dereq_('../scatter/line_points');
89719var helpers = _dereq_('./helpers');
89720
89721module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
89722 var fullLayout = gd._fullLayout;
89723 var xa = plotinfo.xaxis;
89724 var ya = plotinfo.yaxis;
89725
89726 function makePath(pts) {
89727 var segments = linePoints(pts, {
89728 xaxis: xa,
89729 yaxis: ya,
89730 connectGaps: true,
89731 baseTolerance: 0.75,
89732 shape: 'spline',
89733 simplify: true,
89734 linearized: true
89735 });
89736 return Drawing.smoothopen(segments[0], 1);
89737 }
89738
89739 Lib.makeTraceGroups(violinLayer, cdViolins, 'trace violins').each(function(cd) {
89740 var plotGroup = d3.select(this);
89741 var cd0 = cd[0];
89742 var t = cd0.t;
89743 var trace = cd0.trace;
89744
89745 if(trace.visible !== true || t.empty) {
89746 plotGroup.remove();
89747 return;
89748 }
89749
89750 var bPos = t.bPos;
89751 var bdPos = t.bdPos;
89752 var valAxis = plotinfo[t.valLetter + 'axis'];
89753 var posAxis = plotinfo[t.posLetter + 'axis'];
89754 var hasBothSides = trace.side === 'both';
89755 var hasPositiveSide = hasBothSides || trace.side === 'positive';
89756 var hasNegativeSide = hasBothSides || trace.side === 'negative';
89757
89758 var violins = plotGroup.selectAll('path.violin').data(Lib.identity);
89759
89760 violins.enter().append('path')
89761 .style('vector-effect', 'non-scaling-stroke')
89762 .attr('class', 'violin');
89763
89764 violins.exit().remove();
89765
89766 violins.each(function(d) {
89767 var pathSel = d3.select(this);
89768 var density = d.density;
89769 var len = density.length;
89770 var posCenter = posAxis.c2l(d.pos + bPos, true);
89771 var posCenterPx = posAxis.l2p(posCenter);
89772
89773 var scale;
89774 if(trace.width) {
89775 scale = t.maxKDE / bdPos;
89776 } else {
89777 var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
89778 scale = trace.scalemode === 'count' ?
89779 (groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) :
89780 groupStats.maxKDE / bdPos;
89781 }
89782
89783 var pathPos, pathNeg, path;
89784 var i, k, pts, pt;
89785
89786 if(hasPositiveSide) {
89787 pts = new Array(len);
89788 for(i = 0; i < len; i++) {
89789 pt = pts[i] = {};
89790 pt[t.posLetter] = posCenter + (density[i].v / scale);
89791 pt[t.valLetter] = valAxis.c2l(density[i].t, true);
89792 }
89793 pathPos = makePath(pts);
89794 }
89795
89796 if(hasNegativeSide) {
89797 pts = new Array(len);
89798 for(k = 0, i = len - 1; k < len; k++, i--) {
89799 pt = pts[k] = {};
89800 pt[t.posLetter] = posCenter - (density[i].v / scale);
89801 pt[t.valLetter] = valAxis.c2l(density[i].t, true);
89802 }
89803 pathNeg = makePath(pts);
89804 }
89805
89806 if(hasBothSides) {
89807 path = pathPos + 'L' + pathNeg.substr(1) + 'Z';
89808 } else {
89809 var startPt = [posCenterPx, valAxis.c2p(density[0].t)];
89810 var endPt = [posCenterPx, valAxis.c2p(density[len - 1].t)];
89811
89812 if(trace.orientation === 'h') {
89813 startPt.reverse();
89814 endPt.reverse();
89815 }
89816
89817 if(hasPositiveSide) {
89818 path = 'M' + startPt + 'L' + pathPos.substr(1) + 'L' + endPt;
89819 } else {
89820 path = 'M' + endPt + 'L' + pathNeg.substr(1) + 'L' + startPt;
89821 }
89822 }
89823 pathSel.attr('d', path);
89824
89825 // save a few things used in getPositionOnKdePath, getKdeValue
89826 // on hover and for meanline draw block below
89827 d.posCenterPx = posCenterPx;
89828 d.posDensityScale = scale * bdPos;
89829 d.path = pathSel.node();
89830 d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1);
89831 });
89832
89833 var boxAttrs = trace.box;
89834 var boxWidth = boxAttrs.width;
89835 var boxLineWidth = (boxAttrs.line || {}).width;
89836 var bdPosScaled;
89837 var bPosPxOffset;
89838
89839 if(hasBothSides) {
89840 bdPosScaled = bdPos * boxWidth;
89841 bPosPxOffset = 0;
89842 } else if(hasPositiveSide) {
89843 bdPosScaled = [0, bdPos * boxWidth / 2];
89844 bPosPxOffset = boxLineWidth * {x: 1, y: -1}[t.posLetter];
89845 } else {
89846 bdPosScaled = [bdPos * boxWidth / 2, 0];
89847 bPosPxOffset = boxLineWidth * {x: -1, y: 1}[t.posLetter];
89848 }
89849
89850 // inner box
89851 boxPlot.plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, {
89852 bPos: bPos,
89853 bdPos: bdPosScaled,
89854 bPosPxOffset: bPosPxOffset
89855 });
89856
89857 // meanline insider box
89858 boxPlot.plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, {
89859 bPos: bPos,
89860 bdPos: bdPosScaled,
89861 bPosPxOffset: bPosPxOffset
89862 });
89863
89864 var fn;
89865 if(!trace.box.visible && trace.meanline.visible) {
89866 fn = Lib.identity;
89867 }
89868
89869 // N.B. use different class name than boxPlot.plotBoxMean,
89870 // to avoid selectAll conflict
89871 var meanPaths = plotGroup.selectAll('path.meanline').data(fn || []);
89872 meanPaths.enter().append('path')
89873 .attr('class', 'meanline')
89874 .style('fill', 'none')
89875 .style('vector-effect', 'non-scaling-stroke');
89876 meanPaths.exit().remove();
89877 meanPaths.each(function(d) {
89878 var v = valAxis.c2p(d.mean, true);
89879 var p = helpers.getPositionOnKdePath(d, trace, v);
89880
89881 d3.select(this).attr('d',
89882 trace.orientation === 'h' ?
89883 'M' + v + ',' + p[0] + 'V' + p[1] :
89884 'M' + p[0] + ',' + v + 'H' + p[1]
89885 );
89886 });
89887
89888 boxPlot.plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
89889 });
89890};
89891
89892},{"../../components/drawing":74,"../../lib":178,"../box/plot":305,"../scatter/line_points":403,"./helpers":428,"d3":16}],434:[function(_dereq_,module,exports){
89893/**
89894* Copyright 2012-2020, Plotly, Inc.
89895* All rights reserved.
89896*
89897* This source code is licensed under the MIT license found in the
89898* LICENSE file in the root directory of this source tree.
89899*/
89900
89901'use strict';
89902
89903var d3 = _dereq_('d3');
89904var Color = _dereq_('../../components/color');
89905var stylePoints = _dereq_('../scatter/style').stylePoints;
89906
89907module.exports = function style(gd) {
89908 var s = d3.select(gd).selectAll('g.trace.violins');
89909
89910 s.style('opacity', function(d) { return d[0].trace.opacity; });
89911
89912 s.each(function(d) {
89913 var trace = d[0].trace;
89914 var sel = d3.select(this);
89915 var box = trace.box || {};
89916 var boxLine = box.line || {};
89917 var meanline = trace.meanline || {};
89918 var meanLineWidth = meanline.width;
89919
89920 sel.selectAll('path.violin')
89921 .style('stroke-width', trace.line.width + 'px')
89922 .call(Color.stroke, trace.line.color)
89923 .call(Color.fill, trace.fillcolor);
89924
89925 sel.selectAll('path.box')
89926 .style('stroke-width', boxLine.width + 'px')
89927 .call(Color.stroke, boxLine.color)
89928 .call(Color.fill, box.fillcolor);
89929
89930 var meanLineStyle = {
89931 'stroke-width': meanLineWidth + 'px',
89932 'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px'
89933 };
89934
89935 sel.selectAll('path.mean')
89936 .style(meanLineStyle)
89937 .call(Color.stroke, meanline.color);
89938
89939 sel.selectAll('path.meanline')
89940 .style(meanLineStyle)
89941 .call(Color.stroke, meanline.color);
89942
89943 stylePoints(sel, trace, gd);
89944 });
89945};
89946
89947},{"../../components/color":52,"../scatter/style":412,"d3":16}],435:[function(_dereq_,module,exports){
89948/**
89949* Copyright 2012-2020, Plotly, Inc.
89950* All rights reserved.
89951*
89952* This source code is licensed under the MIT license found in the
89953* LICENSE file in the root directory of this source tree.
89954*/
89955
89956'use strict';
89957
89958// package version injected by `npm run preprocess`
89959exports.version = '1.54.3';
89960
89961},{}]},{},[11])(11)
89962});