plotly.js
Version:
The open source javascript graphing library that powers plotly
243 lines (191 loc) • 9.06 kB
JavaScript
'use strict';
var d3 = require('@plotly/d3');
var Drawing = require('../../components/drawing');
var map1dArray = require('./map_1d_array');
var makepath = require('./makepath');
var orientText = require('./orient_text');
var svgTextUtils = require('../../lib/svg_text_utils');
var Lib = require('../../lib');
var strRotate = Lib.strRotate;
var strTranslate = Lib.strTranslate;
var alignmentConstants = require('../../constants/alignment');
module.exports = function plot(gd, plotinfo, cdcarpet, carpetLayer) {
var isStatic = gd._context.staticPlot;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
var clipLayer = fullLayout._clips;
Lib.makeTraceGroups(carpetLayer, cdcarpet, 'trace').each(function(cd) {
var axisLayer = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var aax = trace.aaxis;
var bax = trace.baxis;
var minorLayer = Lib.ensureSingle(axisLayer, 'g', 'minorlayer');
var majorLayer = Lib.ensureSingle(axisLayer, 'g', 'majorlayer');
var boundaryLayer = Lib.ensureSingle(axisLayer, 'g', 'boundarylayer');
var labelLayer = Lib.ensureSingle(axisLayer, 'g', 'labellayer');
axisLayer.style('opacity', trace.opacity);
drawGridLines(xa, ya, majorLayer, aax, 'a', aax._gridlines, true, isStatic);
drawGridLines(xa, ya, majorLayer, bax, 'b', bax._gridlines, true, isStatic);
drawGridLines(xa, ya, minorLayer, aax, 'a', aax._minorgridlines, true, isStatic);
drawGridLines(xa, ya, minorLayer, bax, 'b', bax._minorgridlines, true, isStatic);
// NB: These are not omitted if the lines are not active. The joins must be executed
// in order for them to get cleaned up without a full redraw
drawGridLines(xa, ya, boundaryLayer, aax, 'a-boundary', aax._boundarylines, isStatic);
drawGridLines(xa, ya, boundaryLayer, bax, 'b-boundary', bax._boundarylines, isStatic);
var labelOrientationA = drawAxisLabels(gd, xa, ya, trace, cd0, labelLayer, aax._labels, 'a-label');
var labelOrientationB = drawAxisLabels(gd, xa, ya, trace, cd0, labelLayer, bax._labels, 'b-label');
drawAxisTitles(gd, labelLayer, trace, cd0, xa, ya, labelOrientationA, labelOrientationB);
drawClipPath(trace, cd0, clipLayer, xa, ya);
});
};
function drawClipPath(trace, t, layer, xaxis, yaxis) {
var seg, xp, yp, i;
var clip = layer.select('#' + trace._clipPathId);
if(!clip.size()) {
clip = layer.append('clipPath')
.classed('carpetclip', true);
}
var path = Lib.ensureSingle(clip, 'path', 'carpetboundary');
var segments = t.clipsegments;
var segs = [];
for(i = 0; i < segments.length; i++) {
seg = segments[i];
xp = map1dArray([], seg.x, xaxis.c2p);
yp = map1dArray([], seg.y, yaxis.c2p);
segs.push(makepath(xp, yp, seg.bicubic));
}
// This could be optimized ever so slightly to avoid no-op L segments
// at the corners, but it's so negligible that I don't think it's worth
// the extra complexity
var clipPathData = 'M' + segs.join('L') + 'Z';
clip.attr('id', trace._clipPathId);
path.attr('d', clipPathData);
}
function drawGridLines(xaxis, yaxis, layer, axis, axisLetter, gridlines, isStatic) {
var lineClass = 'const-' + axisLetter + '-lines';
var gridJoin = layer.selectAll('.' + lineClass).data(gridlines);
gridJoin.enter().append('path')
.classed(lineClass, true)
.style('vector-effect', isStatic ? 'none' : 'non-scaling-stroke');
gridJoin.each(function(d) {
var gridline = d;
var x = gridline.x;
var y = gridline.y;
var xp = map1dArray([], x, xaxis.c2p);
var yp = map1dArray([], y, yaxis.c2p);
var path = 'M' + makepath(xp, yp, gridline.smoothing);
var el = d3.select(this);
el.attr('d', path)
.style('stroke-width', gridline.width)
.style('stroke', gridline.color)
.style('stroke-dasharray', Drawing.dashStyle(gridline.dash, gridline.width))
.style('fill', 'none');
});
gridJoin.exit().remove();
}
function drawAxisLabels(gd, xaxis, yaxis, trace, t, layer, labels, labelClass) {
var labelJoin = layer.selectAll('text.' + labelClass).data(labels);
labelJoin.enter().append('text')
.classed(labelClass, true);
var maxExtent = 0;
var labelOrientation = {};
labelJoin.each(function(label, i) {
// Most of the positioning is done in calc_labels. Only the parts that depend upon
// the screen space representation of the x and y axes are here:
var orientation;
if(label.axis.tickangle === 'auto') {
orientation = orientText(trace, xaxis, yaxis, label.xy, label.dxy);
} else {
var angle = (label.axis.tickangle + 180.0) * Math.PI / 180.0;
orientation = orientText(trace, xaxis, yaxis, label.xy, [Math.cos(angle), Math.sin(angle)]);
}
if(!i) {
// TODO: offsetMultiplier? Not currently used anywhere...
labelOrientation = {angle: orientation.angle, flip: orientation.flip};
}
var direction = (label.endAnchor ? -1 : 1) * orientation.flip;
var labelEl = d3.select(this)
.attr({
'text-anchor': direction > 0 ? 'start' : 'end',
'data-notex': 1
})
.call(Drawing.font, label.font)
.text(label.text)
.call(svgTextUtils.convertToTspans, gd);
var bbox = Drawing.bBox(this);
labelEl.attr('transform',
// Translate to the correct point:
strTranslate(orientation.p[0], orientation.p[1]) +
// Rotate to line up with grid line tangent:
strRotate(orientation.angle) +
// Adjust the baseline and indentation:
strTranslate(label.axis.labelpadding * direction, bbox.height * 0.3)
);
maxExtent = Math.max(maxExtent, bbox.width + label.axis.labelpadding);
});
labelJoin.exit().remove();
labelOrientation.maxExtent = maxExtent;
return labelOrientation;
}
function drawAxisTitles(gd, layer, trace, t, xa, ya, labelOrientationA, labelOrientationB) {
var a, b, xy, dxy;
var aMin = Lib.aggNums(Math.min, null, trace.a);
var aMax = Lib.aggNums(Math.max, null, trace.a);
var bMin = Lib.aggNums(Math.min, null, trace.b);
var bMax = Lib.aggNums(Math.max, null, trace.b);
a = 0.5 * (aMin + aMax);
b = bMin;
xy = trace.ab2xy(a, b, true);
dxy = trace.dxyda_rough(a, b);
if(labelOrientationA.angle === undefined) {
Lib.extendFlat(labelOrientationA, orientText(trace, xa, ya, xy, trace.dxydb_rough(a, b)));
}
drawAxisTitle(gd, layer, trace, t, xy, dxy, trace.aaxis, xa, ya, labelOrientationA, 'a-title');
a = aMin;
b = 0.5 * (bMin + bMax);
xy = trace.ab2xy(a, b, true);
dxy = trace.dxydb_rough(a, b);
if(labelOrientationB.angle === undefined) {
Lib.extendFlat(labelOrientationB, orientText(trace, xa, ya, xy, trace.dxyda_rough(a, b)));
}
drawAxisTitle(gd, layer, trace, t, xy, dxy, trace.baxis, xa, ya, labelOrientationB, 'b-title');
}
var lineSpacing = alignmentConstants.LINE_SPACING;
var midShift = ((1 - alignmentConstants.MID_SHIFT) / lineSpacing) + 1;
function drawAxisTitle(gd, layer, trace, t, xy, dxy, axis, xa, ya, labelOrientation, labelClass) {
var data = [];
if(axis.title.text) data.push(axis.title.text);
var titleJoin = layer.selectAll('text.' + labelClass).data(data);
var offset = labelOrientation.maxExtent;
titleJoin.enter().append('text')
.classed(labelClass, true);
// There's only one, but we'll do it as a join so it's updated nicely:
titleJoin.each(function() {
var orientation = orientText(trace, xa, ya, xy, dxy);
if(['start', 'both'].indexOf(axis.showticklabels) === -1) {
offset = 0;
}
// In addition to the size of the labels, add on some extra padding:
var titleSize = axis.title.font.size;
offset += titleSize + axis.title.offset;
var labelNorm = labelOrientation.angle + (labelOrientation.flip < 0 ? 180 : 0);
var angleDiff = (labelNorm - orientation.angle + 450) % 360;
var reverseTitle = angleDiff > 90 && angleDiff < 270;
var el = d3.select(this);
el.text(axis.title.text)
.call(svgTextUtils.convertToTspans, gd);
if(reverseTitle) {
offset = (-svgTextUtils.lineCount(el) + midShift) * lineSpacing * titleSize - offset;
}
el.attr('transform',
strTranslate(orientation.p[0], orientation.p[1]) +
strRotate(orientation.angle) +
strTranslate(0, offset)
)
.attr('text-anchor', 'middle')
.call(Drawing.font, axis.title.font);
});
titleJoin.exit().remove();
}