This bl.ock builds upon HSL Color Picker
by Mike Bostock and i want hue
by Médialab.
Last active
September 12, 2016 14:32
Star
You must be signed in to star a gist
HSL Qualitative Palette Generator
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
license: mit | |
border: no | |
height: 540 |
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> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<style> | |
* { box-sizing: border-box; } | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
p, svg text, label { | |
font-size: 13px; | |
font-family: sans-serif; } | |
svg { overflow: visible; } | |
.left-container { | |
display: inline-block; | |
width: 360px; | |
vertical-align: top; | |
} | |
.right-container { | |
display: inline-block; | |
width: 540px; | |
padding-left: 16px; | |
} | |
input[type="number"] { | |
outline: none; | |
border: none; | |
border-bottom: 1px solid #DDD; | |
text-align: right; | |
font-size: 13px; | |
width: 32px; } | |
input[type="range"] { | |
width: 100%; | |
} | |
svg.gradient { | |
width: 360px; | |
height: 20px; | |
} | |
svg.gradient { | |
stroke: none; | |
} | |
svg.hue-ctrl .hue-ticks circle { | |
stroke: #999; | |
stroke-width: 1.5px; | |
} | |
svg.hue-ctrl .hue-ticks circle:first-of-type { | |
stroke: black; | |
cursor: move; | |
} | |
.color-list { | |
font-family: monospace; | |
padding: 8px; | |
background: #F9F9F9; | |
} | |
.state-map .state { | |
stroke: #666; | |
stroke-width: 0.5px; | |
} | |
.sparklines path { | |
stroke-width: 2px; | |
fill: none; | |
} | |
.axis { | |
stroke: black; | |
shape-rendering: crispEdges; | |
} | |
</style> | |
<div class='left-container'> | |
<p> | |
Build a palette of | |
<input type='number' | |
dimension='nColors' | |
value='6' min='1' max='360' /> colors, with<br /> | |
Saturation: <span class='s-value'>50</span>% | |
<svg class='gradient saturation'></svg> | |
<input class='s-ctrl' | |
dimension='saturation' | |
type='range' | |
min='0' max='1' step='0.01' value='0.66'><br /> | |
Lightness: <span class='l-value'>66</span>% | |
<svg class='gradient lightness'></svg> | |
<input class='l-ctrl' | |
dimension='lightness' | |
type='range' | |
min='0' max='1' step='0.01' value='0.66'> | |
</p> | |
<svg class='hue-ctrl' width='360' height='360'></svg> | |
</div> | |
<div class='right-container'> | |
<p class='color-list'></p> | |
<svg class='state-map' width='540' height='300'></svg> | |
<svg width='540' height='140'> | |
<line class='axis' x1='0' x2='540' y1='70' y2='70' /> | |
<line class='axis' x1='0' x2='0' y1='0' y2='140' /> | |
<g class='sparklines'></g> | |
</svg> | |
</div> | |
</head> | |
<body> | |
<script> | |
// equidistant hues, same saturation and lightness | |
function palette(nColors, dHue, saturation, lightness) { | |
return d3.range((0 + dHue), (360 + dHue), (360 / nColors)) | |
.map(function(hue) { | |
hue = hue % 360; | |
var hslObj = d3.hsl(hue, saturation, lightness); | |
return { | |
h: hue, | |
s: saturation, | |
l: lightness, | |
_: hslObj, | |
rgb: hslObj.toString() | |
}; | |
}); | |
} | |
// | |
// state, data | |
var _palette = [], | |
_input = { | |
nColors: 5, | |
dHue: 0, | |
saturation: 0.80, | |
lightness: 0.80 | |
}; | |
// | |
// misc constants | |
var radToDeg = 180 / Math.PI, | |
tau = Math.PI * 2; | |
// | |
// rendering | |
var hueCtrl = d3.select('svg.hue-ctrl'), | |
hueWidth = 360, | |
hueCenter = hueWidth / 2, | |
r0 = 170, | |
r1 = r0 - 32, | |
arcs = 360, | |
gradientWidth = 360, | |
gradientRects = 100, | |
hueDrag = d3.behavior.drag(); | |
hueCtrl.append('g') | |
.attr('class', 'hue-arcs') | |
.selectAll("path") | |
.data(d3.range(0, tau, tau / arcs)) | |
.enter().append("path") | |
.attr("d", d3.svg.arc().innerRadius(r0).outerRadius(r1) | |
.startAngle(function(d) { return d; }) | |
.endAngle(function(d) { return d + tau / arcs * 1.1; })); | |
hueCtrl.append('g').attr('class', 'hue-ticks'); | |
hueCtrl.append('text').attr('text-anchor', 'middle'); | |
hueCtrl.selectAll('g, text').attr('transform', | |
'translate(' + hueCenter + ',' + hueCenter + ')'); | |
var lGradient = d3.select('svg.gradient.lightness'), | |
sGradient = d3.select('svg.gradient.saturation'), | |
gradientDx = 360 / gradientRects; | |
d3.selectAll('svg.gradient').selectAll('rect') | |
.data(d3.range(0, gradientWidth, gradientDx)) | |
.enter().append('rect') | |
.attr('x', function(d) { return d; }) | |
.attr('height', 20) | |
.attr('width', gradientDx + 0.33); | |
// | |
// drag to change initial hue | |
var _theta0, _dHue0; | |
hueDrag | |
.on('dragstart', function() { | |
var x = d3.mouse(this)[0] - hueCenter, | |
y = d3.mouse(this)[1] - hueCenter; | |
_theta0 = Math.atan2(-y, x) * radToDeg; | |
_dHue0 = _input.dHue; | |
d3.event.sourceEvent.stopPropagation(); | |
}) | |
.on('drag', function() { | |
var x = d3.mouse(this)[0] - hueCenter, | |
y = d3.mouse(this)[1] - hueCenter, | |
theta = Math.atan2(-y, x) * radToDeg, | |
dTheta = _theta0 - theta; | |
_input.dHue = _dHue0 + dTheta; | |
update(); | |
}); | |
hueCtrl.call(hueDrag); | |
// bind <input> controls | |
d3.selectAll('input[dimension]') | |
.on('input', function() { | |
_input[this.getAttribute('dimension')] = +this.value; | |
update(); | |
}); | |
// | |
// cache selectors, make updates | |
var hueArcSel = hueCtrl.selectAll('.hue-arcs path'), | |
hueTicks = hueCtrl.select('.hue-ticks'), | |
sValues = d3.selectAll('.s-value'), | |
lValues = d3.selectAll('.l-value'), | |
lTicks = lGradient.selectAll('rect'), | |
sTicks = sGradient.selectAll('rect'), | |
hueValue = hueCtrl.select('text'), | |
colorList = d3.selectAll('.color-list'); | |
// render sparklines | |
var sparkSvg = d3.select('.sparklines'), | |
sparkWidth = 540, | |
sparkHeight = 140, | |
nPoints = 20, | |
sparkdX = sparkWidth / nPoints; | |
sparkPath = function() { | |
return 'M0,'+(sparkHeight / 2)+' '+ | |
d3.range(0, nPoints) | |
.map(function() { | |
return 'l'+sparkdX +','+((0.5 - Math.random())* sparkHeight / 8); }) | |
.join(' '); | |
}; | |
function update() { | |
hueArcSel.style('fill', function(d) { return d3.hsl((d * 360) / tau, | |
_input.saturation, | |
_input.lightness); }); | |
lTicks.style('fill', function(d, ndx) { return d3.hsl(_input.dHue, | |
_input.saturation, | |
(ndx * .01)); }); | |
sTicks.style('fill', function(d, ndx) { return d3.hsl(_input.dHue, | |
(ndx * .01), | |
_input.lightness); }); | |
sValues.text(Math.round(_input.saturation * 100)); | |
lValues.text(Math.round(_input.lightness * 100)); | |
hueValue.text(Math.round(_input.dHue) + '° base hue, ' + | |
Math.round(360/_input.nColors) + '° between swatches'); | |
_palette = palette(_input.nColors, | |
_input.dHue, | |
_input.saturation, | |
_input.lightness); | |
colorList.text('var swatches = [' + | |
_palette.map(function(d) { return d.rgb; }).join(', ') + | |
'];'); | |
var tickSel = hueTicks.selectAll('circle').data(_palette); | |
tickSel.exit().remove(); | |
tickSel.enter().append('circle') | |
.attr('r', 9.5) | |
.attr('cx', 0) | |
.attr('cy', (r0 + r1) / -2); | |
tickSel | |
.style('fill', function(d) { return d.rgb; }) | |
.attr('transform', function(d) { | |
return 'rotate('+(d.h)+' 0 0)' | |
}); | |
var sparkSel = sparkSvg.selectAll('path').data(_palette); | |
sparkSel.exit().remove(); | |
sparkSel.enter().append('path').attr('d', sparkPath); | |
sparkSel.style('stroke', function(d) { return d.rgb; }); | |
d3.select('.state-map').selectAll('path.state') | |
.style('fill', function(d, ndx) { | |
// return _palette[Math.floor(Math.random() * _palette.length)].rgb; | |
// ^^ warning, can induce seizures | |
return _palette[ndx % _palette.length].rgb; | |
}); | |
} | |
update(); | |
// | |
// states | |
d3.json('us_states.json', function(err, statejson) { | |
var mapWidth = 580, | |
mapHeight = 300, | |
projection = d3.geo.albersUsa() | |
.scale(mapWidth) | |
.translate([mapWidth / 2, mapHeight / 2]); | |
var path = d3.geo.path().projection(projection); | |
d3.select('.state-map').selectAll('path.state') | |
.data(statejson.features).enter() | |
.append('path') | |
.attr('class', 'state') | |
.attr('d', function(d) { return path(d); }); | |
update(); | |
}); | |
</script> | |
</body> |
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
���� JFIF � � �� �Exif MM * J R( �i Z � � � �� � �� !http://ns.adobe.com/xap/1.0/ <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about=""/> </rdf:RDF> </x:xmpmeta> <?xpacket end="w"?> |