Public
Edited
Nov 20, 2022
1 fork
Importers
58 stars
Insert cell
Insert cell
Insert cell
distributionScore = (palette, criteria, distance=reldist) => {
// We get the sorted measure for our N colours
const dist = sorted(map(palette, criteria))
// The ideal luminance delta is the luminance range of the palette / (N - 1)
const ideal = extent(dist)/((len(dist) - 1)||1);
// The score is the relative distance to our ideal, 0=FAIL 1=SUCCESS
// where a FAIL means we have exceeded have a delta more than 1x the ideal
const scoring = _ => clamp(1 - distance(_, ideal), 0, 1);
// We get the mean scoring
return mean(map(gradients(dist),scoring))
}

Insert cell
luminanceDistributionScore = palette => {
const lum_pal = sorted(map(palette, luminance));
const lum_grd = gradients(lum_pal);
const lum_grd_target =
(lum_pal[lum_pal.length - 1] - lum_pal[0]) / len(palette);
const [ok, not_ok] = partition(
lum_grd,
_ => reldist(_, lum_grd_target) <= 0.25
);
return len(ok) / len(palette);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rangeScore = (palette, criteria) =>
extent(map(palette, criteria))
Insert cell
luminanceRangeScore = palette =>
rangeScore(palette, luminance)

Insert cell
Insert cell
Insert cell
Insert cell
luminanceContrastScore = palette => {
// The minimum luminance difference is 0.05
const threshold = 0.05;
const lum = map(palette, luminance)
// We partition based on the satisfaction of the minimum luminance criteria
const [ok, notok] = partition(combinations(lum, 2).map( ([a,b])=>Math.abs(b - a)), _ => _ >= threshold)
// The ideal luminance difference is the extent of the luminance range divided by the number
// of colours in the palette.
const ideal = Math.max(threshold, extent(lum)/len(lum));
// NOTE: If we put our ideal difference in absolute (ie. assuming a luminance range of 1.0),
// we're going to have worse results.
const [above, below] = partition(ok, _ => (_ - ideal) >= 0)
return len(notok) ? 0 : len(above) / len(ok);
}
Insert cell
Insert cell
Insert cell
Insert cell
hueDistributionScore = palette =>
// We reuse the distribution score, but this time with a different distance function,
// as the hue is circular/periodic.
distributionScore(palette, hue, (a,b) => circdist(a,b))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
hueRangeScore = palette =>
// The hue range is trickier to calculate due to the periodic nature of the
// hue, and that low-saturation colours (greys) might distort the hue components.
// Imagine a palette of greys, each with a different hue, which would get a high
// score, but in practice would be grey.
max(map(combinations(map(filter(palette, _ => saturation(_) >= 0.25), hue)), ([a,b])=>circdist(a,b)))

Insert cell
Insert cell
Insert cell
Insert cell
hueContrastScore = palette => {
// The minimum hue difference is 0.05
const threshold = 0.05;
const hues = map(palette, hue);
// We partition based on the satisfaction of the minimum luminance criteria
const [ok, notok] = partition(combinations(hues, 2).map( ([a,b])=>Math.abs(circdist(a,b))), _ => _ >= threshold);
// The ideal hue difference is the extent of the hue range divided by the number
// of colours in the palette.
// FIXME: This is quite costly
const huedists = map(combinations(hues, 2), _ => circdist(_[0], _[1]));
const [minhue, maxhue] = minmax(huedists);
const ideal = Math.max(threshold, (maxhue-minhue)/len(palette));
// NOTE: If we put our ideal difference in absolute (ie. assuming a luminance range of 1.0),
// we're going to have worse results.
const [above, below] = partition(ok, _ => dist(ideal, _) >= 0)
return len(above) / (len(ok)||1);
}
Insert cell
Scoring(hueContrastScore, {
Hues: _ => map(sorted(filter(_, _=>saturation(_)>=0.25), hue), _ => Swatch(rgb(255*gamma(hue(_))))),
Score: (_,s) => Score(s),
Rating: (_,s) => Rating(s),
})
Insert cell
Insert cell
hueClusters = (palette, threshold=0.20) => {
const byhue = sorted(palette, hue)
const hues = byhue.map(hue);
return hues.reduce((r,v,i)=>{
if (i === 0 || circdist(hues[i], hues[i-1]) > threshold) {
r.push([]);
}
r[r.length - 1].push(byhue[i]);
return r;
}, []);
}
Insert cell
hueClustersScore = palette => len(hueClusters(palette))
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more