plotly.js 2.6.2 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +3 -3
  3. package/dist/README.md +26 -26
  4. package/dist/plot-schema.json +1015 -407
  5. package/dist/plotly-basic.js +568 -225
  6. package/dist/plotly-basic.min.js +4 -4
  7. package/dist/plotly-cartesian.js +1029 -371
  8. package/dist/plotly-cartesian.min.js +3 -3
  9. package/dist/plotly-finance.js +618 -227
  10. package/dist/plotly-finance.min.js +4 -4
  11. package/dist/plotly-geo-assets.js +2 -2
  12. package/dist/plotly-geo.js +564 -223
  13. package/dist/plotly-geo.min.js +2 -2
  14. package/dist/plotly-gl2d.js +580 -224
  15. package/dist/plotly-gl2d.min.js +2 -2
  16. package/dist/plotly-gl3d.js +564 -223
  17. package/dist/plotly-gl3d.min.js +2 -2
  18. package/dist/plotly-mapbox.js +570 -226
  19. package/dist/plotly-mapbox.min.js +2 -2
  20. package/dist/plotly-strict.js +1253 -592
  21. package/dist/plotly-strict.min.js +3 -3
  22. package/dist/plotly-with-meta.js +1345 -654
  23. package/dist/plotly.js +1307 -646
  24. package/dist/plotly.min.js +10 -10
  25. package/package.json +9 -9
  26. package/src/components/colorbar/attributes.js +29 -20
  27. package/src/components/colorbar/defaults.js +30 -8
  28. package/src/components/colorbar/draw.js +398 -141
  29. package/src/components/drawing/index.js +6 -3
  30. package/src/components/fx/hover.js +16 -17
  31. package/src/components/fx/hoverlabel_defaults.js +4 -2
  32. package/src/components/fx/layout_attributes.js +14 -4
  33. package/src/components/fx/layout_defaults.js +2 -0
  34. package/src/components/legend/attributes.js +7 -0
  35. package/src/components/legend/defaults.js +24 -7
  36. package/src/components/titles/index.js +8 -2
  37. package/src/plot_api/plot_api.js +38 -9
  38. package/src/plots/font_attributes.js +3 -0
  39. package/src/plots/layout_attributes.js +1 -0
  40. package/src/plots/mapbox/mapbox.js +6 -3
  41. package/src/plots/plots.js +7 -15
  42. package/src/traces/bar/plot.js +1 -1
  43. package/src/traces/contour/attributes.js +12 -0
  44. package/src/traces/contour/defaults.js +9 -1
  45. package/src/traces/heatmap/attributes.js +16 -0
  46. package/src/traces/heatmap/defaults.js +2 -0
  47. package/src/traces/heatmap/label_defaults.js +13 -0
  48. package/src/traces/heatmap/plot.js +203 -4
  49. package/src/traces/histogram/attributes.js +40 -0
  50. package/src/traces/histogram/defaults.js +11 -0
  51. package/src/traces/histogram2d/attributes.js +8 -0
  52. package/src/traces/histogram2d/defaults.js +4 -0
  53. package/src/traces/histogram2dcontour/attributes.js +3 -1
  54. package/src/traces/histogram2dcontour/defaults.js +8 -1
  55. package/src/traces/pie/calc.js +3 -1
  56. package/src/version.js +1 -1
  57. package/tasks/test_mock.js +1 -0
@@ -167,6 +167,21 @@ function makeColorBarData(gd) {
167
167
  }
168
168
 
