Skip to content

Instantly share code, notes, and snippets.

@gmculp
Last active October 1, 2016 23:45
Show Gist options
  • Select an option

  • Save gmculp/8271673 to your computer and use it in GitHub Desktop.

Select an option

Save gmculp/8271673 to your computer and use it in GitHub Desktop.
multiple force layout of map color palettes in terms of color difference
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.stat_labs {
font: 12px sans-serif;
font-weight: bold;
}
line {
stroke: lightgray;
stroke-width: 1.5px;
}
circle {
stroke: #404040;
stroke-width: 1.5px;
}
.floats {
float: left;
}
.spacer {
width:20px;
height:10px;
}
.containers {
border: 2px solid gray;
margin-left: 10px;
}
.subs {
border: 1px solid gray;
margin: 0;
}
#map_label {
font: 24px sans-serif;
font-weight: bold;
border: 0px solid gray;
fill: gray;
}
.caption {
padding-left: 10px;
background: gray;
font: 14px sans-serif;
font-weight: bold;
color: white;
}
.hide_me {
display:none;
}
</style>
<body>
<div id="normal" class="floats containers">
<div id="caption1" class="caption"></div>
<div>
<div id="div1" class="floats subs"></div>
<div id="div2" class="floats subs"></div>
</div>
<div style="clear: left;">
<div id="div3" class="floats subs"></div>
<div id="div4" class="floats subs"></div>
</div>
</div>
<div id="recolored" class="floats containers">
<div id="caption2" class="caption"></div>
<div>
<div id="div5" class="floats subs"></div>
<div id="div6" class="floats subs"></div>
</div>
<div style="clear: left;">
<div id="div7" class="floats subs"></div>
<div id="div8" class="floats subs"></div>
</div>
</div>
<div id="map_label" class="floats containers"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
var margin = {top: 10, right: 10, bottom: 10, left: 10};
var width = ((($( window ).width())*0.9)/4) - margin.left - margin.right,
height = ((($( window ).height())*0.9)/2) - margin.top - margin.bottom;
//color palette arrays
var all_palettes = [
//sequential monochrome
{map_type:"Sequential Monochrome", map_palette:
[[["#bfd5ff"], ["#7fa9ff"], ["#3c7cff"], ["#004dea"], ["#003ab2"]]]},
//diverging
{map_type:"Diverging", map_palette:
[[["#ff7f00"], ["#ffbf00"], ["#ffff00"], ["#00ff00"], ["#00ffd4"], ["#00bfff"]],
[["#f38437"], ["#faaf7a"], ["#fedac0"], ["#d4d4d4"], ["#b0dfff"], ["#58b5f6"]]]},
//qual dot
{map_type:"Qualitative Dot", map_palette:
[[["#f46500"], ["#00a81b"], ["#2592ff"]],[["#e17123"], ["#8e8e8e"], ["#2d97e0"]]]},
//bivariate
{map_type:"Two-Variable", map_palette:
[[["#80d5ff", "#2198d3", "#125c81"], ["#64da78", "#00a81b", "#006610"], ["#e1cc67", "#aa8e00", "#685600"]],[["#8ccffd", "#4594cb", "#2a5a7b"], ["#bbc1c6", "#8e8e8e", "#585858"], ["#fdba8b", "#bc825a", "#735037"]]]},
//sequential polychrome
{map_type:"Sequential Polychrome", map_palette:
[[["#ff78c7"], ["#ff30cb"], ["#d800c6"], ["#a500b5"], ["#8900b7"]],[["#c2a18a"], ["#a68873"], ["#80756e"], ["#576067"], ["#3b5669"]]]},
//balance
{map_type:"Balance", map_palette:
[[["#ff0000"], ["#ff00aa"], ["#ff00ff"], ["#aa00ff"], ["#0000ff"]],
[["#c25204"], ["#ab7957"], ["#8f8f8f"], ["#3f7093"], ["#004f85"]]]},
//qual fill
{map_type:"Qualitative Area", map_palette:
[[["#ff0000"], ["#009300"], ["#5571ff"], ["#00e9ff"], ["#ffcd37"], ["#00f700"]],[["#c25204"], ["#7c7c7c"], ["#2386cb"], ["#86cbfc"], ["#fdbc8e"], ["#cecece"]]]}
];
Math.degrees = function(rad)
{
return rad*(180/Math.PI);
}
Math.radians = function(deg)
{
return deg * (Math.PI/180);
}
//adapted from Sharma et al., 2005
function CIEDE2000(hex1,hex2){
//convert from hex to CIE1976
var lab1 = d3.lab(hex1);
var lab2 = d3.lab(hex2);
//step1: calculate C
var C1 = Math.sqrt(Math.pow(lab1.a, 2) + Math.pow(lab1.b, 2));
var C2 = Math.sqrt(Math.pow(lab2.a, 2) + Math.pow(lab2.b, 2));
//step2: calculate a', C', and h'
var mC = (C1+C2)/2;
var G = 0.5 * (1-Math.sqrt(Math.pow(mC, 7)/(Math.pow(mC, 7)+Math.pow(25, 7))));
var L1p = lab1.l;
var a1p = (1+G)*lab1.a;
var b1p = lab1.b;
var L2p = lab2.l;
var a2p = (1+G)*lab2.a;
var b2p = lab2.b;
var C1p = Math.sqrt(Math.pow(a1p, 2)+Math.pow(b1p, 2));
var C2p = Math.sqrt(Math.pow(a2p, 2)+Math.pow(b2p, 2));
var h1;
if ((a1p==0)&&(b1p==0)){
h1 = 0;
}
else if (b1p>0) {
h1 = Math.degrees(Math.atan2(b1p,a1p));
}
else {
h1 = Math.degrees(Math.atan2(b1p,a1p))+360
}
var h2;
if ((a2p==0)&&(b2p==0)){
h2 = 0;
}
else if (b2p>0) {
h2 = Math.degrees(Math.atan2(b2p,a2p));
}
else {
h2 = Math.degrees(Math.atan2(b2p,a2p))+360
}
var dh_cond;
if ((h2-h1)>180){
dh_cond=1;
}
else if ((h2-h1)<-180) {
dh_cond=2;
}
else {
dh_cond=0;
}
//step 3: calculate dL', dC', dH', dh'
var dhp;
if (dh_cond==0){
dhp = h2-h1;
}
else if (dh_cond==1) {
dhp = h2-h1-360;
}
else {
dhp = h2+360-h1;
}
var dLp = L2p-L1p;
var dCp = C2p - C1p;
var dhhp = 2*(Math.sqrt(C1p*C2p))*(Math.sin(Math.radians(dhp/2)));
//step4: Calculate dE2000
var Lp_ave = (L1p+L2p)/2;
var Cp_ave = (C1p+C2p)/2;
var h_ave_cond;
if ((C1p*C2p)==0){
h_ave_cond=3;
}
else if ((Math.abs(h2-h1))<=180){
h_ave_cond=0;
}
else if ((h2+h1)<360){
h_ave_cond=1;
}
else {
h_ave_cond=2;
}
var Hp_ave;
if (h_ave_cond==3){
Hp_ave=h2+h1;
}
else if (h_ave_cond==0){
Hp_ave=(h2+h1)/2;
}
else if (h_ave_cond==1){
Hp_ave=((h2+h1)/2)+180;
}
else {
Hp_ave=((h2+h1)/2)-180;
}
var Lp50= Math.pow((Lp_ave-50),2);
var S_L = 1+((0.015*Lp50)/(Math.sqrt(20+Lp50)));
var S_C = 1+(0.045*Cp_ave);
var T = 1 - (0.17*Math.cos(Math.radians(Hp_ave-30))) + (0.24*Math.cos(Math.radians(Hp_ave*2))) + (0.32*Math.cos(Math.radians(3*Hp_ave+6))) - (0.2*Math.cos(Math.radians(4*Hp_ave-63)));
var S_H = 1 + 0.015*T*Cp_ave;
var dTheta = 30*Math.exp(-1*Math.pow(((Hp_ave-275)/25),2));
var R_C =2*(Math.sqrt((Math.pow(Cp_ave,7))/((Math.pow(Cp_ave,7))+Math.pow(25,7))));
var R_T = -Math.sin(Math.radians(2*dTheta))*R_C;
var dlpk = dLp/S_L/1;
var dcpk = dCp/S_C/1;
var dhpk = dhhp/S_H/1;
var de2000 = Math.sqrt(Math.pow(dlpk,2)+Math.pow(dcpk,2)+Math.pow(dhpk,2)+R_T*dcpk*dhpk);
return de2000;
}
//from http://tech.karbassi.com/2009/12/17/pure-javascript-flatten-array
function flatten(array){
var flat = [];
for (var i = 0, l = array.length; i < l; i++){
var type = Object.prototype.toString.call(array[i]).split(' ').pop().split(']').shift().toLowerCase();
if (type) { flat = flat.concat(/^(array|collection|arguments|object)$/.test(type) ? flatten(array[i]) : array[i]); }
}
return flat;
}
//adapted from Vie´not, Brettel and Mollon, 1999
function simulate_CVD (the_color, cvd_type){
var CVD_obj = {tritan: [[1, 0.1446, -0.1446],[0, 0.8592, 0.1408],[0, 0.8592, 0.1408]],
deutan:[[0.2928, 0.7072, 0],[0.2928, 0.7072, 0],[-0.0223, 0.0223, 1]],
protan:[[0.1124, 0.8876, 0],[0.1124, 0.8876, 0],[0.004,-0.004,1]]};
var CVD_arr = [];
if (CVD_obj.hasOwnProperty(cvd_type)){
CVD_arr = CVD_obj[cvd_type];
}
else {
CVD_arr = [[1,0,0],[0,1,0],[0,0,1]];
}
//convert color string to RGB
var rgb_col =d3.rgb(the_color);
var exp1 = 2.2;
var exp2 = 1/exp1;
var R_in = Math.pow((rgb_col.r)/ 255, exp1);
var G_in = Math.pow((rgb_col.g)/ 255, exp1);
var B_in = Math.pow((rgb_col.b)/ 255, exp1);
var R_out = 255 * (Math.pow((Math.max(((CVD_arr[0][0] * R_in) + (CVD_arr[0][1] * G_in) + (CVD_arr[0][2] * B_in)), 0)), exp2));
var G_out = 255 * (Math.pow((Math.max(((CVD_arr[1][0] * R_in) + (CVD_arr[1][1] * G_in) + (CVD_arr[1][2] * B_in)), 0)), exp2));
var B_out = 255 * (Math.pow((Math.max(((CVD_arr[2][0] * R_in) + (CVD_arr[2][1] * G_in) + (CVD_arr[2][2] * B_in)), 0)), exp2));
var R_CVD = Math.round(Math.min((Math.max(R_out, 0)), 255));
var G_CVD = Math.round(Math.min((Math.max(G_out, 0)), 255));
var B_CVD = Math.round(Math.min((Math.max(B_out, 0)), 255));
return d3.rgb(R_CVD,G_CVD,B_CVD);
}
//CVD simulation of palette array
function CVD_palette(p_arr, cvc_type){
var t_array = [];
for (var j = 0; j < p_arr.length; j++) {
//simulate_CVD (the_color, cvd_type)
var col_match =simulate_CVD(p_arr[j], cvc_type);
var hex_string = d3.rgb(col_match).toString();
t_array.push(hex_string);
}
return t_array;
}
function process_palette (in_pal){
var links_arr = [];
for (var j = 0; j < in_pal.length; j++) {
var color1 = in_pal[j];
for (var k = 0; k < in_pal.length; k++) {
var color2 = in_pal[k];
if (color1!==color2){
var dE00 = CIEDE2000(color1,color2);
links_arr.push({source:color1, target:color2, CIE_2000:dE00});
}
}
}
return links_arr;
}
var nn=1; //select palette here
var map_title = all_palettes[nn].map_type;
var CVD_types = ["normal","deutan","protan","tritan"];
var all_versions = [];
$( "#caption1" ).html( "Original" );
if (all_palettes[nn].map_palette.length>1){
$( "#caption2" ).html( "Re-Colored" );
}
else {
//if no re-colored palette, hide div
$( "#recolored" ).addClass( "hide_me" );
}
for (var p = 0; p < all_palettes[nn].map_palette.length; p++) {
var this_pal = all_palettes[nn].map_palette;
var in_norm_pal = flatten(this_pal[p]);
for (var q = 0; q < CVD_types.length; q++) {
var in_cvd_pal = CVD_palette(in_norm_pal, CVD_types[q]);
all_versions.push({version: CVD_types[q], palette: in_cvd_pal});
}
}
for (var j = 0; j < all_versions.length; j++) {
draw_graph(all_versions[j],j)
}
var svg_title = d3.select("#map_label").append("svg")
.attr("width", 30)
.attr("height", ((height + margin.top + margin.bottom)*2));
svg_title.append("text")
.attr("x", -((height + margin.top + margin.bottom)*2))
.attr("y", 18)
.style("text-anchor", "start")
.attr('transform', 'rotate(-90)')
.text(map_title);
function draw_graph (input_arr, num){
var svg = d3.select("#div"+(num+1)).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var nodesByName = {};
var r = 12;
var links = process_palette(input_arr.palette);
// Create nodes for each unique source and target.
links.forEach(function(link) {
link.source = nodesByName[link.source] || (nodesByName[link.source] = {name: link.source});
link.target = nodesByName[link.target] || (nodesByName[link.target] = {name: link.target});
link.distance = link.CIE_2000 * 2.5;
});
var nodes = d3.values(nodesByName);
var link;
var node;
var CIEDE200_arr = [];
for (var k = 0; k < links.length; k++) {
CIEDE200_arr.push(links[k].CIE_2000);
}
var title_g = svg.append("g")
.attr("id", "title_g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sub_title1 =title_g.append("text")
.attr("dy","0.35em")
.style("text-anchor", "start")
.attr("class", "stat_labs")
.text("Color Vision Type: " + (input_arr.version).toUpperCase());
var bbox1 = sub_title1.node().getBBox();
var sub_title2 = title_g.append("text")
.attr("y", (bbox1.y + bbox1.height + margin.top))
.attr("dy","0.35em")
.style("text-anchor", "start")
.attr("class", "stat_labs")
.text("Min CIEDE2000: " + d3.min(CIEDE200_arr).toFixed(2));
var bbox = title_g.node().getBBox();
var force = d3.layout.force()
.size([width, (height - (bbox.y + bbox.height))])
.linkDistance(function(d) { return d.distance; });
var this_g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top + bbox.y + bbox.height) + ")");
//test bound rect
//svg.append("rect")
// .attr("x", margin.left)
// .attr("y", (margin.top + bbox.y + bbox.height))
// .attr("width", width)
// .attr("height", (height - (bbox.y + bbox.height)))
// .style("stroke", "red")
// .style("fill","none")
// .style("stroke-width", "2px")
// .style("stroke-dasharray", ("3, 3")) ;
//start force
force
.nodes(nodes)
.links(links)
.gravity(.1)
.start();
link = this_g.selectAll(".link")
.data(links)
.enter().append("line")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// draw node circles and labels.
node = this_g.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node");
node.append("circle")
.attr("r", r)
.style("fill",function(d) { return d.name; });
node.append("text")
.text(function(d,z) {
return (z+1);
})
.style("fill", function(d) {
var lightness = d3.lab(d.name).l;
return (lightness>65)?"black":"white";
})
.style("text-anchor", "middle")
.style("font-weight", 600)
.style("font-size", "16px")
.style("font-family", "sans-serif")
.attr("dy", ".35em");
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment