По какой — то причине, я никогда не брал очень хороший взгляд на 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