По какой — то причине, я никогда не брал очень хороший взгляд на SVG (ссылка MozDev) в прошлом. Я концептуально знал, что это был способ описать графику в формате XML, и я знал, что Adobe имеет большой объем истории / поддержки, но я никогда не думал, что это полезно для того, что я делаю в своей повседневной работе. Это было до прошлой недели, когда читатель отправил интересный вопрос.
Читатель хотел взять данные графства для Америки и отобразить их на экране. Первоначально я был в растерянности относительно того, как это будет сделано. Очевидно, что часть AJAX не имела большого значения, но я понятия не имел, как, черт возьми, это будет сделано. Затем читатель связал меня с этим ресурсом — картой Америки в SVG, в которой каждый округ определен в чистых, славных данных. Я провел небольшое исследование и обнаружил, что данные SVG существуют в браузере DOM. Это означает, что вы можете редактировать — насколько я знаю — практически все — что связано с данными. Woot!
Я решил начать просто, хотя. Я знал, что Adobe Illustrator может выплевывать файлы SVG, поэтому я открыл Illustrator и использовал весь свой художественный талант для его создания.
Хотя это и не самая захватывающая графика в мире, она дала мне кое-что для начала. Файл SVG полностью основан на XML. Вы можете увидеть источник этого здесь:
<?xml version="1.0" encoding="utf-8"?> <!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300px" height="300px" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve"> <rect x="19" y="37" fill="#52B848" stroke="#000000" stroke-miterlimit="10" width="80" height="59" id="greenBlock" /> <rect x="168" y="53" fill="#E61E25" stroke="#000000" stroke-miterlimit="10" width="80" height="59"/> <rect x="35" y="179" fill="#DFE21D" stroke="#000000" stroke-miterlimit="10" width="227" height="81"/> <text transform="matrix(1 0 0 1 102 215)" font-family="'MyriadPro-Regular'" font-size="12" id="textBlock">TESTING</text> </svg>
Никогда прежде не видя SVG, вы можете догадаться, что делает каждая часть. Я провел небольшое исследование и обнаружил, что одним из способов доступа к данным SVG является вызов DOM getSVGDocument (). Чтобы это работало, вам нужно дождаться события загрузки изображения. Я использовал тег объекта для встраивания файла SVG, добавил прослушиватель событий, а затем сделал две простые модификации.
<script>
function svgloaded() {
console.log("test");
var svgEmbed = document.querySelector("#svgembed");
var svg = svgEmbed.getSVGDocument();
var td = svg.getElementById("textBlock");
td.textContent = "Foo";
var gn = svg.getElementById("greenBlock");
gn.setAttribute("fill", "#ff0000");
}
document.addEventListener("DOMContentLoaded", function(){
var svgEmbed = document.querySelector("#svgembed");
svgEmbed.addEventListener("load", svgloaded);
},false);
</script>
<object data="Untitled-1.svg" id="svgembed"></object>
Не очень захватывающе, но и чертовски просто. Вы можете запустить демо здесь или посмотреть на скриншот ниже.
Я проверил это в Chrome, Firefox и IE10, и он отлично работал там. Из того, что я мог видеть в своем исследовании, «основы» SVG работали довольно хорошо везде, но более продвинутые варианты не были универсально доступны. (Проверьте этот список параметров SVG на CanIUse.com для получения дополнительной информации.)
Поэтому, учитывая, что у нас есть JavaScript-доступ к отдельным частям SVG-документа и мы можем легко изменять эти данные, я оглянулся на SVG-документ американского округа. Это очень большой файл (около 2 мегабайт), содержащий данные, которые «рисуют» каждый округ в теге пути. Вот пример двух стран.
<path
style="font-size:12px;fill:#d0d0d0;fill-rule:nonzero;stroke:#000000;stroke-opacity:1;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:butt;marker-start:none;stroke-linejoin:bevel"
d="M 345.25498,268.988 L 345.54298,269.051 L 345.62898,269.092 L 347.56698,273.203 L 345.67898,273.306 L 345.24598,272.761 L 344.01998,271.43 L 343.64598,271.151 L 345.25498,268.988"
id="22121"
inkscape:label="West Baton Rouge, LA" />
<path
style="font-size:12px;fill:#d0d0d0;fill-rule:nonzero;stroke:#000000;stroke-opacity:1;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:butt;marker-start:none;stroke-linejoin:bevel"
d="M 336.45198,274.027 L 338.12498,273.058 L 338.70498,276.839 L 338.56598,277.345 L 337.41198,276.88 L 336.69998,276.384 L 335.77298,276.078 L 334.94298,276.037 L 336.45198,274.027"
id="22055"
inkscape:label="Lafayette, LA" />
Поскольку у меня не было доступа к каким-либо реальным данным для отображения, я разработал быстрый скрипт ColdFusion, который анализировал XML, считывал данные округа и создавал значение для каждого округа от 1 до 10. На данный момент давайте предположим, что число показывает, насколько вероятно, что вас съест человек. Мой сценарий затем сохранил этот файл как X.json, где X представляет год. Я создавал файлы с 1990 по 1995 год. Теперь у меня есть данные, мне просто нужно было построить приложение на их основе.
Первое, что я хотел сделать, — убедиться, что этот огромный файл SVG не затянул все приложение. Мой босс, Алан Гринблатт, недавно написал на эту тему: Асинхронная загрузка SVG . Я решил сделать что-то более сложное. Хотя его решение позволило бы мне отложить загрузку SVG, я хотел вообще пропустить загрузку SVG, если браузер поддерживает IndexedDB. Я быстро собрал немного логики, чтобы справиться с этим.
var storeIndexedDB = false;
var db;
$(document).ready(function() {
$("#svg-content").html("<p>Loading content...</p>");
//check for indexeddb and cache
if("indexedDB" in window) {
loadViaIndexedDB();
} else {
loadViaAjax();
}
});
function loadViaIndexedDB() {
var openRequest = indexedDB.open("svgdemo",1);
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
console.log("running onupgradeneeded");
if(!thisDB.objectStoreNames.contains("binarycache")) {
var os = thisDB.createObjectStore("binarycache", {autoIncrement:true});
os.createIndex("name", "name", {unique:true});
}
}
openRequest.onsuccess = function(e) {
console.log("running onsuccess");
db = e.target.result;
//Do we have it?
var transaction = db.transaction(["binarycache"],"readonly");
var store = transaction.objectStore("binarycache");
var index = store.index("name");
var request = index.get("usa.svg");
request.onsuccess = function(e) {
var result = e.target.result;
if(result) {
console.log('had it in store');
loadSVG(result.data);
} else {
//Don't have it, so load Ajax way and flag to cache
storeIndexedDB = true;
loadViaAjax();
}
}
}
openRequest.onerror = function(e) {
//Do something for the error
}
}
function loadViaAjax() {
$.get("USA_Counties.svg", {}, function(res) {
console.log("svg loaded via ajax");
if(storeIndexedDB) {
console.log("I supposed indexeddb, so will store");
db.transaction(["binarycache"],"readwrite").objectStore("binarycache").add({data:res,name:"usa.svg"});
}
loadSVG(res);
},"text");
}
Полагаю, это довольно много кода, но на самом деле все сводится к следующим шагам:
- Браузер пользователя поддерживает IndexedDB
- Откройте базу данных и посмотрите, нужно ли нам создать начальный магазин
- Посмотрим, есть ли у нас там данные
- Если нет, установите флаг, говорящий о том, что мы готовы сохранить SVG и отмените запрос на загрузку через Ajax.
- Нет IndexedDB? Нет проблем. Просто загрузите его через Ajax
- И еще одну последнюю проверку , чтобы убедиться , что мы сделали поддержку индексированной , но только еще не загружены. Если это так, сохраните этого щенка.
Конечным результатом является вызов loadSVG.
function loadSVG(data) {
console.log('about to use the svg');
$("#svg-content").html(data);
$(".yearButton").on("click", loadYear);
}
Все, что это делает, это вытягивает SVG в DOM и добавляет слушателей к нескольким кнопкам. Кнопки используются для загрузки удаленных данных JSON, которые я создал ранее. Теперь давайте посмотрим на это.
function loadYear(e) {
var year = $(this).text();
$.get(year + ".json", {}, function(res) {
for(var i=0, len=res.length;i<len; i++) {
//console.log(res[i].ID);
var id = res[i].ID;
var total = res[i].DATA;
$("#"+id).attr("style","fill:"+dataToCol(total));
}
},"json");
}
//I just translate 1-10 to a color
function dataToCol(x) {
switch(x) {
case 1: return "#ff0000";
case 2: return "#d61a00";
case 3: return "#cd3300";
case 4: return "#b34d00";
case 5: return "#9a6600";
case 6: return "#808000";
case 7: return "#669A00";
case 8: return "#4db300";
case 9: return "#1ad600";
case 10: return "#00ff00";
}
}
loadYear — это относительно простой Ajax-вызов нашего JSON-файла. Получив данные, я перебираю все графства, получаю значение и переводю значение 1-10 в цвет с красного на зеленый. Спасибо Клиффу Джонстону (@iClifDotMe) за значения RGB.
Конечный результат довольно крутой, я думаю. JSON-файлы имеют размер около 70 КБ каждый, поэтому их не так уж плохо загрузить. Вы можете увидеть полную демонстрацию здесь: http://www.raymondcamden.com/demos/2013/feb/2/test3.html
Для меня первоначально потребовалось около 5 секунд для загрузки, и каждый вызов Ajax «чувствует» около секунды или около того. Учитывая объем передаваемых данных, я чувствую, что они работают адекватно.
Но потом я решил, что этого недостаточно. Я подумал, что если мы кэшируем формы округов, почему бы не кэшировать удаленные данные? Я быстро настроил сохранение данных в кеше SessionStorage браузера.
function loadYear(e) {
var year = $(this).text();
if("sessionStorage" in window && sessionStorage[year]) {
renderYearData(JSON.parse(sessionStorage[year]));
} else {
$.get(year + ".json", {}, function(res) {
console.log("storing to "+year);
if("sessionStorage" in window) sessionStorage[year] = res;
renderYearData(JSON.parse(res));
},"text")
}
}
Тривиальная модификация, но если вы немного щелкнете по ней, она должна быть немного более быстрой. Вы можете найти это демо здесь: http://www.raymondcamden.com/demos/2013/feb/2/test4.html

