'use strict'; var Axes = require('../../plots/cartesian/axes'); var Lib = require('../../lib'); var getAxisGroup = require('../../plots/cartesian/constraints').getAxisGroup; var orientations = ['v', 'h']; function crossTraceCalc(gd, plotinfo) { var calcdata = gd.calcdata; var xa = plotinfo.xaxis; var ya = plotinfo.yaxis; for(var i = 0; i < orientations.length; i++) { var orientation = orientations[i]; var posAxis = orientation === 'h' ? ya : xa; var boxList = []; // make list of boxes / candlesticks // For backward compatibility, candlesticks are treated as if they *are* box traces here for(var j = 0; j < calcdata.length; j++) { var cd = calcdata[j]; var t = cd[0].t; var trace = cd[0].trace; if(trace.visible === true && (trace.type === 'box' || trace.type === 'candlestick') && !t.empty && (trace.orientation || 'v') === orientation && trace.xaxis === xa._id && trace.yaxis === ya._id ) { boxList.push(j); } } setPositionOffset('box', gd, boxList, posAxis); } } function setPositionOffset(traceType, gd, boxList, posAxis) { var calcdata = gd.calcdata; var fullLayout = gd._fullLayout; var axId = posAxis._id; var axLetter = axId.charAt(0); var i, j, calcTrace; var pointList = []; var shownPts = 0; // make list of box points for(i = 0; i < boxList.length; i++) { calcTrace = calcdata[boxList[i]]; for(j = 0; j < calcTrace.length; j++) { pointList.push(posAxis.c2l(calcTrace[j].pos, true)); shownPts += (calcTrace[j].pts2 || []).length; } } if(!pointList.length) return; // box plots - update dPos based on multiple traces var boxdv = Lib.distinctVals(pointList); if(posAxis.type === 'category' || posAxis.type === 'multicategory') { boxdv.minDiff = 1; } var dPos0 = boxdv.minDiff / 2; // check for forced minimum dtick Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true); var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes'; var numTotal = fullLayout[numKey]; var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1; var groupFraction = 1 - fullLayout[traceType + 'gap']; var groupGapFraction = 1 - fullLayout[traceType + 'groupgap']; for(i = 0; i < boxList.length; i++) { calcTrace = calcdata[boxList[i]]; var trace = calcTrace[0].trace; var t = calcTrace[0].t; var width = trace.width; var side = trace.side; // position coordinate delta var dPos; // box half width; var bdPos; // box center offset var bPos; // half-width within which to accept hover for this box/violin // always split the distance to the closest box/violin var wHover; if(width) { dPos = bdPos = wHover = width / 2; bPos = 0; } else { dPos = dPos0; if(group) { var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation; var alignmentGroups = fullLayout._alignmentOpts[groupId] || {}; var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {}; var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length; var num = nOffsetGroups || numTotal; var shift = nOffsetGroups ? trace._offsetIndex : t.num; bdPos = dPos * groupFraction * groupGapFraction / num; bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction; wHover = dPos * groupFraction / num; } else { bdPos = dPos * groupFraction * groupGapFraction; bPos = 0; wHover = dPos; } } t.dPos = dPos; t.bPos = bPos; t.bdPos = bdPos; t.wHover = wHover; // box/violin-only value-space push value var pushplus; var pushminus; // edge of box/violin var edge = bPos + bdPos; var edgeplus; var edgeminus; // value-space padding var vpadplus; var vpadminus; // pixel-space padding var ppadplus; var ppadminus; // do we add 5% of both sides (more logic for points beyond box/violin below) var padded = Boolean(width); // does this trace show points? var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0); if(side === 'positive') { pushplus = dPos * (width ? 1 : 0.5); edgeplus = edge; pushminus = edgeplus = bPos; } else if(side === 'negative') { pushplus = edgeplus = bPos; pushminus = dPos * (width ? 1 : 0.5); edgeminus = edge; } else { pushplus = pushminus = dPos; edgeplus = edgeminus = edge; } if(hasPts) { var pointpos = trace.pointpos; var jitter = trace.jitter; var ms = trace.marker.size / 2; var pp = 0; if((pointpos + jitter) >= 0) { pp = edge * (pointpos + jitter); if(pp > pushplus) { // (++) beyond plus-value, use pp padded = true; ppadplus = ms; vpadplus = pp; } else if(pp > edgeplus) { // (+), use push-value (it's bigger), but add px-pad ppadplus = ms; vpadplus = pushplus; } } if(pp <= pushplus) { // (->) fallback to push value vpadplus = pushplus; } var pm = 0; if((pointpos - jitter) <= 0) { pm = -edge * (pointpos - jitter); if(pm > pushminus) { // (--) beyond plus-value, use pp padded = true; ppadminus = ms; vpadminus = pm; } else if(pm > edgeminus) { // (-), use push-value (it's bigger), but add px-pad ppadminus = ms; vpadminus = pushminus; } } if(pm <= pushminus) { // (<-) fallback to push value vpadminus = pushminus; } } else { vpadplus = pushplus; vpadminus = pushminus; } var pos = new Array(calcTrace.length); for(j = 0; j < calcTrace.length; j++) { pos[j] = calcTrace[j].pos; } trace._extremes[axId] = Axes.findExtremes(posAxis, pos, { padded: padded, vpadminus: vpadminus, vpadplus: vpadplus, vpadLinearized: true, // N.B. SVG px-space positive/negative ppadminus: {x: ppadminus, y: ppadplus}[axLetter], ppadplus: {x: ppadplus, y: ppadminus}[axLetter], }); } } module.exports = { crossTraceCalc: crossTraceCalc, setPositionOffset: setPositionOffset };