UNPKG

plotly.js

Version:

The open source javascript graphing library that powers plotly

1,025 lines (876 loc) 36.6 kB
'use strict'; var d3 = require('@plotly/d3'); var tinycolor = require('tinycolor2'); var Plots = require('../../plots/plots'); var Registry = require('../../registry'); var Axes = require('../../plots/cartesian/axes'); var dragElement = require('../dragelement'); var Lib = require('../../lib'); var strTranslate = Lib.strTranslate; var extendFlat = require('../../lib/extend').extendFlat; var setCursor = require('../../lib/setcursor'); var Drawing = require('../drawing'); var Color = require('../color'); var Titles = require('../titles'); var svgTextUtils = require('../../lib/svg_text_utils'); var flipScale = require('../colorscale/helpers').flipScale; var handleAxisDefaults = require('../../plots/cartesian/axis_defaults'); var handleAxisPositionDefaults = require('../../plots/cartesian/position_defaults'); var axisLayoutAttrs = require('../../plots/cartesian/layout_attributes'); var alignmentConstants = require('../../constants/alignment'); var LINE_SPACING = alignmentConstants.LINE_SPACING; var FROM_TL = alignmentConstants.FROM_TL; var FROM_BR = alignmentConstants.FROM_BR; var cn = require('./constants').cn; function draw(gd) { var fullLayout = gd._fullLayout; var colorBars = fullLayout._infolayer .selectAll('g.' + cn.colorbar) .data(makeColorBarData(gd), function(opts) { return opts._id; }); colorBars.enter().append('g') .attr('class', function(opts) { return opts._id; }) .classed(cn.colorbar, true); colorBars.each(function(opts) { var g = d3.select(this); Lib.ensureSingle(g, 'rect', cn.cbbg); Lib.ensureSingle(g, 'g', cn.cbfills); Lib.ensureSingle(g, 'g', cn.cblines); Lib.ensureSingle(g, 'g', cn.cbaxis, function(s) { s.classed(cn.crisp, true); }); Lib.ensureSingle(g, 'g', cn.cbtitleunshift, function(s) { s.append('g').classed(cn.cbtitle, true); }); Lib.ensureSingle(g, 'rect', cn.cboutline); var done = drawColorBar(g, opts, gd); if(done && done.then) (gd._promises || []).push(done); if(gd._context.edits.colorbarPosition) { makeEditable(g, opts, gd); } }); colorBars.exit() .each(function(opts) { Plots.autoMargin(gd, opts._id); }) .remove(); colorBars.order(); } function makeColorBarData(gd) { var fullLayout = gd._fullLayout; var calcdata = gd.calcdata; var out = []; // single out item var opts; // colorbar attr parent container var cont; // trace attr container var trace; // colorbar options var cbOpt; function initOpts(opts) { return extendFlat(opts, { // fillcolor can be a d3 scale, domain is z values, range is colors // or leave it out for no fill, // or set to a string constant for single-color fill _fillcolor: null, // line.color has the same options as fillcolor _line: {color: null, width: null, dash: null}, // levels of lines to draw. // note that this DOES NOT determine the extent of the bar // that's given by the domain of fillcolor // (or line.color if no fillcolor domain) _levels: {start: null, end: null, size: null}, // separate fill levels (for example, heatmap coloring of a // contour map) if this is omitted, fillcolors will be // evaluated halfway between levels _filllevels: null, // for continuous colorscales: fill with a gradient instead of explicit levels // value should be the colorscale [[0, c0], [v1, c1], ..., [1, cEnd]] _fillgradient: null, // when using a gradient, we need the data range specified separately _zrange: null }); } function calcOpts() { if(typeof cbOpt.calc === 'function') { cbOpt.calc(gd, trace, opts); } else { opts._fillgradient = cont.reversescale ? flipScale(cont.colorscale) : cont.colorscale; opts._zrange = [cont[cbOpt.min], cont[cbOpt.max]]; } } for(var i = 0; i < calcdata.length; i++) { var cd = calcdata[i]; trace = cd[0].trace; if(!trace._module) continue; var moduleOpts = trace._module.colorbar; if(trace.visible === true && moduleOpts) { var allowsMultiplotCbs = Array.isArray(moduleOpts); var cbOpts = allowsMultiplotCbs ? moduleOpts : [moduleOpts]; for(var j = 0; j < cbOpts.length; j++) { cbOpt = cbOpts[j]; var contName = cbOpt.container; cont = contName ? trace[contName] : trace; if(cont && cont.showscale) { opts = initOpts(cont.colorbar); opts._id = 'cb' + trace.uid + (allowsMultiplotCbs && contName ? '-' + contName : ''); opts._traceIndex = trace.index; opts._propPrefix = (contName ? contName + '.' : '') + 'colorbar.'; opts._meta = trace._meta; calcOpts(); out.push(opts); } } } } for(var k in fullLayout._colorAxes) { cont = fullLayout[k]; if(cont.showscale) { var colorAxOpts = fullLayout._colorAxes[k]; opts = initOpts(cont.colorbar); opts._id = 'cb' + k; opts._propPrefix = k + '.colorbar.'; opts._meta = fullLayout._meta; cbOpt = {min: 'cmin', max: 'cmax'}; if(colorAxOpts[0] !== 'heatmap') { trace = colorAxOpts[1]; cbOpt.calc = trace._module.colorbar.calc; } calcOpts(); out.push(opts); } } return out; } function drawColorBar(g, opts, gd) { var isVertical = opts.orientation === 'v'; var len = opts.len; var lenmode = opts.lenmode; var thickness = opts.thickness; var thicknessmode = opts.thicknessmode; var outlinewidth = opts.outlinewidth; var borderwidth = opts.borderwidth; var bgcolor = opts.bgcolor; var xanchor = opts.xanchor; var yanchor = opts.yanchor; var xpad = opts.xpad; var ypad = opts.ypad; var optsX = opts.x; var optsY = isVertical ? opts.y : 1 - opts.y; var isPaperY = opts.yref === 'paper'; var isPaperX = opts.xref === 'paper'; var fullLayout = gd._fullLayout; var gs = fullLayout._size; var fillColor = opts._fillcolor; var line = opts._line; var title = opts.title; var titleSide = title.side; var zrange = opts._zrange || d3.extent((typeof fillColor === 'function' ? fillColor : line.color).domain()); var lineColormap = typeof line.color === 'function' ? line.color : function() { return line.color; }; var fillColormap = typeof fillColor === 'function' ? fillColor : function() { return fillColor; }; var levelsIn = opts._levels; var levelsOut = calcLevels(gd, opts, zrange); var fillLevels = levelsOut.fill; var lineLevels = levelsOut.line; // we calculate pixel sizes based on the specified graph size, // not the actual (in case something pushed the margins around) // which is a little odd but avoids an odd iterative effect // when the colorbar itself is pushing the margins. // but then the fractional size is calculated based on the // actual graph size, so that the axes will size correctly. var thickPx = Math.round(thickness * (thicknessmode === 'fraction' ? (isVertical ? gs.w : gs.h) : 1)); var thickFrac = thickPx / (isVertical ? gs.w : gs.h); var lenPx = Math.round(len * (lenmode === 'fraction' ? (isVertical ? gs.h : gs.w) : 1)); var lenFrac = lenPx / (isVertical ? gs.h : gs.w); var posW = isPaperX ? gs.w : gd._fullLayout.width; var posH = isPaperY ? gs.h : gd._fullLayout.height; // x positioning: do it initially just for left anchor, // then fix at the end (since we don't know the width yet) var uPx = Math.round(isVertical ? optsX * posW + xpad : optsY * posH + ypad ); var xRatio = {center: 0.5, right: 1}[xanchor] || 0; var yRatio = {top: 1, middle: 0.5}[yanchor] || 0; // for dragging... this is getting a little muddled... var uFrac = isVertical ? optsX - xRatio * thickFrac : optsY - yRatio * thickFrac; // y/x positioning (for v/h) we can do correctly from the start var vFrac = isVertical ? optsY - yRatio * lenFrac : optsX - xRatio * lenFrac; var vPx = Math.round(isVertical ? posH * (1 - vFrac) : posW * vFrac ); // stash a few things for makeEditable opts._lenFrac = lenFrac; opts._thickFrac = thickFrac; opts._uFrac = uFrac; opts._vFrac = vFrac; // stash mocked axis for contour label formatting var ax = opts._axis = mockColorBarAxis(gd, opts, zrange); // position can't go in through supplyDefaults // because that restricts it to [0,1] ax.position = thickFrac + (isVertical ? optsX + xpad / gs.w : optsY + ypad / gs.h ); var topOrBottom = ['top', 'bottom'].indexOf(titleSide) !== -1; if(isVertical && topOrBottom) { ax.title.side = titleSide; ax.titlex = optsX + xpad / gs.w; ax.titley = vFrac + (title.side === 'top' ? lenFrac - ypad / gs.h : ypad / gs.h); } if(!isVertical && !topOrBottom) { ax.title.side = titleSide; ax.titley = optsY + ypad / gs.h; ax.titlex = vFrac + xpad / gs.w; // right side } if(line.color && opts.tickmode === 'auto') { ax.tickmode = 'linear'; ax.tick0 = levelsIn.start; var dtick = levelsIn.size; // expand if too many contours, so we don't get too many ticks var autoNtick = Lib.constrain(lenPx / 50, 4, 15) + 1; var dtFactor = (zrange[1] - zrange[0]) / ((opts.nticks || autoNtick) * dtick); if(dtFactor > 1) { var dtexp = Math.pow(10, Math.floor(Math.log(dtFactor) / Math.LN10)); dtick *= dtexp * Lib.roundUp(dtFactor / dtexp, [2, 5, 10]); // if the contours are at round multiples, reset tick0 // so they're still at round multiples. Otherwise, // keep the first label on the first contour level if((Math.abs(levelsIn.start) / levelsIn.size + 1e-6) % 1 < 2e-6) { ax.tick0 = 0; } } ax.dtick = dtick; } // set domain after init, because we may want to // allow it outside [0,1] ax.domain = isVertical ? [ vFrac + ypad / gs.h, vFrac + lenFrac - ypad / gs.h ] : [ vFrac + xpad / gs.w, vFrac + lenFrac - xpad / gs.w ]; ax.setScale(); g.attr('transform', strTranslate(Math.round(gs.l), Math.round(gs.t))); var titleCont = g.select('.' + cn.cbtitleunshift) .attr('transform', strTranslate(-Math.round(gs.l), -Math.round(gs.t))); var ticklabelposition = ax.ticklabelposition; var titleFontSize = ax.title.font.size; var axLayer = g.select('.' + cn.cbaxis); var titleEl; var titleHeight = 0; var titleWidth = 0; function drawTitle(titleClass, titleOpts) { var dfltTitleOpts = { propContainer: ax, propName: opts._propPrefix + 'title', traceIndex: opts._traceIndex, _meta: opts._meta, placeholder: fullLayout._dfltTitle.colorbar, containerGroup: g.select('.' + cn.cbtitle) }; // this class-to-rotate thing with convertToTspans is // getting hackier and hackier... delete groups with the // wrong class (in case earlier the colorbar was drawn on // a different side, I think?) var otherClass = titleClass.charAt(0) === 'h' ? titleClass.substr(1) : 'h' + titleClass; g.selectAll('.' + otherClass + ',.' + otherClass + '-math-group').remove(); Titles.draw(gd, titleClass, extendFlat(dfltTitleOpts, titleOpts || {})); } function drawDummyTitle() { // draw the title so we know how much room it needs // when we squish the axis. // On vertical colorbars this only applies to top or bottom titles, not right side. // On horizontal colorbars this only applies to right, etc. if( (isVertical && topOrBottom) || (!isVertical && !topOrBottom) ) { var x, y; if(titleSide === 'top') { x = xpad + gs.l + posW * optsX; y = ypad + gs.t + posH * (1 - vFrac - lenFrac) + 3 + titleFontSize * 0.75; } if(titleSide === 'bottom') { x = xpad + gs.l + posW * optsX; y = ypad + gs.t + posH * (1 - vFrac) - 3 - titleFontSize * 0.25; } if(titleSide === 'right') { y = ypad + gs.t + posH * optsY + 3 + titleFontSize * 0.75; x = xpad + gs.l + posW * vFrac; } drawTitle(ax._id + 'title', { attributes: {x: x, y: y, 'text-anchor': isVertical ? 'start' : 'middle'} }); } } function drawCbTitle() { if( (isVertical && !topOrBottom) || (!isVertical && topOrBottom) ) { var pos = ax.position || 0; var mid = ax._offset + ax._length / 2; var x, y; if(titleSide === 'right') { y = mid; x = gs.l + posW * pos + 10 + titleFontSize * ( ax.showticklabels ? 1 : 0.5 ); } else { x = mid; if(titleSide === 'bottom') { y = gs.t + posH * pos + 10 + ( ticklabelposition.indexOf('inside') === -1 ? ax.tickfont.size : 0 ) + ( ax.ticks !== 'intside' ? opts.ticklen || 0 : 0 ); } if(titleSide === 'top') { var nlines = title.text.split('<br>').length; y = gs.t + posH * pos + 10 - thickPx - LINE_SPACING * titleFontSize * nlines; } } drawTitle((isVertical ? // the 'h' + is a hack to get around the fact that // convertToTspans rotates any 'y...' class by 90 degrees. // TODO: find a better way to control this. 'h' : 'v' ) + ax._id + 'title', { avoid: { selection: d3.select(gd).selectAll('g.' + ax._id + 'tick'), side: titleSide, offsetTop: isVertical ? 0 : gs.t, offsetLeft: isVertical ? gs.l : 0, maxShift: isVertical ? fullLayout.width : fullLayout.height }, attributes: {x: x, y: y, 'text-anchor': 'middle'}, transform: {rotate: isVertical ? -90 : 0, offset: 0} }); } } function drawAxis() { if( (!isVertical && !topOrBottom) || (isVertical && topOrBottom) ) { // squish the axis top to make room for the title var titleGroup = g.select('.' + cn.cbtitle); var titleText = titleGroup.select('text'); var titleTrans = [-outlinewidth / 2, outlinewidth / 2]; var mathJaxNode = titleGroup .select('.h' + ax._id + 'title-math-group') .node(); var lineSize = 15.6; if(titleText.node()) { lineSize = parseInt(titleText.node().style.fontSize, 10) * LINE_SPACING; } var bb; if(mathJaxNode) { bb = Drawing.bBox(mathJaxNode); titleWidth = bb.width; titleHeight = bb.height; if(titleHeight > lineSize) { // not entirely sure how mathjax is doing // vertical alignment, but this seems to work. titleTrans[1] -= (titleHeight - lineSize) / 2; } } else if(titleText.node() && !titleText.classed(cn.jsPlaceholder)) { bb = Drawing.bBox(titleText.node()); titleWidth = bb.width; titleHeight = bb.height; } if(isVertical) { if(titleHeight) { // buffer btwn colorbar and title // TODO: configurable titleHeight += 5; if(titleSide === 'top') { ax.domain[1] -= titleHeight / gs.h; titleTrans[1] *= -1; } else { ax.domain[0] += titleHeight / gs.h; var nlines = svgTextUtils.lineCount(titleText); titleTrans[1] += (1 - nlines) * lineSize; } titleGroup.attr('transform', strTranslate(titleTrans[0], titleTrans[1])); ax.setScale(); } } else { // horizontal colorbars if(titleWidth) { if(titleSide === 'right') { ax.domain[0] += (titleWidth + titleFontSize / 2) / gs.w; } titleGroup.attr('transform', strTranslate(titleTrans[0], titleTrans[1])); ax.setScale(); } } } g.selectAll('.' + cn.cbfills + ',.' + cn.cblines) .attr('transform', isVertical ? strTranslate(0, Math.round(gs.h * (1 - ax.domain[1]))) : strTranslate(Math.round(gs.w * ax.domain[0]), 0) ); axLayer.attr('transform', isVertical ? strTranslate(0, Math.round(-gs.t)) : strTranslate(Math.round(-gs.l), 0) ); var fills = g.select('.' + cn.cbfills) .selectAll('rect.' + cn.cbfill) .attr('style', '') .data(fillLevels); fills.enter().append('rect') .classed(cn.cbfill, true) .attr('style', ''); fills.exit().remove(); var zBounds = zrange .map(ax.c2p) .map(Math.round) .sort(function(a, b) { return a - b; }); fills.each(function(d, i) { var z = [ (i === 0) ? zrange[0] : (fillLevels[i] + fillLevels[i - 1]) / 2, (i === fillLevels.length - 1) ? zrange[1] : (fillLevels[i] + fillLevels[i + 1]) / 2 ] .map(ax.c2p) .map(Math.round); // offset the side adjoining the next rectangle so they // overlap, to prevent antialiasing gaps if(isVertical) { z[1] = Lib.constrain(z[1] + (z[1] > z[0]) ? 1 : -1, zBounds[0], zBounds[1]); } /* else { // TODO: horizontal case } */ // Colorbar cannot currently support opacities so we // use an opaque fill even when alpha channels present var fillEl = d3.select(this) .attr(isVertical ? 'x' : 'y', uPx) .attr(isVertical ? 'y' : 'x', d3.min(z)) .attr(isVertical ? 'width' : 'height', Math.max(thickPx, 2)) .attr(isVertical ? 'height' : 'width', Math.max(d3.max(z) - d3.min(z), 2)); if(opts._fillgradient) { Drawing.gradient(fillEl, gd, opts._id, isVertical ? 'vertical' : 'horizontalreversed', opts._fillgradient, 'fill'); } else { // tinycolor can't handle exponents and // at this scale, removing it makes no difference. var colorString = fillColormap(d).replace('e-', ''); fillEl.attr('fill', tinycolor(colorString).toHexString()); } }); var lines = g.select('.' + cn.cblines) .selectAll('path.' + cn.cbline) .data(line.color && line.width ? lineLevels : []); lines.enter().append('path') .classed(cn.cbline, true); lines.exit().remove(); lines.each(function(d) { var a = uPx; var b = (Math.round(ax.c2p(d)) + (line.width / 2) % 1); d3.select(this) .attr('d', 'M' + (isVertical ? a + ',' + b : b + ',' + a) + (isVertical ? 'h' : 'v') + thickPx ) .call(Drawing.lineGroupStyle, line.width, lineColormap(d), line.dash); }); // force full redraw of labels and ticks axLayer.selectAll('g.' + ax._id + 'tick,path').remove(); var shift = uPx + thickPx + (outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0); var vals = Axes.calcTicks(ax); var tickSign = Axes.getTickSigns(ax)[2]; Axes.drawTicks(gd, ax, { vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals, layer: axLayer, path: Axes.makeTickPath(ax, shift, tickSign), transFn: Axes.makeTransTickFn(ax) }); return Axes.drawLabels(gd, ax, { vals: vals, layer: axLayer, transFn: Axes.makeTransTickLabelFn(ax), labelFns: Axes.makeLabelFns(ax, shift) }); } // wait for the axis & title to finish rendering before // continuing positioning // TODO: why are we redrawing multiple times now with this? // I guess autoMargin doesn't like being post-promise? function positionCB() { var bb; var innerThickness = thickPx + outlinewidth / 2; if(ticklabelposition.indexOf('inside') === -1) { bb = Drawing.bBox(axLayer.node()); innerThickness += isVertical ? bb.width : bb.height; } titleEl = titleCont.select('text'); var titleWidth = 0; var topSideVertical = isVertical && titleSide === 'top'; var rightSideHorizontal = !isVertical && titleSide === 'right'; var moveY = 0; if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) { var _titleHeight; var mathJaxNode = titleCont.select('.h' + ax._id + 'title-math-group').node(); if(mathJaxNode && ( (isVertical && topOrBottom) || (!isVertical && !topOrBottom) )) { bb = Drawing.bBox(mathJaxNode); titleWidth = bb.width; _titleHeight = bb.height; } else { // note: the formula below works for all title sides, // (except for top/bottom mathjax, above) // but the weird gs.l is because the titleunshift // transform gets removed by Drawing.bBox bb = Drawing.bBox(titleCont.node()); titleWidth = bb.right - gs.l - (isVertical ? uPx : vPx); _titleHeight = bb.bottom - gs.t - (isVertical ? vPx : uPx); if( !isVertical && titleSide === 'top' ) { innerThickness += bb.height; moveY = bb.height; } } if(rightSideHorizontal) { titleEl.attr('transform', strTranslate(titleWidth / 2 + titleFontSize / 2, 0)); titleWidth *= 2; } innerThickness = Math.max(innerThickness, isVertical ? titleWidth : _titleHeight ); } var outerThickness = (isVertical ? xpad : ypad ) * 2 + innerThickness + borderwidth + outlinewidth / 2; var hColorbarMoveTitle = 0; if(!isVertical && title.text && yanchor === 'bottom' && optsY <= 0) { hColorbarMoveTitle = outerThickness / 2; outerThickness += hColorbarMoveTitle; moveY += hColorbarMoveTitle; } fullLayout._hColorbarMoveTitle = hColorbarMoveTitle; fullLayout._hColorbarMoveCBTitle = moveY; var extraW = borderwidth + outlinewidth; // TODO - are these the correct positions? var lx = (isVertical ? uPx : vPx) - extraW / 2 - (isVertical ? xpad : 0); var ly = (isVertical ? vPx : uPx) - (isVertical ? lenPx : ypad + moveY - hColorbarMoveTitle); g.select('.' + cn.cbbg) .attr('x', lx) .attr('y', ly) .attr(isVertical ? 'width' : 'height', Math.max(outerThickness - hColorbarMoveTitle, 2)) .attr(isVertical ? 'height' : 'width', Math.max(lenPx + extraW, 2)) .call(Color.fill, bgcolor) .call(Color.stroke, opts.bordercolor) .style('stroke-width', borderwidth); var moveX = rightSideHorizontal ? Math.max(titleWidth - 10, 0) : 0; g.selectAll('.' + cn.cboutline) .attr('x', (isVertical ? uPx : vPx + xpad) + moveX) .attr('y', (isVertical ? vPx + ypad - lenPx : uPx) + (topSideVertical ? titleHeight : 0)) .attr(isVertical ? 'width' : 'height', Math.max(thickPx, 2)) .attr(isVertical ? 'height' : 'width', Math.max(lenPx - (isVertical ? 2 * ypad + titleHeight : 2 * xpad + moveX ), 2)) .call(Color.stroke, opts.outlinecolor) .style({ fill: 'none', 'stroke-width': outlinewidth }); var xShift = ((isVertical ? xRatio * outerThickness : 0)); var yShift = ((isVertical ? 0 : (1 - yRatio) * outerThickness - moveY)); xShift = isPaperX ? gs.l - xShift : -xShift; yShift = isPaperY ? gs.t - yShift : -yShift; g.attr('transform', strTranslate( xShift, yShift )); if(!isVertical && ( borderwidth || ( tinycolor(bgcolor).getAlpha() && !tinycolor.equals(fullLayout.paper_bgcolor, bgcolor) ) )) { // for horizontal colorbars when there is a border line or having different background color // hide/adjust x positioning for the first/last tick labels if they go outside the border var tickLabels = axLayer.selectAll('text'); var numTicks = tickLabels[0].length; var border = g.select('.' + cn.cbbg).node(); var oBb = Drawing.bBox(border); var oTr = Drawing.getTranslate(g); var TEXTPAD = 2; tickLabels.each(function(d, i) { var first = 0; var last = numTicks - 1; if(i === first || i === last) { var iBb = Drawing.bBox(this); var iTr = Drawing.getTranslate(this); var deltaX; if(i === last) { var iRight = iBb.right + iTr.x; var oRight = oBb.right + oTr.x + vPx - borderwidth - TEXTPAD + optsX; deltaX = oRight - iRight; if(deltaX > 0) deltaX = 0; } else if(i === first) { var iLeft = iBb.left + iTr.x; var oLeft = oBb.left + oTr.x + vPx + borderwidth + TEXTPAD; deltaX = oLeft - iLeft; if(deltaX < 0) deltaX = 0; } if(deltaX) { if(numTicks < 3) { // adjust position this.setAttribute('transform', 'translate(' + deltaX + ',0) ' + this.getAttribute('transform') ); } else { // hide this.setAttribute('visibility', 'hidden'); } } } }); } // auto margin adjustment var marginOpts = {}; var lFrac = FROM_TL[xanchor]; var rFrac = FROM_BR[xanchor]; var tFrac = FROM_TL[yanchor]; var bFrac = FROM_BR[yanchor]; var extraThickness = outerThickness - thickPx; if(isVertical) { if(lenmode === 'pixels') { marginOpts.y = optsY; marginOpts.t = lenPx * tFrac; marginOpts.b = lenPx * bFrac; } else { marginOpts.t = marginOpts.b = 0; marginOpts.yt = optsY + len * tFrac; marginOpts.yb = optsY - len * bFrac; } if(thicknessmode === 'pixels') { marginOpts.x = optsX; marginOpts.l = outerThickness * lFrac; marginOpts.r = outerThickness * rFrac; } else { marginOpts.l = extraThickness * lFrac; marginOpts.r = extraThickness * rFrac; marginOpts.xl = optsX - thickness * lFrac; marginOpts.xr = optsX + thickness * rFrac; } } else { // horizontal colorbars if(lenmode === 'pixels') { marginOpts.x = optsX; marginOpts.l = lenPx * lFrac; marginOpts.r = lenPx * rFrac; } else { marginOpts.l = marginOpts.r = 0; marginOpts.xl = optsX + len * lFrac; marginOpts.xr = optsX - len * rFrac; } if(thicknessmode === 'pixels') { marginOpts.y = 1 - optsY; marginOpts.t = outerThickness * tFrac; marginOpts.b = outerThickness * bFrac; } else { marginOpts.t = extraThickness * tFrac; marginOpts.b = extraThickness * bFrac; marginOpts.yt = optsY - thickness * tFrac; marginOpts.yb = optsY + thickness * bFrac; } } var sideY = opts.y < 0.5 ? 'b' : 't'; var sideX = opts.x < 0.5 ? 'l' : 'r'; gd._fullLayout._reservedMargin[opts._id] = {}; var possibleReservedMargins = { r: (fullLayout.width - lx - xShift), l: lx + marginOpts.r, b: (fullLayout.height - ly - yShift), t: ly + marginOpts.b }; if(isPaperX && isPaperY) { Plots.autoMargin(gd, opts._id, marginOpts); } else if(isPaperX) { gd._fullLayout._reservedMargin[opts._id][sideY] = possibleReservedMargins[sideY]; } else if(isPaperY) { gd._fullLayout._reservedMargin[opts._id][sideX] = possibleReservedMargins[sideX]; } else { if(isVertical) { gd._fullLayout._reservedMargin[opts._id][sideX] = possibleReservedMargins[sideX]; } else { gd._fullLayout._reservedMargin[opts._id][sideY] = possibleReservedMargins[sideY]; } } } return Lib.syncOrAsync([ Plots.previousPromises, drawDummyTitle, drawAxis, drawCbTitle, Plots.previousPromises, positionCB ], gd); } function makeEditable(g, opts, gd) { var isVertical = opts.orientation === 'v'; var fullLayout = gd._fullLayout; var gs = fullLayout._size; var t0, xf, yf; dragElement.init({ element: g.node(), gd: gd, prepFn: function() { t0 = g.attr('transform'); setCursor(g); }, moveFn: function(dx, dy) { g.attr('transform', t0 + strTranslate(dx, dy)); xf = dragElement.align( (isVertical ? opts._uFrac : opts._vFrac) + (dx / gs.w), isVertical ? opts._thickFrac : opts._lenFrac, 0, 1, opts.xanchor); yf = dragElement.align( (isVertical ? opts._vFrac : (1 - opts._uFrac)) - (dy / gs.h), isVertical ? opts._lenFrac : opts._thickFrac, 0, 1, opts.yanchor); var csr = dragElement.getCursor(xf, yf, opts.xanchor, opts.yanchor); setCursor(g, csr); }, doneFn: function() { setCursor(g); if(xf !== undefined && yf !== undefined) { var update = {}; update[opts._propPrefix + 'x'] = xf; update[opts._propPrefix + 'y'] = yf; if(opts._traceIndex !== undefined) { Registry.call('_guiRestyle', gd, update, opts._traceIndex); } else { Registry.call('_guiRelayout', gd, update); } } } }); } function calcLevels(gd, opts, zrange) { var levelsIn = opts._levels; var lineLevels = []; var fillLevels = []; var l; var i; var l0 = levelsIn.end + levelsIn.size / 100; var ls = levelsIn.size; var zr0 = (1.001 * zrange[0] - 0.001 * zrange[1]); var zr1 = (1.001 * zrange[1] - 0.001 * zrange[0]); for(i = 0; i < 1e5; i++) { l = levelsIn.start + i * ls; if(ls > 0 ? (l >= l0) : (l <= l0)) break; if(l > zr0 && l < zr1) lineLevels.push(l); } if(opts._fillgradient) { fillLevels = [0]; } else if(typeof opts._fillcolor === 'function') { var fillLevelsIn = opts._filllevels; if(fillLevelsIn) { l0 = fillLevelsIn.end + fillLevelsIn.size / 100; ls = fillLevelsIn.size; for(i = 0; i < 1e5; i++) { l = fillLevelsIn.start + i * ls; if(ls > 0 ? (l >= l0) : (l <= l0)) break; if(l > zrange[0] && l < zrange[1]) fillLevels.push(l); } } else { fillLevels = lineLevels.map(function(v) { return v - levelsIn.size / 2; }); fillLevels.push(fillLevels[fillLevels.length - 1] + levelsIn.size); } } else if(opts._fillcolor && typeof opts._fillcolor === 'string') { // doesn't matter what this value is, with a single value // we'll make a single fill rect covering the whole bar fillLevels = [0]; } if(levelsIn.size < 0) { lineLevels.reverse(); fillLevels.reverse(); } return {line: lineLevels, fill: fillLevels}; } function mockColorBarAxis(gd, opts, zrange) { var fullLayout = gd._fullLayout; var isVertical = opts.orientation === 'v'; var cbAxisIn = { type: 'linear', range: zrange, tickmode: opts.tickmode, nticks: opts.nticks, tick0: opts.tick0, dtick: opts.dtick, tickvals: opts.tickvals, ticktext: opts.ticktext, ticks: opts.ticks, ticklen: opts.ticklen, tickwidth: opts.tickwidth, tickcolor: opts.tickcolor, showticklabels: opts.showticklabels, labelalias: opts.labelalias, ticklabelposition: opts.ticklabelposition, ticklabeloverflow: opts.ticklabeloverflow, ticklabelstep: opts.ticklabelstep, tickfont: opts.tickfont, tickangle: opts.tickangle, tickformat: opts.tickformat, exponentformat: opts.exponentformat, minexponent: opts.minexponent, separatethousands: opts.separatethousands, showexponent: opts.showexponent, showtickprefix: opts.showtickprefix, tickprefix: opts.tickprefix, showticksuffix: opts.showticksuffix, ticksuffix: opts.ticksuffix, title: opts.title, showline: true, anchor: 'free', side: isVertical ? 'right' : 'bottom', position: 1 }; var letter = isVertical ? 'y' : 'x'; var cbAxisOut = { type: 'linear', _id: letter + opts._id }; var axisOptions = { letter: letter, font: fullLayout.font, noAutotickangles: letter === 'y', noHover: true, noTickson: true, noTicklabelmode: true, noInsideRange: true, calendar: fullLayout.calendar // not really necessary (yet?) }; function coerce(attr, dflt) { return Lib.coerce(cbAxisIn, cbAxisOut, axisLayoutAttrs, attr, dflt); } handleAxisDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions, fullLayout); handleAxisPositionDefaults(cbAxisIn, cbAxisOut, coerce, axisOptions); return cbAxisOut; } module.exports = { draw: draw };