Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add texttemplate attribute to shape.label #6527

Merged
merged 29 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7071ff9
support shape.label.texttemplate attribute
emilykl Mar 13, 2023
b153b2d
add mock
emilykl Mar 13, 2023
0fb52cd
add label.texttemplate to draw_newshape
emilykl Mar 13, 2023
a5b9aa8
update plot-schema
emilykl Mar 14, 2023
ead380a
rename some functions/variables, add comments, improve docstrings
emilykl Mar 14, 2023
eaf1927
add baseline for new mock
emilykl Mar 14, 2023
9575eb3
update some editTypes, fix drag behavior
emilykl Mar 14, 2023
10550bb
update plot-schema
emilykl Mar 14, 2023
5f54668
add additional shape label template vars, exclude paths
emilykl Mar 21, 2023
0b2bb2a
handle datetimes and log axes, small refactor, fix docstrings
emilykl Mar 29, 2023
657ec2e
improved mock for shape label text templates
emilykl Mar 29, 2023
91ceff2
update mocks, fix attr bug
emilykl Mar 29, 2023
ad4841f
improve docstring
emilykl Mar 29, 2023
b7eeb59
update plot-schema
emilykl Mar 29, 2023
9276af4
update plot-schema
emilykl Mar 29, 2023
48e273e
simplify exports/imports for shapeLabelTexttemplateVars
emilykl Mar 29, 2023
efea57b
small cleanup
emilykl Mar 29, 2023
8eb9cf7
revise label_texttemplate to handle log
archmoj Mar 29, 2023
4a3a7ae
small cleanup, disable slope variable for non-lines
emilykl Mar 31, 2023
c619433
small adjustment to mock
emilykl Mar 31, 2023
ba780f9
update image baseline
emilykl Mar 31, 2023
e1ac9d7
improved coerce behavior
emilykl Apr 10, 2023
e6f76d5
fix texttemplate behavior for log axes and simplify
emilykl Apr 10, 2023
a9a559e
add category axis to mock
emilykl Apr 10, 2023
b82896a
handle paper-referenced shapes
emilykl Apr 10, 2023
970943b
update image baseline
emilykl Apr 10, 2023
66a12de
fix mock
emilykl Apr 13, 2023
3b32d8d
update image baseline
emilykl Apr 14, 2023
0163281
simplify parsing logic
emilykl Apr 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/shapes/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var dash = require('../drawing/attributes').dash;
var extendFlat = require('../../lib/extend').extendFlat;
var templatedArray = require('../../plot_api/plot_template').templatedArray;
var axisPlaceableObjs = require('../../constants/axis_placeable_objects');
var shapeTexttemplateAttrs = require('../../plots/template_attributes').shapeTexttemplateAttrs;
var shapeLabelTexttemplateVars = require('./label_texttemplate');

