Last active
January 3, 2018 19:42
-
-
Save hhrogersii/cd3ff2a1031204276522 to your computer and use it in GitHub Desktop.
Maintain equidistant text contrast against background palette gradient
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=320" /> | |
<meta name="apple-mobile-web-app-capable" content="yes" /> | |
<title>Visualizing Text/Background Color Contrast</title> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sucrose@0.7.5/build/sucrose.min.css"> | |
<script> | |
less = { | |
globalVars: { | |
env: 'production', | |
// color palette | |
PALETTE: [ | |
{ color: '#de3839', name: 'red' }, | |
{ color: '#f19547', name: 'orange' }, | |
{ color: '#f3cc25', name: 'yellow' }, | |
{ color: '#3c910e', name: 'army' }, | |
{ color: '#61d523', name: 'green' }, | |
{ color: '#2fdbca', name: 'mint' }, | |
{ color: '#24e7f4', name: 'teal' }, | |
{ color: '#3ab5f5', name: 'pacific' }, | |
{ color: '#166e90', name: 'ocean' }, | |
{ color: '#1285d5', name: 'blue' }, | |
{ color: '#09467c', name: 'night' }, | |
{ color: '#956cd6', name: 'purple' }, | |
{ color: '#da8ee5', name: 'pink' }, | |
{ color: '#d35db1', name: 'coral' }, | |
{ color: '#555555', name: 'gray' } | |
], | |
// number of swatches per gradient | |
SWATCHCOUNT: 21, | |
// height of gradient row | |
ROWHEIGHT: '48px' | |
} | |
}; | |
</script> | |
<style type="text/less"> | |
body { | |
padding: 10px 20px 20px; | |
margin: 0; | |
font: normal 12px/18px Helvetica, Arial, sans-serif; | |
color: #555; | |
background-color: #fff; | |
box-sizing: border-box; | |
} | |
h1 { | |
font-size: 15px; | |
display: inline-block; | |
} | |
.toggle { | |
display: inline-block; | |
margin: 0 0 0 20px; | |
background-color: #fff; | |
padding: 4px; | |
cursor: pointer; | |
border: 1px solid transparent; | |
border-radius: 3px; | |
color: blue; | |
&:hover { | |
text-decoration: underline; | |
color: purple; | |
} | |
&.selected { | |
border-color: #777; | |
} | |
} | |
.palette { | |
position: relative; | |
} | |
.gradient { | |
height: @ROWHEIGHT; | |
span { | |
display: block; | |
line-height: 12px; | |
} | |
div { | |
height: @ROWHEIGHT; | |
float: left; | |
text-align: center; | |
font-size: 10px; | |
font-weight: bold; | |
box-sizing: border-box; | |
&.label { | |
padding-top: (@ROWHEIGHT - 20) / 2; | |
width: 6%; | |
} | |
&.swatch { | |
@width: ~"100% / @{SWATCHCOUNT}"; | |
width: calc(@width); | |
padding-top: (@ROWHEIGHT - 12) / 2; | |
} | |
} | |
} | |
.swatches-wrap { | |
position: relative; | |
width: 94%; | |
} | |
.sparkline-wrap { | |
position: absolute; | |
width: 100%; | |
top: 0; | |
left: 0; | |
z-index: 1; | |
background-color: transparent; | |
pointer-events: none; | |
&.show { | |
display: block; | |
} | |
&.hide { | |
display: none; | |
} | |
} | |
.sparkline-chart { | |
display: block; | |
width: 100%; | |
height: 100%; | |
path.sc-line, | |
.sc-point-paths path { | |
stroke-width: 1px; | |
} | |
} | |
</style> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/sucrose@0.7.5/build/sucrose.js"></script> | |
</head> | |
<body> | |
<!-- Maintain equidistant text contrast against background palette gradients --> | |
<h1>Visualizing Text/Background Color Contrast</h1> | |
<button class="toggle" onclick="toggleGraph(event)">toggle graph</button> | |
<button class="toggle" onclick="toggleContrast(event)">no contrast adjustment</button> | |
<div class="palette"></div> | |
<script> | |
var config, scale, lineChart, palette, gradientsEnter, gradients; | |
var dispatch, noContrastAdjust, tooltip; | |
// adjust contrast of text color relative to background | |
function getTextContrast(backColor, callback) { | |
var backLab, backLumen, brighter, darker, textLumen, textLab, text; | |
backLab = d3.lab(backColor); | |
backLumen = backLab.l; | |
brighter = noContrastAdjust === 'false' ? | |
backLab.brighter(4 + (18 - backLumen) / 25).l : | |
255; // (0..50)[3..1] | |
darker = noContrastAdjust === 'false' ? | |
backLab.darker(4 + (backLumen - 75) / 25).l : | |
0; // (50..100)[1..3] | |
textLumen = backLumen > 60 ? darker : brighter; | |
textLab = d3.lab(textLumen, 0, 0); | |
text = textLab.toString(); | |
callback(backLumen, textLumen); | |
return text; | |
} | |
// chart main | |
function renderChart(viz, chart, data) { | |
var width, swatchHalfWidth; | |
width = parseInt(d3.select('.swatches-wrap').style('width'), 10); | |
swatchHalfWidth = width / config.SWATCHCOUNT / 2; | |
// reset chart width and margin to align points with swatch center | |
chart | |
.width(width) | |
.margin({ | |
top: 5, | |
right: swatchHalfWidth, | |
bottom: 5, | |
left: swatchHalfWidth | |
}); | |
// instantiate chart | |
viz | |
.datum(data) | |
.call(chart); | |
} | |
// resize swatches and chart on window resize | |
function windowResize(fn) { | |
if (window.attachEvent) { | |
window.attachEvent('onresize', fn); | |
} else if (window.addEventListener) { | |
window.addEventListener('resize', fn, true); | |
} | |
} | |
// draw the palette swatches and chart | |
function paint(gradients) { | |
// for each gradient row | |
gradients.each(function(g) { | |
var backData, textData, lineData, colors, gradient, sparkline; | |
// color data stores for visualization | |
backData = []; | |
textData = []; | |
lineData = []; | |
// setup color gradient based on background-color | |
colors = d3.scaleLinear() | |
.range(['#000000', g.color, '#FFFFFF']) | |
.domain([0, 50, 100]) | |
.interpolate(d3.interpolateLab); | |
// current gradient row | |
gradient = d3.select(this); | |
// line chart svg | |
sparkline = gradient.select('.sparkline-chart'); | |
// gradient color details | |
gradient.select('.label').html(function(d) { | |
return '<span>@' + d.name + '</span><span>' + d.color + '</span>'; | |
}); | |
// for each swatch set data stores and swatch attributes | |
gradient.selectAll('.swatch').each(function(d, i) { | |
var data = {}; | |
// get back color from color array | |
data.backColor = colors(scale(i)); | |
// calculate text color based on back color lumen | |
data.textColor = getTextContrast(data.backColor, function(b, t) { | |
// store color values for visualization | |
data.backLumen = b; | |
data.textLumen = t; | |
backData.push({ | |
x: i, | |
y: b | |
}); | |
textData.push({ | |
x: i, | |
y: t | |
}); | |
}); | |
// set background and text color | |
d3.select(this).datum(data) | |
.style('background-color', data.backColor) | |
.select('span') | |
.style('color', data.textColor); | |
}); | |
lineData = [{ | |
'key': g.name, | |
'values': backData, | |
'color': '#00f2ff' | |
}, //cyan | |
{ | |
'key': 'text', | |
'values': textData, | |
'color': '#ff00ca' | |
} //magenta | |
]; | |
renderChart(sparkline, lineChart, lineData); | |
// rerender chart on window resize | |
windowResize(function() { | |
renderChart(sparkline, lineChart, lineData); | |
}); | |
}); | |
} | |
// ------------------------------------------------------------------------- | |
// Main | |
config = less.options.globalVars; | |
noContrastAdjust = 'false'; | |
// for converting swatch index to lumen | |
scale = d3.scaleLinear() | |
.domain([0, config.SWATCHCOUNT - 1]) | |
.range([0, 100]); | |
// sparkline model | |
lineChart = sucrose.models.line() | |
.x(function(d) { | |
return d.x; | |
}) | |
.y(function(d) { | |
return d.y; | |
}) | |
.useVoronoi(false) | |
.height(parseInt(config.ROWHEIGHT, 10)); | |
// preset toggle button state | |
d3.selectAll('.toggle').datum({selected: 'false'}); | |
// outer container | |
palette = d3.select('.palette'); | |
// a gradient row for each color in palette | |
gradientsEnter = palette.selectAll('.gradient') | |
.data(config.PALETTE) | |
.enter().append('div') | |
.attr('class', 'gradient'); | |
gradients = palette.selectAll('.gradient').merge(gradientsEnter); | |
// build palette with swatches | |
gradientsEnter | |
.append('div').attr('class', 'label'); | |
gradientsEnter | |
.append('div').attr('class', 'swatches-wrap') | |
.selectAll('.swatches').data(d3.range(0, config.SWATCHCOUNT)) | |
.enter().append('div') | |
.attr('class', 'swatch') | |
.html('<span>TEXT</span>'); | |
// line chart container | |
gradientsEnter.select('.swatches-wrap') | |
.append('div').attr('class', 'sparkline-wrap hide') | |
.append('svg').attr('class', 'sparkline-chart') | |
.attr('id', function(d, i) { return 'sparkline-' + i; }); | |
// draw the palette swatches and chart for each gradient row | |
gradients.call(paint); | |
// ------------------------------------------------------------------------- | |
// Buttons | |
// handle toggling of button states | |
function toggleState(e) { | |
var el, s; | |
el = d3.select(e.srcElement); | |
s = el.datum().selected === 'false' ? 'true' : 'false'; | |
el.datum({selected: s}); | |
el.classed('selected', s === 'true'); | |
return s; | |
} | |
// hide/show visualization | |
function toggleGraph(e) { | |
var s = toggleState(e); | |
d3.selectAll('.sparkline-wrap').classed('hide', s === 'false'); | |
} | |
// toggle use of text contrast adjustment | |
function toggleContrast(e) { | |
var s = toggleState(e); | |
noContrastAdjust = s; | |
gradients.call(paint); | |
} | |
// ------------------------------------------------------------------------- | |
// Tooltips | |
function buildEventObject(e, d, i) { | |
return { | |
e: e, | |
point: d, | |
pointIndex: i | |
}; | |
} | |
function tooltipContent(d) { | |
return '<p><i>Back Lumen: </i>' + Math.round(d.backLumen, 2) + '</p>' + | |
'<p><i>Text Lumen: </i>' + Math.round(d.textLumen, 2) + '</p>' + | |
'<p><i>Back Color: </i>' + d.backColor + '</p>' + | |
'<p><i>Text Color: </i>' + d.textColor + '</p>'; | |
} | |
function showTooltip(eo, offsetElement) { | |
var content = tooltipContent(eo.point); | |
tooltip = sucrose.tooltip.show(eo.e, content, 'n', null, offsetElement); | |
} | |
tooltip = null; | |
dispatch = d3.dispatch('Mouseover', 'Mousemove', 'Mouseout'); | |
dispatch.on('Mouseover.tooltip', function(e) { | |
showTooltip(e, palette.node()); | |
}); | |
dispatch.on('Mousemove.tooltip', function(e) { | |
if (tooltip) { | |
sucrose.tooltip.position(palette.node(), tooltip, e); | |
} | |
}); | |
dispatch.on('Mouseout.tooltip', function(e) { | |
if (tooltip) { | |
sucrose.tooltip.cleanup(); | |
} | |
}); | |
d3.selectAll('.swatch') | |
.on('mouseover', function(d, i) { | |
var eo = buildEventObject(d3.event, d, i); | |
d3.select(this).classed('hover', true); | |
dispatch.call('Mouseover', this, eo); | |
}) | |
.on('mousemove', function(d, i) { | |
var e = d3.event; | |
dispatch.call('Mousemove', this, e); | |
}) | |
.on('mouseout', function(d, i) { | |
var eo = buildEventObject(d3.event, d, i); | |
d3.select(this).classed('hover', false); | |
dispatch.call('Mouseout', this, eo); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment