Skip to content

Commit d6127d1

Browse files
committed
Refactor to better match tinycolor2
1 parent f27aa55 commit d6127d1

4 files changed

Lines changed: 107 additions & 40 deletions

File tree

src/components/color/index.js

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
'use strict';
2-
1+
const _color = require('color').default;
32
const isNumeric = require('fast-isnumeric');
4-
const isTypedArray = require('../../lib/array').isTypedArray;
5-
const color = require('color').default;
6-
3+
const { isTypedArray } = require('../../lib/array');
74
const { background, defaultLine, defaults, lightLine } = require('./attributes');
85

6+
// Safe wrapper: falls back to black instead of throwing on invalid input.
7+
// This matches the tinycolor2 behavior.
8+
const color = (cstr) => {
9+
try {
10+
return _color(cstr);
11+
} catch (e) {
12+
return _color('#000');
13+
}
14+
};
15+
916
const rgb = (cstr) => {
1017
const { r, g, b } = color(cstr).rgb().object();
1118
return `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
@@ -79,15 +86,12 @@ const contrast = (cstr, lightAmount, darkAmount) => {
7986
if (c.alpha() !== 1) c = color(combine(cstr, background));
8087

8188
// TODO: Should the API change such that lightAmount/darkAmount are passed in as decimal instead of percent number?
82-
const newColor = color(
83-
c.isDark()
84-
? lightAmount
85-
? c.lighten(lightAmount / 100)
86-
: background
87-
: darkAmount
88-
? c.darken(darkAmount / 100)
89-
: defaultLine
90-
);
89+
let newColor;
90+
if (c.isDark()) {
91+
newColor = color(lightAmount ? c.lighten(lightAmount / 100) : background);
92+
} else {
93+
newColor = color(darkAmount ? c.darken(darkAmount / 100) : defaultLine);
94+
}
9195

9296
return newColor.rgb().string();
9397
};
@@ -175,39 +179,52 @@ const equals = (cstr1, cstr2) => cstr1 && cstr2 && color(cstr1).rgb().string() =
175179

176180
const isValid = (cstr) => {
177181
try {
178-
return cstr && !!color(cstr);
182+
return cstr && !!_color(cstr);
179183
} catch {
180184
return false;
181185
}
182186
};
183187

188+
// RGB-space brightening equivalent to tinycolor's brighten().
189+
// Adds a fixed amount to each RGB channel (unlike lighten which works in HSL space).
190+
const brighten = (cstr, amount) => {
191+
amount = amount === 0 ? 0 : amount || 10;
192+
const c = color(cstr).rgb().object();
193+
const adj = Math.round(255 * (amount / 100));
194+
return color({
195+
r: Math.max(0, Math.min(255, c.r + adj)),
196+
g: Math.max(0, Math.min(255, c.g + adj)),
197+
b: Math.max(0, Math.min(255, c.b + adj))
198+
})
199+
.rgb()
200+
.string();
201+
};
202+
184203
const mix = (cstr1, cstr2, weight) =>
185204
color(cstr1)
186205
.mix(color(cstr2), weight / 100)
187206
.rgb()
188207
.string();
189208

190-
const mostReadable = (baseColor, colorList = []) => {
209+
const mostReadable = (baseColor, colorList = ['#000', '#fff']) => {
191210
let bestColor;
192211
let bestContrast = -Infinity;
193212

194213
for (const cstr of colorList) {
195-
const contrast = color(baseColor).contrast(color(cstr));
196-
if (contrast > bestContrast) {
197-
bestContrast = contrast;
214+
const contrastRatio = color(baseColor).contrast(color(cstr));
215+
if (contrastRatio > bestContrast) {
216+
bestContrast = contrastRatio;
198217
bestColor = color(cstr).rgb().string();
199218
}
200219
}
201220

202-
// Fall back to black/white if provided colors don't have proper contrast level
203-
return bestColor && color(baseColor).level(color(bestColor))
204-
? bestColor
205-
: mostReadable(baseColor, ['#000', '#fff']);
221+
return bestColor;
206222
};
207223

208224
module.exports = {
209225
addOpacity,
210226
background,
227+
brighten,
211228
clean,
212229
color,
213230
combine,

src/traces/sankey/defaults.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3737

3838
var colors = layout.colorway;
3939

40-
var defaultNodePalette = function(i) {return colors[i % colors.length];};
40+
const defaultNodePalette = (i) => colors[i % colors.length];
4141

42-
coerceNode('color', nodeOut.label.map(function(d, i) {
43-
return Color.addOpacity(defaultNodePalette(i), 0.8);
44-
}));
42+
coerceNode(
43+
'color',
44+
nodeOut.label.map((d, i) => Color.addOpacity(defaultNodePalette(i), 0.8))
45+
);
4546
coerceNode('customdata');
4647

4748
// link attributes
@@ -68,19 +69,22 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
6869

6970
function makeDefaultHoverColor(_linkColor) {
7071
// hopefully the user-specified color is valid, but if not that can be caught elsewhere
71-
if(!Color.isValid(_linkColor)) return _linkColor;
72+
if (!Color.isValid(_linkColor)) return _linkColor;
7273

7374
const c = Color.color(_linkColor);
7475
const alpha = c.alpha();
7576

7677
return alpha <= 0.8
77-
? c.alpha(alpha + 0.2).rgb().string()
78-
: (darkBG ? c.lighten(0.1) : c.darken(0.1)).rgb().string()
78+
? c
79+
.alpha(alpha + 0.2)
80+
.rgb()
81+
.string()
82+
: (darkBG ? c.lighten(0.1) : c.darken(0.1)).rgb().string();
7983
}
8084

81-
coerceLink('hovercolor', Array.isArray(linkColor) ?
82-
linkColor.map(makeDefaultHoverColor) :
83-
makeDefaultHoverColor(linkColor)
85+
coerceLink(
86+
'hovercolor',
87+
Array.isArray(linkColor) ? linkColor.map(makeDefaultHoverColor) : makeDefaultHoverColor(linkColor)
8488
);
8589

8690
coerceLink('customdata');
@@ -96,10 +100,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
96100
coerce('valueformat');
97101
coerce('valuesuffix');
98102

99-
var dfltArrangement;
100-
if(nodeOut.x.length && nodeOut.y.length) {
101-
dfltArrangement = 'freeform';
102-
}
103+
const dfltArrangement = nodeOut.x.length && nodeOut.y.length ? 'freeform' : undefined;
103104
coerce('arrangement', dfltArrangement);
104105

105106
Lib.coerceFont(coerce, 'textfont', layout.font, { autoShadowDflt: true });

test/image/mocks/test_mock.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"data": [{ "type": "scatter", "x": [1, 2], "y": [1, 2] }],
3+
"layout": {
4+
"margin": { "t": 92 },
5+
"shapes": [
6+
{
7+
"label": {
8+
"text": "Frankenstein",
9+
"textangle": 0,
10+
"textposition": "middle center",
11+
"xanchor": "center",
12+
"yanchor": "middle"
13+
},
14+
"showlegend": false,
15+
"type": "circle",
16+
"x0": -25,
17+
"x1": 25,
18+
"xanchor": 1,
19+
"xref": "x",
20+
"xsizemode": "pixel",
21+
"y0": 25,
22+
"y1": 75,
23+
"yanchor": 1,
24+
"yref": "paper",
25+
"ysizemode": "pixel"
26+
},
27+
{
28+
"label": {
29+
"text": "Rectangle",
30+
"textangle": 0,
31+
"textposition": "middle center",
32+
"xanchor": "center",
33+
"yanchor": "middle"
34+
},
35+
"showlegend": false,
36+
"type": "rectangle",
37+
"x0": -25,
38+
"x1": 25,
39+
"xanchor": 2,
40+
"xref": "x",
41+
"xsizemode": "pixel",
42+
"y0": 25,
43+
"y1": 75,
44+
"yanchor": 1,
45+
"yref": "paper",
46+
"ysizemode": "pixel"
47+
}
48+
]
49+
}
50+
}

test/jasmine/tests/color_test.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,8 @@ describe('Test color:', function() {
5656
});
5757

5858
it('should count 0 as a fraction but not 1, except in alpha', function() {
59-
// this is weird... but old tinycolor actually breaks
60-
// if you pass in a 1, while in some cases a 1 here
61-
// could be ambiguous - so we treat it as a real 1.
59+
// a value of 1 here could be ambiguous (fraction vs integer)
60+
// so we treat it as a real 1.
6261
var container = {
6362
fractioncolor: 'rgb(0, 0.4, 0.8)',
6463
regularcolor: 'rgb(1, 0.5, 0.5)',

0 commit comments

Comments
 (0)