Trước có đọc cuốn Interactive Data Visualization for the Web, rồi dùng thử D3. Nhưng từ đó giờ chỉ xài cái Sankey của D3 còn cũng không dùng nhiều. Cuốn sách này giờ có thể đọc online. Ví dụ cũng trực quan dễ hiểu.
Ví dụ phần mapping thì coi chapter 12, github nó ở đây https://github.com/alignedleft/d3-book/tree/master/chapter_12.
Đầu tiên là 1 số examples cần xem:
Let’s Make a Map
Chapter 12. Geomapping, Interactive Data Visualization for the Web
Thường trong sách dùng dữ liệu của Mỹ, mình sẽ dùng data VN.
Data
Đầu tiên lấy 1 data về dân số mật độ dân số tại https://www.gso.gov.vn
Mình có edit lại thêm field ISO 3166 alpha 2 (ISO 3166-2 state codes for identifying the principal subdivisions e.g., provinces or states). Có thể dùng HASC (Hierarchical administrative subdivision codes) cũng được ko có vấn đề gì: vn-population-2011.csv.
Dữ liệu như sau:
Dữ liệu như sau:
Sau đó là dự liệu 1 số thành phố (city, township) bao gồm lat, lng, level: vn-cities.csv. Ví dụ vài dòng:
Cuối cùng là dữ liệu map. Đầu tiên download shapefile chẳng hạn tại GADM.
Sau khi download shapefile từ GADM convert nó sang GeoJSON và TopoJSON. Mình dùng TopoJSON nên cũng ko cần convert sang GeoJSON làm gì. Mở page http://mapshaper.org/, kéo 2 file VNM_adm1.dbf với VNM_adm1.shp vào page này. OK nó sẽ render ra cái bản đồ VN. Xong mình sẽ simplify cái data này, chọn Simplify > Lấy options giả sử Visvalingam / weighted area, kéo xuống khoảng 15-20% là ổn. Để ý nếu có line intersection thì repair.
http://www.gadm.org/
GADM is a spatial database of the location of the world's administrative areas (or adminstrative boundaries) for use in GIS and similar software.
http://mapshaper.org/
A tool for topologically aware shape simplification. Reads and writes Shapefile, GeoJSON and TopoJSON formats.
Xuất ra file TopoJSON, khi đó khoảng chừng 200KB. Mình có thể mở bằng Notepad++ chẳng hạn, dùng JSON Viewer plugin format lại cho dễ nhìn. Find "objects" để đổi lại tên đang là tên của file mình kéo vào: "VNM_adm1" thành "states" chẳng hạn. Edit lại 1 số thông tin properties ví dụ:
{"name":"Trà Vinh","iso":"VN-51","hasc":"VN.TV","types":"Tỉnh","alt_types":"Province","capital":"Trà Vinh","region":8}
Dùng RegEx replace cũng khá nhanh. Trong data VNM_adm1 lưu ý có 2 entry cho tỉnh Ninh Bình và Kiên Giang (tách riêng đảo), nên chuyển qua TopoJSON mình gộp lại thành MultiPolygon luôn, còn 1 entry cho mỗi tỉnh thôi. File đã chuẩn bị xong: vn-states.json.
Mình phát hiện ra 1 điều là Github nhận ra file GeoJSON và TopoJSON nên thực hiện viewer được luôn. Công nhận sp người ta làm chiều sâu dễ sợ.
Display data
Đầu tiên để code có thể dùng JSFiddle không thì nên tạo 1 local server để dev (xài node) vì không cho load file:// không cho request resource do CORS. OK tạo 1 folder, tạo file server.js để có 1 cái localhost xài (nếu không xài JSFiddle). Mình hay xài localhost server: server.jsvar http = require("http"), url = require("url"), path = require("path"), fs = require("fs") port = process.argv[2] || 8888; var contentTypes = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.css': 'text/css; charset=utf-8', '.png': 'image/png', '.gif': 'image/gif', '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', '.xml': 'application/xml; charset=utf-8', '.kml': 'application/vnd.google-earth.kml+xml; charset=utf-8', '.kmz': 'application/vnd.google-earth.kmz', }; http.createServer(function(request, response) { var uri = url.parse(request.url).pathname , filename = path.join(process.cwd(), uri); fs.exists(filename, function(exists) { if(!exists) { response.writeHead(404, {"Content-Type": "text/plain"}); response.write("404 Not Found\n"); response.end(); return; } if (fs.statSync(filename).isDirectory()) { filename += '/index.html'; } fs.readFile(filename, "binary", function(err, file) { if(err) { response.writeHead(500, {"Content-Type": "text/plain"}); response.write(err + "\n"); response.end(); return; } response.writeHead(200, {"Content-Type": contentTypes[path.extname(filename)]}); response.write(file, "binary"); response.end(); }); }); }).listen(parseInt(port, 10)); console.log("Static file server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown");
Nếu xài JSFiddle thì file data để trên Github xong load raw là ổn.
OK chuẩn bị xong.
Các ví dụ ban đầu thì xài GeoJSON, còn load TopoJSON thì cũng như vậy. Sau khi load json file thì chuyển ngược lại thành GeoJSON để display
// While our data can be stored more efficiently in TopoJSON, // we must convert back to GeoJSON for display. var features = topojson.feature(json, json.objects.states).features;
Quantile scale
Để color cho tỉnh theo density thì mình dùng scale quantile (colorful hơn là quantize). Tức là chia 63 thành 9 nhóm. Sau đó push hết density vào domain. Nếu xài quantize() thì map domain là min và max của các density. Không thì xài linear cho đơn giản.Demostration cho scale Quantile, Quantize, Threshold Scales
Xem thêm bài d3: scales, and color. của Jerome Cukier.
// We create a quantile scale to categorize the values in 9 groups. // The domain is static and has a minimum/maximum of population/density. // The scale returns text values which can be used for the color CSS // classes (q0-9, q1-9 ... q8-9) var quantiles = d3.scale.quantile() .range(d3.range(9).map(function(i) { return 'q' + i + '-9'; })); ... // Set the domain of the values quantiles.domain(features.map(function(d) { return d.properties.density; })); g. ... .attr('class', function(d) { // Use the quantiled value for the class return 'feature ' + quantiles(d.properties.density); }) // add attribute class and fill with result from quantiles
Projection
Projection function chuyển từ tọa độ geo lat, lng sang Cartesian. Mặc định projection của D3 là Albers USA (albersUsa). Projection này thực hiện chuyển các vùng như Alaska và Hawaii về hiển thị phía góc dưới bên phải của bản đồ nước Mỹ, đồng thời center map vào bản đồ nước Mỹ. Scale mặc định là 1000, nếu muốn tăng hay giảm scale thì thực hiện điều chỉnh. Mình chọn center là Sài Gòn.var width = 960, height = 600; var center = [106.34899620666437, 16.553160650957434]; var scale = 4000; var offset = [width / 2, height / 2 - 300]; // The projection function takes a location [longitude, latitude] // and returns a Cartesian coordinates [x,y] (in pixels). // // D3 has several built-in projections. Albers USA is a composite projection // that nicely tucks Alaska and Hawaii beneath the Southwest. // // Albers USA (albersUsa) is actually the default projection for d3.path.geo() // The default scale value is 1,000. Anything smaller will shrink the map; // anything larger will expand it. // // Add a scale() method with 800 to our projection in order to shrink things down a bit // var projection = d3.geo.albersUsa() // .translate([w/2, h/2]).scale([800]); projection = d3.geo.mercator() .translate(offset) .scale([scale]) .center(center); // We define our first path generator for translating that // mess of GeoJSON coordinates into even messier messes of SVG path codes. // Tell the path generator explicitly that it should reference our customized // projection when generating all those paths path = d3.geo.path() .projection(projection);
Boundary và mess
Thực hiện merge tất cả các geometries của states loại bỏ interior borders thành country boundary. Sau đó dùng topojson.mesh để có mesh giữa các tỉnh. Format bằng CSS sẽ có kết quả OK.var boundary = g.append('g') .attr('class', 'boundary'); // Country boundary from merge all geometries boundary.append('path') .attr('class', 'country-boundary') .datum(topojson.merge(json, json.objects.states.geometries)) .attr('d', path); ... // State mesh boundary.append('path') .attr('class', 'state-boundary') .datum(topojson.mesh(json, json.objects.states, function(a, b) { return a !== b; })).attr('d', path);
CSS lưu ý fill là none. Trong bước chuẩn bị dữ liệu nếu line intersections quá nhiều thì mess sẽ rất tệ.
.country-boundary { fill: none; stroke: #37C3BC; stroke-linejoin: round; } .state-boundary { fill: none; stroke: #003568; stroke-dasharray: 5, 3; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
Data bound
Về data binding của D3, nếu parent sử dụng function data() với array sau đó enter().append() thì data của các node child là item trong array.Nếu child node có append các node cháu thì data bound của các node cháu lúc đó cũng là item của child node. Khi đó sử dụng .text(function(d) {}) thì d là item đó.
Nếu muốn set data bound cho 1 node thì dùng function datum(). Nếu dùng datum() thì có thể forEach array và dùng datum() thay vì data binding với data().
Zooming, text ...
Phần cuối cùng zooming thì follow theo ví dụ của Mike Bostock. Text thì nên append cuối cùng vào SVG để ko bị che, đè. Điều chỉ zooming text về font-size, stroke-width + CSS.Github: https://github.com/kierandg/d3tuts
JSFiddle: https://jsfiddle.net/kierandg/5gxacnj2/
No comments:
Post a Comment