А как создать свой собственный Мир с материками и океанами можно узнать под катом.
Начало
Сперва нам нужно найти геоданные. Как и в прошлый раз мы будем использовать TopoJSON для этих целей. О том как его получить можно прочитать в предыдущей статье в разделе «Дела картографические». И так у нас есть TopoJSON файл world-110m.json
для карты с масштабом 1:110,000,000, или 1 см = 1,100 км (1″ = 1,736 миль) и файл world-110m-country-names.tsv
с названиями стран вида id
— название страны. Внешний файл с названиями используется для удобства, так как в этом случае можно легко перевести названия на любой язык. Всё, можно приступать непосредственно к созданию глобуса.
Рисуем интерактивный глобус
Нашей целью будет глобус, который можно:
- вращать мышкой «хватая» за сушу
- центрировать глобус на страну, выбранную из списка
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Nice title</title> <script src="http://d3js.org/d3.v3.min.js"></script> <script src="http://d3js.org/queue.v1.min.js"></script> <script src="http://d3js.org/topojson.v1.min.js"></script> </head> <style> Your awesome CSS </style> <body> <h1>Cool Header</h1> <script> Your awesome d3.js code </script> </body> </html>
Определим основные переменные и добавим DOM элементы.
var width = 600, height = 500, sens = 0.25, focused; //Setting projection var projection = d3.geo.orthographic() .scale(245) .rotate([0, 0]) .translate([width / 2, height / 2]) .clipAngle(90); var path = d3.geo.path() .projection(projection); //SVG container var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); //Adding water svg.append("path") .datum({type: "Sphere"}) .attr("class", "water") .attr("d", path); var countryTooltip = d3.select("body").append("div").attr("class", "countryTooltip"), countryList = d3.select("body").append("select").attr("name", "countries");
Переменная sens
отвечает за точность при вращении мышкой, а focused
используется как триггер для выбранной (центрированной) страны. Про используемую проекцию можно почитать на wikipedia: Orthographic projection. Метод .clipAngle() определяет какую часть сферы мы будем отображать (а точнее видеть), про это опять же можно почитать на wikipedia: small-circle clipping. Остальное вроде в разъяснениях не нуждается.
Далее мы загружаем наши файлы при помощи библиотеки queue.js, которая позволяет делать нам это асинхронно.
queue() .defer(d3.json, "data/world-110m.json") .defer(d3.tsv, "data/world-110m-country-names.tsv") .await(ready);
Теперь перейдём к главной функции, в нашем случае она называется ready
. Вначале, мы добавляем названия стран в наш dropdown list и отрисовываем страны на глобусе.
function ready(error, world, countryData) { var countryById = {}, countries = topojson.feature(world, world.objects.countries).features; //Adding countries to select countryData.forEach(function(d) { countryById[d.id] = d.name; option = countryList.append("option"); option.text(d.name); option.property("value", d.id); }); //Drawing countries on the globe var world = svg.selectAll("path.land") .data(countries) .enter().append("path") .attr("class", "land") .attr("d", path)
Перейдём к обработке событий мыши. Здесь пояснений требует drag.origin(), он позволяет нам задать «оригинальные» (действительные) стартовые координаты при захвате элемента, в нашем случае широту и долготу.
//Drag event .call(d3.behavior.drag() .origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; }) .on("drag", function() { var rotate = projection.rotate(); projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]); svg.selectAll("path.land").attr("d", path); svg.selectAll(".focused").classed("focused", focused = false); })) //Mouse events .on("mouseover", function(d) { countryTooltip.text(countryById[d.id]) .style("left", (d3.event.pageX + 7) + "px") .style("top", (d3.event.pageY - 15) + "px") .style("display", "block") .style("opacity", 1); }) .on("mouseout", function(d) { countryTooltip.style("opacity", 0) .style("display", "none"); }) .on("mousemove", function(d) { countryTooltip.style("left", (d3.event.pageX + 7) + "px") .style("top", (d3.event.pageY - 15) + "px"); });
Для реализации фокусировки на стране нам необходимо написать функцию, которая бы возвращала нам геоданные для страны по её id
‘шнику. Собственно вот она.
function country(cnt, sel) { for(var i = 0, l = cnt.length; i < l; i++) { if(cnt[i].id == sel.value) {return cnt[i];} } };
Теперь можно непосредственно перейти к реализации фокусировки (центровки) на стране, выбранной из списка.
//Country focus on option select d3.select("select").on("change", function() { var rotate = projection.rotate(), focusedCountry = country(countries, this), p = d3.geo.centroid(focusedCountry); svg.selectAll(".focused").classed("focused", focused = false); //Globe rotating (function transition() { d3.transition() .duration(2500) .tween("rotate", function() { var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]); return function(t) { projection.rotate(r(t)); svg.selectAll("path").attr("d", path) .classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; }); }; }) .transition(); })(); });
Здесь вся соль кроется в transition.tween(), который позволяет нам вызывать заданную функцию (поворот) для каждого интерполированного значения.
Крутится, вертится шар голубой.
Всё — SVG глобус готов. Исходники можно найти на GitHub (там же можно задать вопросы тем у кого read-only на Хабрахабре), а пощупать результат можно через сервис bl.ocks.org.
Давайте рассмотрим преимущества SVG:
- Возможность взаимодействовать с DOM элементами, в частности
path
- Возможность использовать CSS (как следствие из предыдущего пункта)
- Текст является текстом, со всеми вытекающими отсюда плюсами
Анимация планеты Земля
С SVG реализацией вроде разобрались, давайте посмотрим как сделать что-то подобное на canvas. Создадим простую анимацию вращения Земли. Тут многое будет аналогично предыдущему примеру. Кода мало, поэтому приведу его весь сразу.
var width = 800, height = 500; var projection = d3.geo.orthographic() .scale(245) .rotate([180, 0]) .translate([width / 2, height / 2]) .clipAngle(90); var canvas = d3.select("body").append("canvas") .attr("width", width) .attr("height", height); var c = canvas.node().getContext("2d"); var path = d3.geo.path() .projection(projection) .context(c); function getImage(path, callback) { var img = new Image(); img.src = path; img.onload = callback(null, img); } queue() .defer(d3.json, "data/world-110m.json") .defer(d3.tsv, "data/world-110m-country-names.tsv") .defer(getImage, "data/space.jpg") .await(ready); //Main function function ready(error, world, countryData, space) { var globe = {type: "Sphere"}, land = topojson.feature(world, world.objects.land), borders = topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }); //Earth rotating (function transition() { d3.transition() .duration(15000) .ease("linear") .tween("rotate", function() { var r = d3.interpolate(projection.rotate(), [-180, 0]); return function(t) { projection.rotate(r(t)); c.clearRect(0, 0, width, height); c.drawImage(space, 0, 0); c.fillStyle = "#00006B", c.beginPath(), path(globe), c.fill(); c.fillStyle = "#29527A", c.beginPath(), path(land), c.fill(); c.strokeStyle = "#fff", c.lineWidth = .5, c.beginPath(), path(borders), c.stroke(); projection.rotate([180, 0]); }; }) .transition().duration(30).ease("linear") .each("end", transition); })(); };
Вращение реализовано как поворот из точки [180, 0]
в точку [-180, 0]
, которые совпадают. Таким образом, «интерполятор», не заметив подвоха, сделает то что нам нужно. Потом мы начинаем рисовать на canvas, предварительно очистив его. Рисуем фон, сферу, материки и границы стран. Бесконечное вращение получаем за счёт рекурсивного вызова функции transition
.
«И всё-таки она вертится!»
Ну вот мы и создали анимацию. Исходники можно найти на GitHub, а полюбоваться космическими видами можно через сервис bl.ocks.org.
Рассмотрим преимущества Canvas:
- Более быстрая/плавная работа по сравнению с SVG
- Возможность интеграции с анимацией, видео, играми и прочими штуками, которые реализуют сегодня с его помощью
Заключение
Вот мы и рассмотрели ещё пару интересных примеров созданных с помощью замечательной библиотеки d3.js. Я старался, чтобы примеры были в меру просты для понимания, наглядны и довольно-таки интересны. В борьбе SVG и Canvas в итоге победила дружба, так использование той или иной технологии зависит от типа вашего проекта. Например, если ваш проект связан с картографией, то целесообразно использовать SVG, если же вы работаете с мультимедиа, то Canvas вам в помощь. Надеюсь, вам было интересно. Удачи и успехов в дальнейшем освоении d3.js!
ссылка на оригинал статьи http://habrahabr.ru/post/186532/
Добавить комментарий