|
<!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> |