D3.jsを使って、色相方向に地図を色分けする 2015年1月25日 こんばんは。今日は前回記事の続きです。色相方向に補間して求めた色を使って、実際に地図を色分けしてみました。 題材として、過去の記事「横浜市地図に数値Dataラベルを貼る」で描いた横浜市・区人口ラベル付き地図を用いました。 ここでの色の範囲は最大値を赤(#ee0000)、最小値を緑(#00ee00)に設定して進めます。 まずは、下記のような普通の方法(?)で色分けしてみます。 areaGrad = d3.scale.linear().domain([target_min, target_max]).range([hexcode_min, hexcode_max]) これを使うと、下のような地図が表示されました。※画像をclickすると地図のページに飛びます。 かなり残念な配色ですよね。 一方で、HSV色空間で補間した結果はこんな感じです。※画像をclickすると地図のページに飛びます。 ソースコードは、次の通りです。 'use strict' ### * 色を変換する関数を定義する ### hex2rgb = (hexcode) -> # rgbに変換する result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexcode); if result is null console.log "error!" return [r,g,b] = [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] return [r,g,b] rgb2hsv = (r,g,b) -> # rgb to hsv r = r / 255 g = g / 255 b = b / 255 max_v = Math.max(Math.max(r,g), b) min_v = Math.min(Math.min(r,g), b) v = max_v s = max_v - min_v if min_v == max_v h = 0 else if min_v == b h = 60 * (g-r)/s + 60 else if min_v == r h = 60 * (b-g)/s + 180 else h = 60 * (r-b)/s + 300 return [h,s,v] hsv2rgb = (h,s,v) -> console.log [h,s,v] if h is null r = 0 g = 0 b = 0 else h_dash = h / 60 x = s * (1 - Math.abs(h_dash % 2 - 1)) if h_dash < 1 [r,g,b] = [s,x,0] else if h_dash < 2 [r,g,b] = [x,s,0] else if h_dash < 3 [r,g,b] = [0,s,x] else if h_dash < 4 [r,g,b] = [0,x,s] else if h_dash < 5 [r,g,b] = [x,0,s] else if h_dash < 6 [r,g,b] = [s,0,x] else console.log '変換エラー' return r += (v-s) g += (v-s) b += (v-s) r *= 255 g *= 255 b *= 255 return [Math.round(r),Math.round(g),Math.round(b)] readTopoFileDone = false readAttrFileDone = false # fileから読み込んだdataの格納変数 topo_data = null attr_data = null map_width = 500 map_height = 650 svg = null ### * JSONファイルを読み込み、地図描画関数mainを呼び出す ### readTopoFile = (json) -> topo_data = json readTopoFileDone = true readAttrFile = (json) -> attr_data = json readAttrFileDone = true d3.json("yokohama_topo_out.json", (error, json) -> if error return console.warn error readTopoFile(json) if readTopoFileDone and readAttrFileDone main(topo_data, attr_data)) d3.json("yokohama_codes_and_stat.json", (error, json) -> if error return console.warn(error) readAttrFile(json) if readTopoFileDone and readAttrFileDone main(topo_data, attr_data)) ### * 地図を描画する ### main = (topo, attr) -> labelLineHeight = 16 # 属性情報を入れ直すためのHash Table attrHash = [] # attr情報を、IDをkeyとするhash-tableに変換する # idをKeyとしたhashテーブル&property毎のhashテーブルを作成する for e in attr attrHash[e.cityid]=e # 人口の最大値と最小値を求めておく # --全年齢人口データのみを入れた配列を作成する elements = [] target_key = "ttl" for e in attr elements.push(e[target_key]) # -- maxとminをとる target_max = Math.max.apply(null, elements) target_min = Math.min.apply(null, elements) # svgを追加 svg = d3.select("body #map").append("svg").attr("width", map_width).attr("height", map_height) # 横浜市のmapを描画する yokohama = topojson.object(topo, topo.objects.yokohama) # 横浜市を中心に指定したメルカトル図法で10万分の1で描画する projection = d3.geo.mercator().center([139.59,35.45]).scale(100000).translate([map_width / 2, map_height / 2]) # pathを作成する path = d3.geo.path().projection(projection) svg.append("path").datum(yokohama).attr("d", path) # classを指定する svg.selectAll(".yokohama").data(yokohama.geometries).enter().append("path").attr("class", (d) -> return "yokohama " + d.properties.name).attr("d", path) # 色を塗る hexcode_max = "#ee0000" hexcode_min = "#00ee00" # 念のためあらかじめ塗っておく svg.selectAll("path").attr("fill", "#bee59e") # HEXカラーコードからRGBに変換 [r_min,g_min,b_min] = hex2rgb(hexcode_min) [r_max,g_max,b_max] = hex2rgb(hexcode_max) # RGB(0-255)からHSVに変換 [h_min,s_min,v_min] = rgb2hsv(r_min,g_min,b_min) [h_max,s_max,v_max] = rgb2hsv(r_max,g_max,b_max) areaGrad = d3.scale.linear().domain([target_min, target_max]).range([[h_min,s_min,v_min], [h_max,s_max,v_max]]) for k, v of attrHash [h,s,v] = areaGrad(attrHash[k][target_key]) [r,g,b] = hsv2rgb(h,s,v) svg.selectAll("."+k).attr("fill", "rgba(#{r},#{g},#{b},1)") # 内部境界に線を引く svg.append("path") .datum(topojson.mesh(topo, topo.objects.yokohama, (a, b) -> return a isnt b)) .attr("d", path) .attr("class", "subunit-boundary") # 区コードのラベル貼り city_labels = new Array() svg.selectAll(".cityname-label") .data(yokohama.geometries) .enter() .append("text") .attr("class", (d) -> if not (city_labels.indexOf(d.properties.name) > -1) city_labels.push(d.properties.name) return "cityname-label "+d.properties.name) .attr("transform", (d) -> # 文字をpath中央から、文字高さ1/2分高い位置に貼る pos = path.centroid(d) pos[1] -= labelLineHeight / 2 return "translate(" + pos + ")") .attr("dy", ".35em") .text((d) -> return attrHash[d.properties.name].name) # 値ラベル貼り svg.selectAll(".stat-label") .data(yokohama.geometries) .enter() .append("text") .attr("class", (d) -> if not (city_labels.indexOf(d.properties.name) > -1) city_labels.push(d.properties.name) return "stat-label "+d.properties.name) .attr("transform", (d) -> # 文字をpath中央から、文字高さ1/2分高い位置に貼る pos = path.centroid(d) pos[1] += labelLineHeight / 2 return "translate(" + pos + ")") .attr("dy", ".35em") .text((d) -> return attrHash[d.properties.name][target_key]) # 各最初のエリアのみラベルを表示する for id in city_labels svg.select(".cityname-label."+id).style("display", "block") svg.select(".stat-label."+id).style("display", "block") return true このように、HSV色空間の方が人の直感に合う結果が得られると思いますので、オススメです。 それでは♡