module.exports = templatedArray('shape', {
visible: {
Expand Down Expand Up @@ -232,6 +234,7 @@ module.exports = templatedArray('shape', {
editType: 'arraydraw',
description: 'Sets the text to display with shape.'
},
texttemplate: shapeTexttemplateAttrs({}, {keys: Object.keys(shapeLabelTexttemplateVars)}),
font: fontAttrs({
editType: 'calc+arraydraw',
colorEditType: 'arraydraw',
Expand Down
3 changes: 2 additions & 1 deletion src/components/shapes/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) {
// Label options
var isLine = shapeType === 'line';
var labelText = coerce('label.text');
if(labelText) {
var labelTextTemplate = coerce('label.texttemplate');
if(labelText || labelTextTemplate) {
coerce('label.textangle');
var labelTextPosition = coerce('label.textposition', isLine ? 'middle' : 'middle center');
coerce('label.xanchor');
Expand Down
29 changes: 24 additions & 5 deletions src/components/shapes/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var svgTextUtils = require('../../lib/svg_text_utils');
var constants = require('./constants');
var helpers = require('./helpers');
var getPathString = helpers.getPathString;
var shapeLabelTexttemplateVars = require('./label_texttemplate');
var FROM_TL = require('../../constants/alignment').FROM_TL;


Expand All @@ -38,7 +39,8 @@ var FROM_TL = require('../../constants/alignment').FROM_TL;
module.exports = {
draw: draw,
drawOne: drawOne,
eraseActiveShape: eraseActiveShape
eraseActiveShape: eraseActiveShape,
drawLabel: drawLabel,
};

function draw(gd) {
Expand Down Expand Up @@ -168,7 +170,7 @@ function drawOne(gd, index) {
plotinfo: plotinfo,
gd: gd,
editHelpers: editHelpers,
hasText: options.label.text,
hasText: options.label.text || options.label.texttemplate,
isActiveShape: true // i.e. to enable controllers
};

Expand Down Expand Up @@ -605,13 +607,30 @@ function drawLabel(gd, index, options, shapeGroup) {
// Remove existing label
shapeGroup.selectAll('.shape-label').remove();

// If no label, return
if(!options.label.text) return;
// If no label text or texttemplate, return
if(!(options.label.text || options.label.texttemplate)) return;

// Text template overrides text
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let defaults do the overriding - ie don't coerce text if there's a texttemplate.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and even better - don't try to coerce texttemplate if type==='path' - then you don't need the if(options.type !== 'path') below :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see the comment below about new shapes - you may be right that the if below is still needed, but it'll still be better if we only coerce what's needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexcjohnson Changed the coercion in defaults.js but I believe we still need the if(texttemplate) statement (see updated code) -- correct me if wrong!

var text = options.label.text;
if(options.label.texttemplate) {
var templateValues = {};
if(options.type !== 'path') {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
var _xa = Axes.getFromId(gd, options.xref);
var _ya = Axes.getFromId(gd, options.yref);
Object.keys(shapeLabelTexttemplateVars).forEach(function(key) {
var val = shapeLabelTexttemplateVars[key](options, _xa, _ya);
if(val !== undefined) templateValues[key] = val;
});
emilykl marked this conversation as resolved.
Show resolved Hide resolved
}
text = Lib.texttemplateStringForShapes(options.label.texttemplate,
{},
gd._fullLayout._d3locale,
templateValues);
}

var labelGroupAttrs = {
'data-index': index,
};
var text = options.label.text;
var font = options.label.font;

var labelTextAttrs = {
Expand Down
4 changes: 4 additions & 0 deletions src/components/shapes/draw_newshape/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
var fontAttrs = require('../../../plots/font_attributes');
var dash = require('../../drawing/attributes').dash;
var extendFlat = require('../../../lib/extend').extendFlat;
var shapeTexttemplateAttrs = require('../../../plots/template_attributes').shapeTexttemplateAttrs;
var shapeLabelTexttemplateVars = require('../label_texttemplate');


module.exports = {
newshape: {
Expand Down Expand Up @@ -86,6 +89,7 @@ module.exports = {
editType: 'none',
description: 'Sets the text to display with the new shape.'
},
texttemplate: shapeTexttemplateAttrs({newshape: true, editType: 'none'}, {keys: Object.keys(shapeLabelTexttemplateVars)}),
font: fontAttrs({
editType: 'none',
description: 'Sets the new shape label text font.'
Expand Down
3 changes: 2 additions & 1 deletion src/components/shapes/draw_newshape/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce

var isLine = layoutIn.dragmode === 'drawline';
var labelText = coerce('newshape.label.text');
if(labelText) {
var labelTextTemplate = coerce('newshape.label.texttemplate');
if(labelText || labelTextTemplate) {
coerce('newshape.label.textangle');
var labelTextPosition = coerce('newshape.label.textposition', isLine ? 'middle' : 'middle center');
coerce('newshape.label.xanchor');
Expand Down
82 changes: 82 additions & 0 deletions src/components/shapes/label_texttemplate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

function d2l(v, axis) {
return (
axis.type === 'log' ? v :
axis.d2l(v)
);
}

function getPos(x, xa) {
return (
xa.type === 'date' ? x :
d2l(x, xa)
);
}

function x0Fn(shape, xa) { return getPos(shape.x0, xa); }
function x1Fn(shape, xa) { return getPos(shape.x1, xa); }
function y0Fn(shape, xa, ya) { return getPos(shape.y0, ya); }
function y1Fn(shape, xa, ya) { return getPos(shape.y1, ya); }

function dxFn(shape, xa) {
return (
d2l(shape.x1, xa) -
d2l(shape.x0, xa)
);
}

function dyFn(shape, xa, ya) {
return (
d2l(shape.y1, ya) -
d2l(shape.y0, ya)
);
}

function widthFn(shape, xa) {
return Math.abs(dxFn(shape, xa));
}

function heightFn(shape, xa, ya) {
return Math.abs(dyFn(shape, xa, ya));
}

function lengthFn(shape, xa, ya) {
return (shape.type !== 'line') ? undefined :
Math.sqrt(
Math.pow((d2l(shape.x1, xa) - d2l(shape.x0, xa)), 2) +
Math.pow((d2l(shape.y1, ya) - d2l(shape.y0, ya)), 2)
);
}

function xcenterFn(shape, xa) {
var val = (d2l(shape.x1, xa) + d2l(shape.x0, xa)) / 2;
return (xa.type === 'date') ? xa.l2d(val) : val;
}

function ycenterFn(shape, xa, ya) {
var val = (d2l(shape.y1, ya) + d2l(shape.y0, ya)) / 2;
return (ya.type === 'date') ? ya.l2d(val) : val;
}

function slopeFn(shape, xa, ya) {
return (shape.type !== 'line') ? undefined : (
(d2l(shape.y1, ya) - d2l(shape.y0, ya)) /
(d2l(shape.x1, xa) - d2l(shape.x0, xa))
emilykl marked this conversation as resolved.
Show resolved Hide resolved
);
}

module.exports = {
x0: x0Fn,
x1: x1Fn,
y0: y0Fn,
y1: y1Fn,
slope: slopeFn,
dx: dxFn,
dy: dyFn,
width: widthFn,
height: heightFn,
length: lengthFn,
xcenter: xcenterFn,
ycenter: ycenterFn,
};
39 changes: 39 additions & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,26 @@ lib.texttemplateString = function() {
return templateFormatString.apply(texttemplateWarnings, arguments);
};

// Regex for parsing multiplication and division operations applied to a template key
// Used for shape.label.texttemplate
// Matches a key name (non-whitespace characters), followed by a * or / character, followed by a number
// For example, the following strings are matched: `x0*2`, `slope/1.60934`, `y1*2.54`
var MULT_DIV_REGEX = /^(\S+)([\*\/])(-?\d+(\.\d+)?)$/;
function multDivParser(inputStr) {
var match = inputStr.match(MULT_DIV_REGEX);
if(match) return { key: match[1], op: match[2], number: Number(match[3]) };
return { key: inputStr, op: null, number: null };
}
var texttemplateWarningsForShapes = {
max: 10,
count: 0,
name: 'texttemplate',
parseMultDiv: true,
};
lib.texttemplateStringForShapes = function() {
return templateFormatString.apply(texttemplateWarningsForShapes, arguments);
};

var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/;
/**
* Substitute values from an object into a string and optionally formats them using d3-format,
Expand Down Expand Up @@ -1122,6 +1142,17 @@ function templateFormatString(string, labels, d3locale) {
if(isSpaceOther || isSpaceOtherSpace) key = key.substring(1);
if(isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1);

// Shape labels support * and / operators in template string
// Parse these if the parseMultDiv param is set to true
var parsedOp = null;
var parsedNumber = null;
if(opts.parseMultDiv) {
var _match = multDivParser(key);
key = _match.key;
parsedOp = _match.op;
parsedNumber = _match.number;
}

var value;
if(hasOther) {
value = labels[key];
Expand All @@ -1145,6 +1176,14 @@ function templateFormatString(string, labels, d3locale) {
}
}

// Apply mult/div operation (if applicable)
if((parsedOp === '*' || parsedOp === '/') && value !== undefined) {
value = {
'*': (function(v) { return v * parsedNumber; }),
'/': (function(v) { return v / parsedNumber; }),
}[parsedOp](value);
}
archmoj marked this conversation as resolved.
Show resolved Hide resolved

if(value === undefined && opts) {
if(opts.count < opts.max) {
lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!');
Expand Down
44 changes: 42 additions & 2 deletions src/plots/template_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ function templateFormatStringDescription(opts) {
].join(' ');
}

function shapeTemplateFormatStringDescription() {
return [
'Variables are inserted using %{variable},',
'for example "x0: %{x0}".',
'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{x0:$.2f}". See',
FORMAT_LINK,
'for details on the formatting syntax.',
'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{x0|%m %b %Y}". See',
DATE_FORMAT_LINK,
'for details on the date formatting syntax.',
'A single multiplication or division operation may be applied to numeric variables, and combined with',
'd3 number formatting, for example "Length in cm: %{x0*2.54}", "%{slope*60:.1f} meters per second."',
'For log axes, variable values are given in log units.',
'For date axes, x/y coordinate variables and center variables use datetimes, while all other variable values use values in ms.',
].join(' ');
}

function describeVariables(extra) {
var descPart = extra.description ? ' ' + extra.description : '';
var keys = extra.keys || [];
Expand All @@ -33,9 +50,9 @@ function describeVariables(extra) {
}
descPart = descPart + 'Finally, the template string has access to ';
if(keys.length === 1) {
descPart = 'variable ' + quotedKeys[0];
descPart = descPart + 'variable ' + quotedKeys[0];
emilykl marked this conversation as resolved.
Show resolved Hide resolved
} else {
descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
descPart = descPart + 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.';
}
}
return descPart;
Expand Down Expand Up @@ -94,3 +111,26 @@ exports.texttemplateAttrs = function(opts, extra) {
}
return texttemplate;
};


exports.shapeTexttemplateAttrs = function(opts, extra) {
opts = opts || {};
extra = extra || {};

var newStr = opts.newshape ? 'new ' : '';

var descPart = describeVariables(extra);

var texttemplate = {
valType: 'string',
dflt: '',
editType: opts.editType || 'arraydraw',
description: [
'Template string used for rendering the ' + newStr + 'shape\'s label.',
'Note that this will override `text`.',
shapeTemplateFormatStringDescription(),
descPart,
].join(' ')
};
return texttemplate;
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading