slippy-colors
Version:
A D3 color picker component.
214 lines (182 loc) • 6.31 kB
JavaScript
var interpolate = require('fischer-color').interpolate;
var d3 = require('d3');
function ColorPicker() {
var w = 628,
h = 100,
canvW = 600,
canvH = 400;
var events = d3.dispatch('mousedown', 'mousemove', 'mouseup');
var ext = {min: 0, max: w, xs: [0]};
var pos = [0,0],
zm = 1;
var canv = document.createElement('canvas');
canv.width = w;
canv.height = h;
var colors = canv.getContext('2d');
var image = colors.getImageData(0, 0, w, h);
var imageMap = {};
for (var i = 0, j = -1, c; i < w * h; ++i) {
c = interpolate((i % w)/h, 1, Math.floor(i/w)/h);
image.data[++j] = c[0];
image.data[++j] = c[1];
image.data[++j] = c[2];
image.data[++j] = 255;
var thisC = [];
c.forEach(function(i){ thisC.push(Math.round(i)); });
if (!imageMap[thisC]) imageMap[thisC] = [j];
else imageMap[thisC].push(j);
}
colors.putImageData(image, 0, 0);
function findColor(rgb) {
var l = imageMap[rgb];
if (!l) return null;
var loc = l.reduce(function(a,b){return a+b;}) / l.length;
var x, y;
var pxW = w*4;
y = Math.ceil(loc/pxW);
x = Math.floor((loc % pxW) / 4);
return [x,y];
// TODO this is just a bit off (rounding errors or something)
// TODO this breaks for non-represented RGBs -- figure out a way to do reverse fischer-color lookups.
}
var center;
function cp(selection) {
var canvasBox = selection.append('canvas')
.attr('width', w)
.attr('height', h)
.attr('style', 'width:' + canvW + 'px;height:' + canvH + 'px;');
var dzoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on('zoom', zoom);
var canvas = canvasBox
.call(dzoom)
.node().getContext("2d");
cp.draw(canvas);
function zoom(tr, sc) {
canvas.save();
canvas.clearRect(0, 0, w, h);
var t = tr || d3.event.translate;
if (-t[0] + w > ext.max) {
ext.xs.push(ext.max);
ext.max += w;
} else if (-t[0] < ext.min) {
ext.xs.push(ext.min - w);
ext.min -= 628;
}
var s = sc || d3.event.scale;
t[1] = Math.min(Math.max(t[1], -h*(s-1)), 0);
canvas.translate(t[0],t[1]);
canvas.scale(s,s);
cp.draw(canvas);
pos = t; zm = s;
canvas.restore();
}
function getPos() {
var rect = canvasBox[0][0].getBoundingClientRect();
var mp = [
((d3.event.x - rect.left) * (w / canvW) - pos[0]) / zm,
((d3.event.y - rect.top) * (h / canvH) - pos[1]) / zm
];
return mp;
}
function getColor(p) {
while (p[0] < 0) p[0] += w;
while (p[0] > 628) p[0] -= w;
var rgba = colors.getImageData(Math.round(p[0]), Math.round(p[1]), 1, 1).data;
if (rgba.length > 4) {
var r = [], g = [], b = [], a = [];
for (var i=0; i<rgba.length; i+=4) {
r.push(rgba[i]);
g.push(rgba[i+1]);
b.push(rgba[i+2]);
a.push(rgba[i+3]);
}
rgba = [];
[r,g,b,a].forEach(function(c){
rgba.push(Math.round(c.reduce(function(a,b){
return a+b;
}) / c.length));
});
}
return rgbToHex(rgba);
}
function goTo(loc, scale) {
loc = loc || getPos();
scale = scale || zm;
var tl = [ w/2 - loc[0]*scale, h/2 - loc[1]*scale ];
d3.transition().duration(600).tween('zoom', function() {
var ix = d3.interpolate(pos[0], tl[0]),
iy = d3.interpolate(pos[1], tl[1]),
is = d3.interpolate(zm, scale);
return function(t) {
zoom([ix(t), iy(t)], is(t));
};
});
dzoom.translate(tl);
dzoom.scale(scale);
}
if (center) {
var color;
if (typeof center.color === 'string') color = hextoRGB(center.color);
else if (Array.isArray(center.color)) color = center.color;
else return;
var loc = findColor(color);
if (loc) goTo(loc, center.z);
}
var clicking = false, dragging = false;
selection.on('mousedown', function() {
clicking = true;
});
selection.on('mousemove', function() {
if (clicking) dragging = true;
});
selection.on('mouseup', function(d) {
if (!dragging) {
events.mouseup(getColor(getPos()), goTo());
}
// TODO not on doubleclick
clicking = false; dragging = false;
});
}
cp.draw = function(c) {
ext.xs.forEach(function(atX) {
c.drawImage(canv, atX, 0);
});
};
cp.width = function(_) {
if (!arguments.length) return canvW;
canvW = _;
return cp;
};
cp.height = function(_) {
if (!arguments.length) return canvH;
canvH = _;
return cp;
};
cp.center = function(color, z) {
if (color && z) center = {color: color, z: z};
return cp;
}
function hextoRGB(hex) {
var shr = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shr, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var res = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return res ? [
parseInt(res[1], 16),
parseInt(res[2], 16),
parseInt(res[3], 16)
] : null;
}
function rgbToHex (rgb) {
var hexa = '#';
[rgb[0],rgb[1],rgb[2]].forEach(function(c) {
var h = c.toString(16);
hexa += h.length == 1 ? '0' + h : h;
});
return hexa;
}
return d3.rebind(cp, events, 'on', 'off');
}
module.exports = ColorPicker;