plotly.js
Version:
The open source javascript graphing library that powers plotly
334 lines (270 loc) • 9.75 kB
JavaScript
/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
var isNumeric = require('fast-isnumeric');
var Lib = require('../../lib');
var BADNUM = require('../../constants/numerical').BADNUM;
var geoJsonUtils = require('../../lib/geojson_utils');
var Colorscale = require('../../components/colorscale');
var Drawing = require('../../components/drawing');
var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
var subTypes = require('../scatter/subtypes');
var convertTextOpts = require('../../plots/mapbox/convert_text_opts');
var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue;
var NEWLINES = require('../../lib/svg_text_utils').NEWLINES;
var BR_TAG_ALL = require('../../lib/svg_text_utils').BR_TAG_ALL;
module.exports = function convert(gd, calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = (trace.visible === true && trace._length !== 0);
var hasFill = (trace.fill !== 'none');
var hasLines = subTypes.hasLines(trace);
var hasMarkers = subTypes.hasMarkers(trace);
var hasText = subTypes.hasText(trace);
var hasCircles = (hasMarkers && trace.marker.symbol === 'circle');
var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle');
var fill = initContainer();
var line = initContainer();
var circle = initContainer();
var symbol = initContainer();
var opts = {
fill: fill,
line: line,
circle: circle,
symbol: symbol
};
// early return if not visible or placeholder
if(!isVisible) return opts;
// fill layer and line layer use the same coords
var lineCoords;
if(hasFill || hasLines) {
lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace);
}
if(hasFill) {
fill.geojson = geoJsonUtils.makePolygon(lineCoords);
fill.layout.visibility = 'visible';
Lib.extendFlat(fill.paint, {
'fill-color': trace.fillcolor
});
}
if(hasLines) {
line.geojson = geoJsonUtils.makeLine(lineCoords);
line.layout.visibility = 'visible';
Lib.extendFlat(line.paint, {
'line-width': trace.line.width,
'line-color': trace.line.color,
'line-opacity': trace.opacity
});
// TODO convert line.dash into line-dasharray
}
if(hasCircles) {
var circleOpts = makeCircleOpts(calcTrace);
circle.geojson = circleOpts.geojson;
circle.layout.visibility = 'visible';
Lib.extendFlat(circle.paint, {
'circle-color': circleOpts.mcc,
'circle-radius': circleOpts.mrc,
'circle-opacity': circleOpts.mo
});
}
if(hasSymbols || hasText) {
symbol.geojson = makeSymbolGeoJSON(calcTrace, gd);
Lib.extendFlat(symbol.layout, {
visibility: 'visible',
'icon-image': '{symbol}-15',
'text-field': '{text}'
});
if(hasSymbols) {
Lib.extendFlat(symbol.layout, {
'icon-size': trace.marker.size / 10
});
if('angle' in trace.marker && trace.marker.angle !== 'auto') {
Lib.extendFlat(symbol.layout, {
// unfortunately cant use {angle} do to this issue:
// https://github.com/mapbox/mapbox-gl-js/issues/873
'icon-rotate': {
type: 'identity', property: 'angle'
},
'icon-rotation-alignment': 'map'
});
}
symbol.layout['icon-allow-overlap'] = trace.marker.allowoverlap;
Lib.extendFlat(symbol.paint, {
'icon-opacity': trace.opacity * trace.marker.opacity,
// TODO does not work ??
'icon-color': trace.marker.color
});
}
if(hasText) {
var iconSize = (trace.marker || {}).size;
var textOpts = convertTextOpts(trace.textposition, iconSize);
// all data-driven below !!
Lib.extendFlat(symbol.layout, {
'text-size': trace.textfont.size,
'text-anchor': textOpts.anchor,
'text-offset': textOpts.offset
// TODO font family
// 'text-font': symbol.textfont.family.split(', '),
});
Lib.extendFlat(symbol.paint, {
'text-color': trace.textfont.color,
'text-opacity': trace.opacity
});
}
}
return opts;
};
function initContainer() {
return {
geojson: geoJsonUtils.makeBlank(),
layout: { visibility: 'none' },
paint: {}
};
}
function makeCircleOpts(calcTrace) {
var trace = calcTrace[0].trace;
var marker = trace.marker;
var selectedpoints = trace.selectedpoints;
var arrayColor = Lib.isArrayOrTypedArray(marker.color);
var arraySize = Lib.isArrayOrTypedArray(marker.size);
var arrayOpacity = Lib.isArrayOrTypedArray(marker.opacity);
var i;
function addTraceOpacity(o) { return trace.opacity * o; }
function size2radius(s) { return s / 2; }
var colorFn;
if(arrayColor) {
if(Colorscale.hasColorscale(trace, 'marker')) {
colorFn = Colorscale.makeColorScaleFuncFromTrace(marker);
} else {
colorFn = Lib.identity;
}
}
var sizeFn;
if(arraySize) {
sizeFn = makeBubbleSizeFn(trace);
}
var opacityFn;
if(arrayOpacity) {
opacityFn = function(mo) {
var mo2 = isNumeric(mo) ? +Lib.constrain(mo, 0, 1) : 0;
return addTraceOpacity(mo2);
};
}
var features = [];
for(i = 0; i < calcTrace.length; i++) {
var calcPt = calcTrace[i];
var lonlat = calcPt.lonlat;
if(isBADNUM(lonlat)) continue;
var props = {};
if(colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc);
if(sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms);
if(opacityFn) props.mo = opacityFn(calcPt.mo);
if(selectedpoints) props.selected = calcPt.selected || 0;
features.push({
type: 'Feature',
geometry: {type: 'Point', coordinates: lonlat},
properties: props
});
}
var fns;
if(selectedpoints) {
fns = Drawing.makeSelectedPointStyleFns(trace);
for(i = 0; i < features.length; i++) {
var d = features[i].properties;
if(fns.selectedOpacityFn) {
d.mo = addTraceOpacity(fns.selectedOpacityFn(d));
}
if(fns.selectedColorFn) {
d.mcc = fns.selectedColorFn(d);
}
if(fns.selectedSizeFn) {
d.mrc = fns.selectedSizeFn(d);
}
}
}
return {
geojson: {type: 'FeatureCollection', features: features},
mcc: arrayColor || (fns && fns.selectedColorFn) ?
{type: 'identity', property: 'mcc'} :
marker.color,
mrc: arraySize || (fns && fns.selectedSizeFn) ?
{type: 'identity', property: 'mrc'} :
size2radius(marker.size),
mo: arrayOpacity || (fns && fns.selectedOpacityFn) ?
{type: 'identity', property: 'mo'} :
addTraceOpacity(marker.opacity)
};
}
function makeSymbolGeoJSON(calcTrace, gd) {
var fullLayout = gd._fullLayout;
var trace = calcTrace[0].trace;
var marker = trace.marker || {};
var symbol = marker.symbol;
var angle = marker.angle;
var fillSymbol = (symbol !== 'circle') ?
getFillFunc(symbol) :
blankFillFunc;
var fillAngle = (angle !== 'auto') ?
getFillFunc(angle, true) :
blankFillFunc;
var fillText = subTypes.hasText(trace) ?
getFillFunc(trace.text) :
blankFillFunc;
var features = [];
for(var i = 0; i < calcTrace.length; i++) {
var calcPt = calcTrace[i];
if(isBADNUM(calcPt.lonlat)) continue;
var texttemplate = trace.texttemplate;
var text;
if(texttemplate) {
var tt = Array.isArray(texttemplate) ? (texttemplate[i] || '') : texttemplate;
var labels = trace._module.formatLabels(calcPt, trace, fullLayout);
var pointValues = {};
appendArrayPointValue(pointValues, trace, calcPt.i);
var meta = trace._meta || {};
text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta);
} else {
text = fillText(i);
}
if(text) {
text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n');
}
features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: calcPt.lonlat
},
properties: {
symbol: fillSymbol(i),
angle: fillAngle(i),
text: text
}
});
}
return {
type: 'FeatureCollection',
features: features
};
}
function getFillFunc(attr, numeric) {
if(Lib.isArrayOrTypedArray(attr)) {
if(numeric) {
return function(i) { return isNumeric(attr[i]) ? +attr[i] : 0; };
}
return function(i) { return attr[i]; };
} else if(attr) {
return function() { return attr; };
} else {
return blankFillFunc;
}
}
function blankFillFunc() { return ''; }
// only need to check lon (OR lat)
function isBADNUM(lonlat) {
return lonlat[0] === BADNUM;
}