Skip to content

Commit c7b1003

Browse files
authored
Merge pull request #6983 from plotly/font-shadow-striding-capitalize
Add `shadow`, `lineposition` and `textcase` options to SVG fonts
2 parents cfccbcc + e60123f commit c7b1003

File tree

130 files changed

+12464
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+12464
-174
lines changed

src/components/annotations/draw.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ function drawRaw(gd, options, index, subplotId, xa, ya) {
198198
fontColor: hoverFont.color,
199199
fontWeight: hoverFont.weight,
200200
fontStyle: hoverFont.style,
201-
fontVariant: hoverFont.variant
201+
fontVariant: hoverFont.variant,
202+
fontShadow: hoverFont.fontShadow,
203+
fontLineposition: hoverFont.fontLineposition,
204+
fontTextcase: hoverFont.fontTextcase,
202205
}, {
203206
container: fullLayout._hoverlayer.node(),
204207
outerContainer: fullLayout._paper.node(),

src/components/colorbar/defaults.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,9 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
124124
coerce('title.text', layout._dfltTitle.colorbar);
125125

126126
var tickFont = colorbarOut.showticklabels ? colorbarOut.tickfont : font;
127-
var dfltTitleFont = Lib.extendFlat({}, tickFont, {
128-
weight: font.weight,
129-
style: font.style,
130-
variant: font.variant,
131-
color: font.color,
127+
128+
var dfltTitleFont = Lib.extendFlat({}, font, {
129+
family: tickFont.family,
132130
size: Lib.bigFont(tickFont.size)
133131
});
134132
Lib.coerceFont(coerce, 'title.font', dfltTitleFont);

src/components/drawing/index.js

+36
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ drawing.font = function(s, font) {
3434
var color = font.color;
3535
var size = font.size;
3636
var family = font.family;
37+
var shadow = font.shadow;
38+
var lineposition = font.lineposition;
39+
var textcase = font.textcase;
3740

3841
if(family) s.style('font-family', family);
3942
if(size + 1) s.style('font-size', size + 'px');
@@ -42,8 +45,38 @@ drawing.font = function(s, font) {
4245
if(weight) s.style('font-weight', weight);
4346
if(style) s.style('font-style', style);
4447
if(variant) s.style('font-variant', variant);
48+
49+
if(textcase) s.style('text-transform', dropNone(textcase2transform(textcase)));
50+
if(shadow) s.style('text-shadow', shadow === 'auto' ? svgTextUtils.makeTextShadow(Color.contrast(color)) : dropNone(shadow));
51+
if(lineposition) s.style('text-decoration-line', dropNone(lineposition2decorationLine(lineposition)));
52+
};
53+
54+
function dropNone(a) {
55+
return a === 'none' ? undefined : a;
56+
}
57+
58+
var textcase2transformOptions = {
59+
normal: 'none',
60+
lower: 'lowercase',
61+
upper: 'uppercase',
62+
'word caps': 'capitalize'
4563
};
4664

65+
function textcase2transform(textcase) {
66+
return textcase2transformOptions[textcase];
67+
}
68+
69+
function lineposition2decorationLine(lineposition) {
70+
return (
71+
lineposition
72+
.replace('under', 'underline')
73+
.replace('over', 'overline')
74+
.replace('through', 'line-through')
75+
.split('+')
76+
.join(' ')
77+
);
78+
}
79+
4780
/*
4881
* Positioning helpers
4982
* Note: do not use `setPosition` with <text> nodes modified by
@@ -1136,6 +1169,9 @@ drawing.textPointStyle = function(s, trace, gd) {
11361169
weight: d.tw || trace.textfont.weight,
11371170
style: d.ty || trace.textfont.style,
11381171
variant: d.tv || trace.textfont.variant,
1172+
textcase: d.tC || trace.textfont.textcase,
1173+
lineposition: d.tE || trace.textfont.lineposition,
1174+
shadow: d.tS || trace.textfont.shadow,
11391175
size: fontSize,
11401176
color: fontColor
11411177
})

src/components/fx/hover.js

+24-8
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,9 @@ function createHoverText(hoverData, opts) {
961961
var fontWeight = opts.fontWeight || fullLayout.font.weight;
962962
var fontStyle = opts.fontStyle || fullLayout.font.style;
963963
var fontVariant = opts.fontVariant || fullLayout.font.variant;
964+
var fontTextcase = opts.fontTextcase || fullLayout.font.textcase;
965+
var fontLineposition = opts.fontLineposition || fullLayout.font.lineposition;
966+
var fontShadow = opts.fontShadow || fullLayout.font.shadow;
964967

965968
var c0 = hoverData[0];
966969
var xa = c0.xa;
@@ -1041,13 +1044,17 @@ function createHoverText(hoverData, opts) {
10411044
var commonBgColor = commonLabelOpts.bgcolor || Color.defaultLine;
10421045
var commonStroke = commonLabelOpts.bordercolor || Color.contrast(commonBgColor);
10431046
var contrastColor = Color.contrast(commonBgColor);
1047+
var commonLabelOptsFont = commonLabelOpts.font;
10441048
var commonLabelFont = {
1045-
weight: commonLabelOpts.font.weight || fontWeight,
1046-
style: commonLabelOpts.font.style || fontStyle,
1047-
variant: commonLabelOpts.font.variant || fontVariant,
1048-
family: commonLabelOpts.font.family || fontFamily,
1049-
size: commonLabelOpts.font.size || fontSize,
1050-
color: commonLabelOpts.font.color || contrastColor
1049+
weight: commonLabelOptsFont.weight || fontWeight,
1050+
style: commonLabelOptsFont.style || fontStyle,
1051+
variant: commonLabelOptsFont.variant || fontVariant,
1052+
textcase: commonLabelOptsFont.textcase || fontTextcase,
1053+
lineposition: commonLabelOptsFont.lineposition || fontLineposition,
1054+
shadow: commonLabelOptsFont.shadow || fontShadow,
1055+
family: commonLabelOptsFont.family || fontFamily,
1056+
size: commonLabelOptsFont.size || fontSize,
1057+
color: commonLabelOptsFont.color || contrastColor
10511058
};
10521059

10531060
lpath.style({
@@ -1370,6 +1377,9 @@ function createHoverText(hoverData, opts) {
13701377
weight: fontWeight,
13711378
style: fontStyle,
13721379
variant: fontVariant,
1380+
textcase: fontTextcase,
1381+
lineposition: fontLineposition,
1382+
shadow: fontShadow,
13731383
family: fontFamily,
13741384
size: fontSize
13751385
});
@@ -1413,7 +1423,10 @@ function createHoverText(hoverData, opts) {
14131423
color: d.fontColor || contrastColor,
14141424
weight: d.fontWeight || fontWeight,
14151425
style: d.fontStyle || fontStyle,
1416-
variant: d.fontVariant || fontVariant
1426+
variant: d.fontVariant || fontVariant,
1427+
textcase: d.fontTextcase || fontTextcase,
1428+
lineposition: d.fontLineposition || fontLineposition,
1429+
shadow: d.fontShadow || fontShadow,
14171430
})
14181431
.text(text)
14191432
.attr('data-notex', 1)
@@ -1432,7 +1445,10 @@ function createHoverText(hoverData, opts) {
14321445
color: nameColor,
14331446
weight: d.fontWeight || fontWeight,
14341447
style: d.fontStyle || fontStyle,
1435-
variant: d.fontVariant || fontVariant
1448+
variant: d.fontVariant || fontVariant,
1449+
textcase: d.fontTextcase || fontTextcase,
1450+
lineposition: d.fontLineposition || fontLineposition,
1451+
shadow: d.fontShadow || fontShadow,
14361452
}).text(name)
14371453
.attr('data-notex', 1)
14381454
.call(svgTextUtils.positionText, 0, 0)

src/components/legend/defaults.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ function groupDefaults(legendId, layoutIn, layoutOut, fullData) {
3333
};
3434

3535
var globalFont = layoutOut.font || {};
36-
var grouptitlefont = Lib.coerceFont(coerce, 'grouptitlefont', Lib.extendFlat({}, globalFont, {
36+
var grouptitlefont = Lib.coerceFont(coerce, 'grouptitlefont', globalFont, { overrideDflt: {
3737
size: Math.round(globalFont.size * 1.1)
38-
}));
38+
}});
3939

4040
var legendTraceCount = 0;
4141
var legendReallyHasATrace = false;

src/components/legend/style.js

+3
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ module.exports = function style(s, gd, legend) {
247247
dEdit.tw = boundVal('textfont.weight', pickFirst);
248248
dEdit.ty = boundVal('textfont.style', pickFirst);
249249
dEdit.tv = boundVal('textfont.variant', pickFirst);
250+
dEdit.tC = boundVal('textfont.textcase', pickFirst);
251+
dEdit.tE = boundVal('textfont.lineposition', pickFirst);
252+
dEdit.tS = boundVal('textfont.shadow', pickFirst);
250253
}
251254

252255
dMod = [Lib.minExtend(d0, dEdit)];

src/components/titles/index.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ function draw(gd, titleClass, options) {
7171
var fontWeight = font.weight;
7272
var fontStyle = font.style;
7373
var fontVariant = font.variant;
74+
var fontTextcase = font.textcase;
75+
var fontLineposition = font.lineposition;
76+
var fontShadow = font.shadow;
7477

7578
// only make this title editable if we positively identify its property
7679
// as one that has editing enabled.
@@ -144,14 +147,17 @@ function draw(gd, titleClass, options) {
144147

145148
titleEl.attr('transform', transformVal);
146149

147-
titleEl.style({
148-
'font-family': fontFamily,
149-
'font-size': d3.round(fontSize, 2) + 'px',
150-
fill: Color.rgb(fontColor),
151-
opacity: opacity * Color.opacity(fontColor),
152-
'font-weight': fontWeight,
153-
'font-style': fontStyle,
154-
'font-variant': fontVariant
150+
titleEl.style('opacity', opacity * Color.opacity(fontColor))
151+
.call(Drawing.font, {
152+
color: Color.rgb(fontColor),
153+
size: d3.round(fontSize, 2),
154+
family: fontFamily,
155+
weight: fontWeight,
156+
style: fontStyle,
157+
variant: fontVariant,
158+
textcase: fontTextcase,
159+
shadow: fontShadow,
160+
lineposition: fontLineposition,
155161
})
156162
.attr(attributes)
157163
.call(svgTextUtils.convertToTspans, gd);

src/lib/coerce.js

+20-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
var isNumeric = require('fast-isnumeric');
44
var tinycolor = require('tinycolor2');
55

6+
var extendFlat = require('./extend').extendFlat;
7+
68
var baseTraceAttrs = require('../plots/attributes');
79
var colorscales = require('../components/colorscale/scales');
810
var Color = require('../components/color');
@@ -470,18 +472,27 @@ exports.coerce2 = function(containerIn, containerOut, attributes, attribute, dfl
470472
*/
471473
exports.coerceFont = function(coerce, attr, dfltObj, opts) {
472474
if(!opts) opts = {};
475+
dfltObj = extendFlat({}, dfltObj);
476+
dfltObj = extendFlat(dfltObj, opts.overrideDflt || {});
473477

474-
var out = {};
475-
476-
dfltObj = dfltObj || {};
477-
478-
out.family = coerce(attr + '.family', dfltObj.family);
479-
out.size = coerce(attr + '.size', dfltObj.size);
480-
out.color = coerce(attr + '.color', dfltObj.color);
478+
var out = {
479+
family: coerce(attr + '.family', dfltObj.family),
480+
size: coerce(attr + '.size', dfltObj.size),
481+
color: coerce(attr + '.color', dfltObj.color),
482+
weight: coerce(attr + '.weight', dfltObj.weight),
483+
style: coerce(attr + '.style', dfltObj.style),
484+
};
481485

482-
out.weight = coerce(attr + '.weight', dfltObj.weight);
483-
out.style = coerce(attr + '.style', dfltObj.style);
484486
if(!opts.noFontVariant) out.variant = coerce(attr + '.variant', dfltObj.variant);
487+
if(!opts.noFontLineposition) out.lineposition = coerce(attr + '.lineposition', dfltObj.lineposition);
488+
if(!opts.noFontTextcase) out.textcase = coerce(attr + '.textcase', dfltObj.textcase);
489+
if(!opts.noFontShadow) {
490+
var dfltShadow = dfltObj.shadow;
491+
if(dfltShadow === 'none' && opts.autoShadowDflt) {
492+
dfltShadow = 'auto';
493+
}
494+
out.shadow = coerce(attr + '.shadow', dfltShadow);
495+
}
485496

486497
return out;
487498
};

src/plots/cartesian/axes.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,9 @@ function tickTextObj(ax, x, text) {
17401740
fontWeight: tf.weight,
17411741
fontStyle: tf.style,
17421742
fontVariant: tf.variant,
1743+
fontTextcase: tf.textcase,
1744+
fontLineposition: tf.lineposition,
1745+
fontShadow: tf.shadow,
17431746
fontColor: tf.color
17441747
};
17451748
}
@@ -3507,7 +3510,10 @@ axes.drawLabels = function(gd, ax, opts) {
35073510
color: d.fontColor,
35083511
weight: d.fontWeight,
35093512
style: d.fontStyle,
3510-
variant: d.fontVariant
3513+
variant: d.fontVariant,
3514+
textcase: d.fontTextcase,
3515+
lineposition: d.fontLineposition,
3516+
shadow: d.fontShadow,
35113517
})
35123518
.text(d.text)
35133519
.call(svgTextUtils.convertToTspans, gd);

src/plots/cartesian/axis_defaults.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
111111
if(!visible) return containerOut;
112112

113113
coerce('title.text', dfltTitle);
114-
Lib.coerceFont(coerce, 'title.font', {
115-
family: font.family,
116-
weight: font.weight,
117-
style: font.style,
118-
variant: font.variant,
114+
Lib.coerceFont(coerce, 'title.font', font, { overrideDflt: {
119115
size: Lib.bigFont(font.size),
120116
color: dfltFontColor
121-
});
117+
}});
122118

123119
// major ticks
124120
handleTickValueDefaults(containerIn, containerOut, coerce, axType);

src/plots/cartesian/tick_label_defaults.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,9 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe
2626
(contColor && contColor !== layoutAttributes.color.dflt) ?
2727
contColor : font.color;
2828

29-
Lib.coerceFont(coerce, 'tickfont', {
30-
family: font.family,
31-
weight: font.weight,
32-
style: font.style,
33-
variant: font.variant,
34-
size: font.size,
29+
Lib.coerceFont(coerce, 'tickfont', font, { overrideDflt: {
3530
color: dfltFontColor
36-
});
31+
}});
3732

3833
if(
3934
!options.noTicklabelstep &&

src/plots/font_attributes.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,42 @@ module.exports = function(opts) {
9999
].join(' ')
100100
},
101101

102+
textcase: opts.noFontTextcase ? undefined : {
103+
editType: editType,
104+
valType: 'enumerated',
105+
values: ['normal', 'word caps', 'upper', 'lower'],
106+
dflt: 'normal',
107+
description: [
108+
'Sets capitalization of text.',
109+
'It can be used to make text appear in all-uppercase or all-lowercase,',
110+
'or with each word capitalized.'
111+
].join(' ')
112+
},
113+
114+
lineposition: opts.noFontLineposition ? undefined : {
115+
editType: editType,
116+
valType: 'flaglist',
117+
flags: ['under', 'over', 'through'],
118+
extras: ['none'],
119+
dflt: 'none',
120+
description: [
121+
'Sets the kind of decoration line(s) with text,',
122+
'such as an *under*, *over* or *through*',
123+
'as well as combinations e.g. *under+over*, etc.'
124+
].join(' ')
125+
},
126+
127+
shadow: opts.noFontShadow ? undefined : {
128+
editType: editType,
129+
valType: 'string',
130+
dflt: opts.autoShadowDflt ? 'auto' : 'none',
131+
description: [
132+
'Sets the shape and color of the shadow behind text.',
133+
'*auto* places minimal shadow and applies contrast text font color.',
134+
'See https://developer.mozilla.org/en-US/docs/Web/CSS/text-shadow for additional options.'
135+
].join(' ')
136+
},
137+
102138
editType: editType,
103139
// blank strings so compress_attributes can remove
104140
// TODO - that's uber hacky... better solution?
@@ -112,7 +148,18 @@ module.exports = function(opts) {
112148
attrs.family.arrayOk = true;
113149
attrs.weight.arrayOk = true;
114150
attrs.style.arrayOk = true;
115-
attrs.variant.arrayOk = true;
151+
if(!opts.noFontVariant) {
152+
attrs.variant.arrayOk = true;
153+
}
154+
if(!opts.noFontTextcase) {
155+
attrs.textcase.arrayOk = true;
156+
}
157+
if(!opts.noFontLineposition) {
158+
attrs.lineposition.arrayOk = true;
159+
}
160+
if(!opts.noFontShadow) {
161+
attrs.shadow.arrayOk = true;
162+
}
116163
attrs.size.arrayOk = true;
117164
attrs.color.arrayOk = true;
118165
}

src/plots/mapbox/layout_attributes.js

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ var constants = require('./constants');
1212

1313
var fontAttr = fontAttrs({
1414
noFontVariant: true,
15+
noFontShadow: true,
16+
noFontLineposition: true,
17+
noFontTextcase: true,
1518
description: [
1619
'Sets the icon text font (color=mapbox.layer.paint.text-color, size=mapbox.layer.layout.text-size).',
1720
'Has an effect only when `type` is set to *symbol*.'

src/plots/mapbox/layout_defaults.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ function handleLayerDefaults(layerIn, layerOut) {
104104

105105
coerce('symbol.text');
106106
Lib.coerceFont(coerce, 'symbol.textfont', undefined, {
107-
noFontVariant: true
107+
noFontVariant: true,
108+
noFontShadow: true,
109+
noFontLineposition: true,
110+
noFontTextcase: true,
108111
});
109112
coerce('symbol.textposition');
110113
coerce('symbol.placement');

0 commit comments

Comments
 (0)