169
169
  function drawColorBar(g, opts, gd) {
170
+ var isVertical = opts.orientation === 'v';
171
+ var len = opts.len;
172
+ var lenmode = opts.lenmode;
173
+ var thickness = opts.thickness;
174
+ var thicknessmode = opts.thicknessmode;
175
+ var outlinewidth = opts.outlinewidth;
176
+ var borderwidth = opts.borderwidth;
177
+ var bgcolor = opts.bgcolor;
178
+ var xanchor = opts.xanchor;
179
+ var yanchor = opts.yanchor;
180
+ var xpad = opts.xpad;
181
+ var ypad = opts.ypad;
182
+ var optsX = opts.x;
183
+ var optsY = isVertical ? opts.y : 1 - opts.y;
184
+
170
185
  var fullLayout = gd._fullLayout;
171
186
  var gs = fullLayout._size;
172
187
 
@@ -196,42 +211,64 @@ function drawColorBar(g, opts, gd) {
196
211
  // when the colorbar itself is pushing the margins.
197
212
  // but then the fractional size is calculated based on the
198
213
  // actual graph size, so that the axes will size correctly.
199
- var thickPx = Math.round(opts.thickness * (opts.thicknessmode === 'fraction' ? gs.w : 1));
200
- var thickFrac = thickPx / gs.w;
201
- var lenPx = Math.round(opts.len * (opts.lenmode === 'fraction' ? gs.h : 1));
202
- var lenFrac = lenPx / gs.h;
203
- var xpadFrac = opts.xpad / gs.w;
204
- var yExtraPx = (opts.borderwidth + opts.outlinewidth) / 2;
205
- var ypadFrac = opts.ypad / gs.h;
214
+ var thickPx = Math.round(thickness * (thicknessmode === 'fraction' ? (isVertical ? gs.w : gs.h) : 1));
215
+ var thickFrac = thickPx / (isVertical ? gs.w : gs.h);
216
+ var lenPx = Math.round(len * (lenmode === 'fraction' ? (isVertical ? gs.h : gs.w) : 1));
217
+ var lenFrac = lenPx / (isVertical ? gs.h : gs.w);
206
218
 
207
219
  // x positioning: do it initially just for left anchor,
208
220
  // then fix at the end (since we don't know the width yet)
209
- var xLeft = Math.round(opts.x * gs.w + opts.xpad);
221
+ var uPx = Math.round(isVertical ?
222
+ optsX * gs.w + xpad :
223
+ optsY * gs.h + ypad
224
+ );
225
+
226
+ var xRatio = {center: 0.5, right: 1}[xanchor] || 0;
227
+ var yRatio = {top: 1, middle: 0.5}[yanchor] || 0;
228
+
210
229
  // for dragging... this is getting a little muddled...
211
- var xLeftFrac = opts.x - thickFrac * ({center: 0.5, right: 1}[opts.xanchor] || 0);
230
+ var uFrac = isVertical ?
231
+ optsX - xRatio * thickFrac :
232
+ optsY - yRatio * thickFrac;
233
+
234
+ // y/x positioning (for v/h) we can do correctly from the start
235
+ var vFrac = isVertical ?
236
+ optsY - yRatio * lenFrac :
237
+ optsX - xRatio * lenFrac;
212
238
 
213
- // y positioning we can do correctly from the start
214
- var yBottomFrac = opts.y + lenFrac * (({top: -0.5, bottom: 0.5}[opts.yanchor] || 0) - 0.5);
215
- var yBottomPx = Math.round(gs.h * (1 - yBottomFrac));
216
- var yTopPx = yBottomPx - lenPx;
239
+ var vPx = Math.round(isVertical ?
240
+ gs.h * (1 - vFrac) :
241
+ gs.w * vFrac
242
+ );
217
243
 
218
244
  // stash a few things for makeEditable
219
245
  opts._lenFrac = lenFrac;
220
246
  opts._thickFrac = thickFrac;
221
- opts._xLeftFrac = xLeftFrac;
222
- opts._yBottomFrac = yBottomFrac;
247
+ opts._uFrac = uFrac;
248
+ opts._vFrac = vFrac;
223
249
 
224
250
  // stash mocked axis for contour label formatting
225
251
  var ax = opts._axis = mockColorBarAxis(gd, opts, zrange);
226
252
 
227
253
  // position can't go in through supplyDefaults
228
254
  // because that restricts it to [0,1]
229
- ax.position = opts.x + xpadFrac + thickFrac;
255
+ ax.position = thickFrac + (isVertical ?
256
+ optsX + xpad / gs.w :
257
+ optsY + ypad / gs.h
258
+ );
259
+
260
+ var topOrBottom = ['top', 'bottom'].indexOf(titleSide) !== -1;
261
+
262
+ if(isVertical && topOrBottom) {
263
+ ax.title.side = titleSide;
264
+ ax.titlex = optsX + xpad / gs.w;
265
+ ax.titley = vFrac + (title.side === 'top' ? lenFrac - ypad / gs.h : ypad / gs.h);
266
+ }
230
267
 
231
- if(['top', 'bottom'].indexOf(titleSide) !== -1) {
268
+ if(!isVertical && !topOrBottom) {
232
269
  ax.title.side = titleSide;
233
- ax.titlex = opts.x + xpadFrac;
234
- ax.titley = yBottomFrac + (title.side === 'top' ? lenFrac - ypadFrac : ypadFrac);
270
+ ax.titley = optsY + ypad / gs.h;
271
+ ax.titlex = vFrac + xpad / gs.w; // right side
235
272
  }
236
273
 
237
274
  if(line.color && opts.tickmode === 'auto') {
@@ -239,7 +276,7 @@ function drawColorBar(g, opts, gd) {
239
276
  ax.tick0 = levelsIn.start;
240
277
  var dtick = levelsIn.size;
241
278
  // expand if too many contours, so we don't get too many ticks
242
- var autoNtick = Lib.constrain((yBottomPx - yTopPx) / 50, 4, 15) + 1;
279
+ var autoNtick = Lib.constrain(lenPx / 50, 4, 15) + 1;
243
280
  var dtFactor = (zrange[1] - zrange[0]) / ((opts.nticks || autoNtick) * dtick);
244
281
  if(dtFactor > 1) {
245
282
  var dtexp = Math.pow(10, Math.floor(Math.log(dtFactor) / Math.LN10));
@@ -256,9 +293,12 @@ function drawColorBar(g, opts, gd) {
256
293
 
257
294
  // set domain after init, because we may want to
258
295
  // allow it outside [0,1]
259
- ax.domain = [
260
- yBottomFrac + ypadFrac,
261
- yBottomFrac + lenFrac - ypadFrac
296
+ ax.domain = isVertical ? [
297
+ vFrac + ypad / gs.h,
298
+ vFrac + lenFrac - ypad / gs.h
299
+ ] : [
300
+ vFrac + xpad / gs.w,
301
+ vFrac + lenFrac - xpad / gs.w
262
302
  ];
263
303
 
264
304
  ax.setScale();
@@ -268,9 +308,13 @@ function drawColorBar(g, opts, gd) {
268
308
  var titleCont = g.select('.' + cn.cbtitleunshift)
269
309
  .attr('transform', strTranslate(-Math.round(gs.l), -Math.round(gs.t)));
270
310
 
311
+ var ticklabelposition = ax.ticklabelposition;
312
+ var titleFontSize = ax.title.font.size;
313
+
271
314
  var axLayer = g.select('.' + cn.cbaxis);
272
315
  var titleEl;
273
316
  var titleHeight = 0;
317
+ var titleWidth = 0;
274
318
 
275
319
  function drawTitle(titleClass, titleOpts) {
276
320
  var dfltTitleOpts = {
@@ -295,58 +339,102 @@ function drawColorBar(g, opts, gd) {
295
339
  }
296
340
 
297
341
  function drawDummyTitle() {
298
- if(['top', 'bottom'].indexOf(titleSide) !== -1) {
299
- // draw the title so we know how much room it needs
300
- // when we squish the axis. This one only applies to
301
- // top or bottom titles, not right side.
302
- var x = gs.l + (opts.x + xpadFrac) * gs.w;
303
- var fontSize = ax.title.font.size;
304
- var y;
342
+ // draw the title so we know how much room it needs
343
+ // when we squish the axis.
344
+ // On vertical colorbars this only applies to top or bottom titles, not right side.
345
+ // On horizontal colorbars this only applies to right, etc.
346
+
347
+ if(
348
+ (isVertical && topOrBottom) ||
349
+ (!isVertical && !topOrBottom)
350
+ ) {
351
+ var x, y;
305
352
 
306
353
  if(titleSide === 'top') {
307
- y = (1 - (yBottomFrac + lenFrac - ypadFrac)) * gs.h +
308
- gs.t + 3 + fontSize * 0.75;
309
- } else {
310
- y = (1 - (yBottomFrac + ypadFrac)) * gs.h +
311
- gs.t - 3 - fontSize * 0.25;
354
+ x = xpad + gs.l + gs.w * optsX;
355
+ y = ypad + gs.t + gs.h * (1 - vFrac - lenFrac) + 3 + titleFontSize * 0.75;
356
+ }
357
+
358
+ if(titleSide === 'bottom') {
359
+ x = xpad + gs.l + gs.w * optsX;
360
+ y = ypad + gs.t + gs.h * (1 - vFrac) - 3 - titleFontSize * 0.25;
361
+ }
362
+
363
+ if(titleSide === 'right') {
364
+ y = ypad + gs.t + gs.h * optsY + 3 + titleFontSize * 0.75;
365
+ x = xpad + gs.l + gs.w * vFrac;
312
366
  }
367
+
313
368
  drawTitle(ax._id + 'title', {
314
- attributes: {x: x, y: y, 'text-anchor': 'start'}
369
+ attributes: {x: x, y: y, 'text-anchor': isVertical ? 'start' : 'middle'}
315
370
  });
316
371
  }
317
372
  }
318
373
 
319
374
  function drawCbTitle() {
320
- if(['top', 'bottom'].indexOf(titleSide) === -1) {
321
- var fontSize = ax.title.font.size;
322
- var y = ax._offset + ax._length / 2;
323
- var x = gs.l + (ax.position || 0) * gs.w + ((ax.side === 'right') ?
324
- 10 + fontSize * ((ax.showticklabels ? 1 : 0.5)) :
325
- -10 - fontSize * ((ax.showticklabels ? 0.5 : 0)));
326
-
327
- // the 'h' + is a hack to get around the fact that
328
- // convertToTspans rotates any 'y...' class by 90 degrees.
329
- // TODO: find a better way to control this.
330
- drawTitle('h' + ax._id + 'title', {
375
+ if(
376
+ (isVertical && !topOrBottom) ||
377
+ (!isVertical && topOrBottom)
378
+ ) {
379
+ var pos = ax.position || 0;
380
+ var mid = ax._offset + ax._length / 2;
381
+ var x, y;
382
+
383
+ if(titleSide === 'right') {
384
+ y = mid;
385
+ x = gs.l + gs.w * pos + 10 + titleFontSize * (
386
+ ax.showticklabels ? 1 : 0.5
387
+ );
388
+ } else {
389
+ x = mid;
390
+
391
+ if(titleSide === 'bottom') {
392
+ y = gs.t + gs.h * pos + 10 + (
393
+ ticklabelposition.indexOf('inside') === -1 ?
394
+ ax.tickfont.size :
395
+ 0
396
+ ) + (
397
+ ax.ticks !== 'intside' ?
398
+ opts.ticklen || 0 :
399
+ 0
400
+ );
401
+ }
402
+
403
+ if(titleSide === 'top') {
404
+ var nlines = title.text.split('<br>').length;
405
+ y = gs.t + gs.h * pos + 10 - thickPx - LINE_SPACING * titleFontSize * nlines;
406
+ }
407
+ }
408
+
409
+ drawTitle((isVertical ?
410
+ // the 'h' + is a hack to get around the fact that
411
+ // convertToTspans rotates any 'y...' class by 90 degrees.
412
+ // TODO: find a better way to control this.
413
+ 'h' :
414
+ 'v'
415
+ ) + ax._id + 'title', {
331
416
  avoid: {
332
417
  selection: d3.select(gd).selectAll('g.' + ax._id + 'tick'),
333
418
  side: titleSide,
334
- offsetLeft: gs.l,
335
- offsetTop: 0,
336
- maxShift: fullLayout.width
419
+ offsetTop: isVertical ? 0 : gs.t,
420
+ offsetLeft: isVertical ? gs.l : 0,
421
+ maxShift: isVertical ? fullLayout.width : fullLayout.height
337
422
  },
338
423
  attributes: {x: x, y: y, 'text-anchor': 'middle'},
339
- transform: {rotate: '-90', offset: 0}
424
+ transform: {rotate: isVertical ? -90 : 0, offset: 0}
340
425
  });
341
426
  }
342
427
  }
343
428
 
344
429
  function drawAxis() {
345
- if(['top', 'bottom'].indexOf(titleSide) !== -1) {
430
+ if(
431
+ (!isVertical && !topOrBottom) ||
432
+ (isVertical && topOrBottom)
433
+ ) {
346
434
  // squish the axis top to make room for the title
347
435
  var titleGroup = g.select('.' + cn.cbtitle);
348
436
  var titleText = titleGroup.select('text');
349
- var titleTrans = [-opts.outlinewidth / 2, opts.outlinewidth / 2];
437
+ var titleTrans = [-outlinewidth / 2, outlinewidth / 2];
350
438
  var mathJaxNode = titleGroup
351
439
  .select('.h' + ax._id + 'title-math-group')
352
440
  .node();
@@ -354,39 +442,63 @@ function drawColorBar(g, opts, gd) {
354
442
  if(titleText.node()) {
355
443
  lineSize = parseInt(titleText.node().style.fontSize, 10) * LINE_SPACING;
356
444
  }
445
+
446
+ var bb;
357
447
  if(mathJaxNode) {
358
- titleHeight = Drawing.bBox(mathJaxNode).height;
448
+ bb = Drawing.bBox(mathJaxNode);
449
+ titleWidth = bb.width;
450
+ titleHeight = bb.height;
359
451
  if(titleHeight > lineSize) {
360
452
  // not entirely sure how mathjax is doing
361
453
  // vertical alignment, but this seems to work.
362
454
  titleTrans[1] -= (titleHeight - lineSize) / 2;
363
455
  }
364
456
  } else if(titleText.node() && !titleText.classed(cn.jsPlaceholder)) {
365
- titleHeight = Drawing.bBox(titleText.node()).height;
457
+ bb = Drawing.bBox(titleText.node());
458
+ titleWidth = bb.width;
459
+ titleHeight = bb.height;
366
460
  }
367
- if(titleHeight) {
368
- // buffer btwn colorbar and title
369
- // TODO: configurable
370
- titleHeight += 5;
371
461
 
372
- if(titleSide === 'top') {
373
- ax.domain[1] -= titleHeight / gs.h;
374
- titleTrans[1] *= -1;
375
- } else {
376
- ax.domain[0] += titleHeight / gs.h;
377
- var nlines = svgTextUtils.lineCount(titleText);
378
- titleTrans[1] += (1 - nlines) * lineSize;
462
+ if(isVertical) {
463
+ if(titleHeight) {
464
+ // buffer btwn colorbar and title
465
+ // TODO: configurable
466
+ titleHeight += 5;
467
+
468
+ if(titleSide === 'top') {
469
+ ax.domain[1] -= titleHeight / gs.h;
470
+ titleTrans[1] *= -1;
471
+ } else {
472
+ ax.domain[0] += titleHeight / gs.h;
473
+ var nlines = svgTextUtils.lineCount(titleText);
474
+ titleTrans[1] += (1 - nlines) * lineSize;
475
+ }
476
+
477
+ titleGroup.attr('transform', strTranslate(titleTrans[0], titleTrans[1]));
478
+ ax.setScale();
479
+ }
480
+ } else { // horizontal colorbars
481
+ if(titleWidth) {
482
+ if(titleSide === 'right') {
483
+ ax.domain[0] += (titleWidth + titleFontSize / 2) / gs.w;
484
+ }
485
+
486
+ titleGroup.attr('transform', strTranslate(titleTrans[0], titleTrans[1]));
487
+ ax.setScale();
379
488
  }
380
-
381
- titleGroup.attr('transform', strTranslate(titleTrans[0], titleTrans[1]));
382
- ax.setScale();
383
489
  }
384
490
  }
385
491
 
386
492
  g.selectAll('.' + cn.cbfills + ',.' + cn.cblines)
387
- .attr('transform', strTranslate(0, Math.round(gs.h * (1 - ax.domain[1]))));
493
+ .attr('transform', isVertical ?
494
+ strTranslate(0, Math.round(gs.h * (1 - ax.domain[1]))) :
495
+ strTranslate(Math.round(gs.w * ax.domain[0]), 0)
496
+ );
388
497
 
389
- axLayer.attr('transform', strTranslate(0, Math.round(-gs.t)));
498
+ axLayer.attr('transform', isVertical ?
499
+ strTranslate(0, Math.round(-gs.t)) :
500
+ strTranslate(Math.round(-gs.l), 0)
501
+ );
390
502
 
391
503
  var fills = g.select('.' + cn.cbfills)
392
504
  .selectAll('rect.' + cn.cbfill)
@@ -412,20 +524,22 @@ function drawColorBar(g, opts, gd) {
412
524
 
413
525
  // offset the side adjoining the next rectangle so they
414
526
  // overlap, to prevent antialiasing gaps
415
- z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]);
416
-
527
+ if(isVertical) {
528
+ z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]);
529
+ } /* else {
530
+ // TODO: horizontal case
531
+ } */
417
532
 
418
533
  // Colorbar cannot currently support opacities so we
419
534
  // use an opaque fill even when alpha channels present
420
- var fillEl = d3.select(this).attr({
421
- x: xLeft,
422
- width: Math.max(thickPx, 2),
423
- y: d3.min(z),
424
- height: Math.max(d3.max(z) - d3.min(z), 2),
425
- });
535
+ var fillEl = d3.select(this)
536
+ .attr(isVertical ? 'x' : 'y', uPx)
537
+ .attr(isVertical ? 'y' : 'x', d3.min(z))
538
+ .attr(isVertical ? 'width' : 'height', Math.max(thickPx, 2))
539
+ .attr(isVertical ? 'height' : 'width', Math.max(d3.max(z) - d3.min(z), 2));
426
540
 
427
541
  if(opts._fillgradient) {
428
- Drawing.gradient(fillEl, gd, opts._id, 'vertical', opts._fillgradient, 'fill');
542
+ Drawing.gradient(fillEl, gd, opts._id, isVertical ? 'vertical' : 'horizontalreversed', opts._fillgradient, 'fill');
429
543
  } else {
430
544
  // tinycolor can't handle exponents and
431
545
  // at this scale, removing it makes no difference.
@@ -441,17 +555,23 @@ function drawColorBar(g, opts, gd) {
441
555
  .classed(cn.cbline, true);
442
556
  lines.exit().remove();
443
557
  lines.each(function(d) {
558
+ var a = uPx;
559
+ var b = (Math.round(ax.c2p(d)) + (line.width / 2) % 1);
560
+
444
561
  d3.select(this)
445
- .attr('d', 'M' + xLeft + ',' +
446
- (Math.round(ax.c2p(d)) + (line.width / 2) % 1) + 'h' + thickPx)
562
+ .attr('d', 'M' +
563
+ (isVertical ? a + ',' + b : b + ',' + a) +
564
+ (isVertical ? 'h' : 'v') +
565
+ thickPx
566
+ )
447
567
  .call(Drawing.lineGroupStyle, line.width, lineColormap(d), line.dash);
448
568
  });
449
569
 
450
570
  // force full redraw of labels and ticks
451
571
  axLayer.selectAll('g.' + ax._id + 'tick,path').remove();
452
572
 
453
- var shift = xLeft + thickPx +
454
- (opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0);
573
+ var shift = uPx + thickPx +
574
+ (outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0);
455
575
 
456
576
  var vals = Axes.calcTicks(ax);
457
577
  var tickSign = Axes.getTickSigns(ax)[2];
@@ -476,83 +596,211 @@ function drawColorBar(g, opts, gd) {
476
596
  // TODO: why are we redrawing multiple times now with this?
477
597
  // I guess autoMargin doesn't like being post-promise?
478
598
  function positionCB() {
479
- var innerWidth = thickPx + opts.outlinewidth / 2;
480
- if(ax.ticklabelposition.indexOf('inside') === -1) {
481
- innerWidth += Drawing.bBox(axLayer.node()).width;
599
+ var bb;
600
+ var innerThickness = thickPx + outlinewidth / 2;
601
+ if(ticklabelposition.indexOf('inside') === -1) {
602
+ bb = Drawing.bBox(axLayer.node());
603
+ innerThickness += isVertical ? bb.width : bb.height;
482
604
  }
483
605
 
484
606
  titleEl = titleCont.select('text');
485
607
 
608
+ var titleWidth = 0;
609
+
610
+ var topSideVertical = isVertical && titleSide === 'top';
611
+ var rightSideHorizontal = !isVertical && titleSide === 'right';
612
+
613
+ var moveY = 0;
614
+
486
615
  if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) {
616
+ var _titleHeight;
617
+
487
618
  var mathJaxNode = titleCont.select('.h' + ax._id + 'title-math-group').node();
488
- var titleWidth;
489
- if(mathJaxNode && ['top', 'bottom'].indexOf(titleSide) !== -1) {
490
- titleWidth = Drawing.bBox(mathJaxNode).width;
619
+ if(mathJaxNode && (
620
+ (isVertical && topOrBottom) ||
621
+ (!isVertical && !topOrBottom)
622
+ )) {
623
+ bb = Drawing.bBox(mathJaxNode);
624
+ titleWidth = bb.width;
625
+ _titleHeight = bb.height;
491
626
  } else {
492
627
  // note: the formula below works for all title sides,
493
628
  // (except for top/bottom mathjax, above)
494
629
  // but the weird gs.l is because the titleunshift
495
630
  // transform gets removed by Drawing.bBox
496
- titleWidth = Drawing.bBox(titleCont.node()).right - xLeft - gs.l;
631
+ bb = Drawing.bBox(titleCont.node());
632
+ titleWidth = bb.right - gs.l - (isVertical ? uPx : vPx);
633
+ _titleHeight = bb.bottom - gs.t - (isVertical ? vPx : uPx);
634
+
635
+ if(
636
+ !isVertical && titleSide === 'top'
637
+ ) {
638
+ innerThickness += bb.height;
639
+ moveY = bb.height;
640
+ }
641
+ }
642
+
643
+ if(rightSideHorizontal) {
644
+ titleEl.attr('transform', strTranslate(titleWidth / 2 + titleFontSize / 2, 0));
645
+
646
+ titleWidth *= 2;
497
647
  }
498
- innerWidth = Math.max(innerWidth, titleWidth);
648
+
649
+ innerThickness = Math.max(innerThickness,
650
+ isVertical ? titleWidth : _titleHeight
651
+ );
499
652
  }
500
653
 
501
- var outerwidth = 2 * opts.xpad + innerWidth + opts.borderwidth + opts.outlinewidth / 2;
502
- var outerheight = yBottomPx - yTopPx;
654
+ var outerThickness = (isVertical ?
655
+ xpad :
656
+ ypad
657
+ ) * 2 + innerThickness + borderwidth + outlinewidth / 2;
658
+
659
+ var hColorbarMoveTitle = 0;
660
+ if(!isVertical && title.text && yanchor === 'bottom' && optsY <= 0) {
661
+ hColorbarMoveTitle = outerThickness / 2;
662
+
663
+ outerThickness += hColorbarMoveTitle;
664
+ moveY += hColorbarMoveTitle;
665
+ }
666
+ fullLayout._hColorbarMoveTitle = hColorbarMoveTitle;
667
+ fullLayout._hColorbarMoveCBTitle = moveY;
503
668
 
504
- g.select('.' + cn.cbbg).attr({
505
- x: xLeft - opts.xpad - (opts.borderwidth + opts.outlinewidth) / 2,
506
- y: yTopPx - yExtraPx,
507
- width: Math.max(outerwidth, 2),
508
- height: Math.max(outerheight + 2 * yExtraPx, 2)
509
- })
510
- .call(Color.fill, opts.bgcolor)
669
+ var extraW = borderwidth + outlinewidth;
670
+
671
+ g.select('.' + cn.cbbg)
672
+ .attr('x', (isVertical ? uPx : vPx) - extraW / 2 - (isVertical ? xpad : 0))
673
+ .attr('y', (isVertical ? vPx : uPx) - (isVertical ? lenPx : ypad + moveY - hColorbarMoveTitle))
674
+ .attr(isVertical ? 'width' : 'height', Math.max(outerThickness - hColorbarMoveTitle, 2))
675
+ .attr(isVertical ? 'height' : 'width', Math.max(lenPx + extraW, 2))
676
+ .call(Color.fill, bgcolor)
511
677
  .call(Color.stroke, opts.bordercolor)
512
- .style('stroke-width', opts.borderwidth);
513
-
514
- g.selectAll('.' + cn.cboutline).attr({
515
- x: xLeft,
516
- y: yTopPx + opts.ypad + (titleSide === 'top' ? titleHeight : 0),
517
- width: Math.max(thickPx, 2),
518
- height: Math.max(outerheight - 2 * opts.ypad - titleHeight, 2)
519
- })
678
+ .style('stroke-width', borderwidth);
679
+
680
+ var moveX = rightSideHorizontal ? Math.max(titleWidth - 10, 0) : 0;
681
+
682
+ g.selectAll('.' + cn.cboutline)
683
+ .attr('x', (isVertical ? uPx : vPx + xpad) + moveX)
684
+ .attr('y', (isVertical ? vPx + ypad - lenPx : uPx) + (topSideVertical ? titleHeight : 0))
685
+ .attr(isVertical ? 'width' : 'height', Math.max(thickPx, 2))
686
+ .attr(isVertical ? 'height' : 'width', Math.max(lenPx - (isVertical ?
687
+ 2 * ypad + titleHeight :
688
+ 2 * xpad + moveX
689
+ ), 2))
520
690
  .call(Color.stroke, opts.outlinecolor)
521
691
  .style({
522
692
  fill: 'none',
523
- 'stroke-width': opts.outlinewidth
693
+ 'stroke-width': outlinewidth
524
694
  });
525
695
 
526
- // fix positioning for xanchor!='left'
527
- var xoffset = ({center: 0.5, right: 1}[opts.xanchor] || 0) * outerwidth;
528
- g.attr('transform', strTranslate(gs.l - xoffset, gs.t));
696
+ g.attr('transform', strTranslate(
697
+ gs.l - (isVertical ? xRatio * outerThickness : 0),
698
+ gs.t - (isVertical ? 0 : (1 - yRatio) * outerThickness - moveY)
699
+ ));
700
+
701
+ if(!isVertical && (
702
+ borderwidth || (
703
+ tinycolor(bgcolor).getAlpha() &&
704
+ !tinycolor.equals(fullLayout.paper_bgcolor, bgcolor)
705
+ )
706
+ )) {
707
+ // for horizontal colorbars when there is a border line or having different background color
708
+ // hide/adjust x positioning for the first/last tick labels if they go outside the border
709
+ var tickLabels = axLayer.selectAll('text');
710
+ var numTicks = tickLabels[0].length;
711
+
712
+ var border = g.select('.' + cn.cbbg).node();
713
+ var oBb = Drawing.bBox(border);
714
+ var oTr = Drawing.getTranslate(g);
715
+
716
+ var TEXTPAD = 2;
717
+
718
+ tickLabels.each(function(d, i) {
719
+ var first = 0;
720
+ var last = numTicks - 1;
721
+ if(i === first || i === last) {
722
+ var iBb = Drawing.bBox(this);
723
+ var iTr = Drawing.getTranslate(this);
724
+ var deltaX;
725
+
726
+ if(i === last) {
727
+ var iRight = iBb.right + iTr.x;
728
+ var oRight = oBb.right + oTr.x + vPx - borderwidth - TEXTPAD + optsX;
729
+
730
+ deltaX = oRight - iRight;
731
+ if(deltaX > 0) deltaX = 0;
732
+ } else if(i === first) {
733
+ var iLeft = iBb.left + iTr.x;
734
+ var oLeft = oBb.left + oTr.x + vPx + borderwidth + TEXTPAD;
735
+
736
+ deltaX = oLeft - iLeft;
737
+ if(deltaX < 0) deltaX = 0;
738
+ }
739
+
740
+ if(deltaX) {
741
+ if(numTicks < 3) { // adjust position
742
+ this.setAttribute('transform',
743
+ 'translate(' + deltaX + ',0) ' +
744
+ this.getAttribute('transform')
745
+ );
746
+ } else { // hide
747
+ this.setAttribute('visibility', 'hidden');
748
+ }
749
+ }
750
+ }
751
+ });
752
+ }
529
753
 
530
754
  // auto margin adjustment
531
755
  var marginOpts = {};
532
- var tFrac = FROM_TL[opts.yanchor];
533
- var bFrac = FROM_BR[opts.yanchor];
534
- if(opts.lenmode === 'pixels') {
535
- marginOpts.y = opts.y;
536
- marginOpts.t = outerheight * tFrac;
537
- marginOpts.b = outerheight * bFrac;
538
- } else {
539
- marginOpts.t = marginOpts.b = 0;
540
- marginOpts.yt = opts.y + opts.len * tFrac;
541
- marginOpts.yb = opts.y - opts.len * bFrac;
542
- }
756
+ var lFrac = FROM_TL[xanchor];
757
+ var rFrac = FROM_BR[xanchor];
758
+ var tFrac = FROM_TL[yanchor];
759
+ var bFrac = FROM_BR[yanchor];
760
+
761
+ var extraThickness = outerThickness - thickPx;
762
+ if(isVertical) {
763
+ if(lenmode === 'pixels') {
764
+ marginOpts.y = optsY;
765
+ marginOpts.t = lenPx * tFrac;
766
+ marginOpts.b = lenPx * bFrac;
767
+ } else {
768
+ marginOpts.t = marginOpts.b = 0;
769
+ marginOpts.yt = optsY + len * tFrac;
770
+ marginOpts.yb = optsY - len * bFrac;
771
+ }
543
772
 
544
- var lFrac = FROM_TL[opts.xanchor];
545
- var rFrac = FROM_BR[opts.xanchor];
546
- if(opts.thicknessmode === 'pixels') {
547
- marginOpts.x = opts.x;
548
- marginOpts.l = outerwidth * lFrac;
549
- marginOpts.r = outerwidth * rFrac;
550
- } else {
551
- var extraThickness = outerwidth - thickPx;
552
- marginOpts.l = extraThickness * lFrac;
553
- marginOpts.r = extraThickness * rFrac;
554
- marginOpts.xl = opts.x - opts.thickness * lFrac;
555
- marginOpts.xr = opts.x + opts.thickness * rFrac;
773
+ if(thicknessmode === 'pixels') {
774
+ marginOpts.x = optsX;
775
+ marginOpts.l = outerThickness * lFrac;
776
+ marginOpts.r = outerThickness * rFrac;
777
+ } else {
778
+ marginOpts.l = extraThickness * lFrac;
779
+ marginOpts.r = extraThickness * rFrac;
780
+ marginOpts.xl = optsX - thickness * lFrac;
781
+ marginOpts.xr = optsX + thickness * rFrac;
782
+ }
783
+ } else { // horizontal colorbars
784
+ if(lenmode === 'pixels') {
785
+ marginOpts.x = optsX;
786
+ marginOpts.l = lenPx * lFrac;
787
+ marginOpts.r = lenPx * rFrac;
788
+ } else {
789
+ marginOpts.l = marginOpts.r = 0;
790
+ marginOpts.xl = optsX + len * lFrac;
791
+ marginOpts.xr = optsX - len * rFrac;
792
+ }
793
+
794
+ if(thicknessmode === 'pixels') {
795
+ marginOpts.y = 1 - optsY;
796
+ marginOpts.t = outerThickness * tFrac;
797
+ marginOpts.b = outerThickness * bFrac;
798
+ } else {
799
+ marginOpts.t = extraThickness * tFrac;
800
+ marginOpts.b = extraThickness * bFrac;
801
+ marginOpts.yt = optsY - thickness * tFrac;
802
+ marginOpts.yb = optsY + thickness * bFrac;
803
+ }
556
804
  }
557
805
 
558
806
  Plots.autoMargin(gd, opts._id, marginOpts);
@@ -569,6 +817,7 @@ function drawColorBar(g, opts, gd) {
569
817
  }
570
818
 
571
819
  function makeEditable(g, opts, gd) {
820
+ var isVertical = opts.orientation === 'v';
572
821
  var fullLayout = gd._fullLayout;
573
822
  var gs = fullLayout._size;
574
823
  var t0, xf, yf;
@@ -583,9 +832,13 @@ function makeEditable(g, opts, gd) {
583
832
  moveFn: function(dx, dy) {
584
833
  g.attr('transform', t0 + strTranslate(dx, dy));
585
834
 
586
- xf = dragElement.align(opts._xLeftFrac + (dx / gs.w), opts._thickFrac,
835
+ xf = dragElement.align(
836
+ (isVertical ? opts._uFrac : opts._vFrac) + (dx / gs.w),
837
+ isVertical ? opts._thickFrac : opts._lenFrac,
587
838
  0, 1, opts.xanchor);
588
- yf = dragElement.align(opts._yBottomFrac - (dy / gs.h), opts._lenFrac,
839
+ yf = dragElement.align(
840
+ (isVertical ? opts._vFrac : (1 - opts._uFrac)) - (dy / gs.h),
841
+ isVertical ? opts._lenFrac : opts._thickFrac,
589
842
  0, 1, opts.yanchor);
590
843
 
591
844
  var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor);
@@ -662,6 +915,8 @@ function calcLevels(gd, opts, zrange) {
662
915
  function mockColorBarAxis(gd, opts, zrange) {
663
916
  var fullLayout = gd._fullLayout;
664
917
 
918
+ var isVertical = opts.orientation === 'v';
919
+
665
920
  var cbAxisIn = {
666
921
  type: 'linear',
667
922
  range: zrange,
@@ -692,17 +947,19 @@ function mockColorBarAxis(gd, opts, zrange) {
692
947
  title: opts.title,
693
948
  showline: true,
694
949
  anchor: 'free',
695
- side: 'right',
950
+ side: isVertical ? 'right' : 'bottom',
696
951
  position: 1
697
952
  };
698
953
 
954
+ var letter = isVertical ? 'y' : 'x';
955
+
699
956
  var cbAxisOut = {
700
957
  type: 'linear',
701
- _id: 'y' + opts._id
958
+ _id: letter + opts._id
702
959
  };
703
960
 
704
961
  var axisOptions = {
705
- letter: 'y',
962
+ letter: letter,
706
963
  font: fullLayout.font,
707
964
  noHover: true,
708
965
  noTickson: true,