Как оживить SVG, часть 2

Азы для начинающих


Оглавление



Введение


После выхода первой части статьи я получил ряд просьб написать продолжение и так же на пальцах показать взаимодействие с серером. Честно говоря, я совсем не понимаю состав целевой аудитории второй части статьи: тем, кто имеет хотя-бы поверхностное представление о WEB-технологиях и о динамическом представлении информации на WEB-страницах, первой части должно быть вполне достаточно, остальным же изложенное здесь вряд-ли будет понятно. Но иногда проще что-то сделать, чем объяснить, почему этого делать не надо. Поэтому, несмотря на то, что на главной странице сайта я писал, что здесь будет рассматриваться только клиентская часть, я сел и написал данную вторую часть.

Сразу приведу несколько замечаний

1. Так как хостинговый план, на котором работает этот сайт, не предусматривает исполнения пользовательских программ на сервере, рабочие примеры будут приводиться в виде <iframe>, с содержимым со стороннего WEB-сервера.

2. По понятным причинам, я не могу располагать примеры для данного общедоступного сайта на рабочих серверах. Поэтому я буду брать информацию с одного из своих сайтов, расположенного на бесплатном хостинге, стабильность работы которого не всегда на высоте.

3. В качестве динамического содержимого, свободно доступного в сети Интернет, у меня в наличие имеется только метеоинформация по ряду городов страны, эту информацию я и буду отображать. Я прекрасно понимаю, что по динамичности изменения информации это не самый наглядный пример, но другого на данный момент у меня нет.

4. Реализацию серверной части, здесь я приведу на языке PHP, как на наиболее известном WEB программистам, хотя в реальных системах я PHP не использую.

5. Вся программная реализация будет представлена на уровне Hello, World!, а не на уровне рабочих программ. То есть это будет всего лишь учебный пример без какой-либо обработки нештатных ситуаций и т. д..

Наверх

Подготовка


В качестве отображаемых параметров я буду показывать направление ветра в 3 городах: Архангельске (ULAA), Мурманске (ULMM) и Санкт-Петербурге (ULLI). Здесь в скобках указан международный код ближайшего аэропорта - в качестве метеоинформации я буду использовать данные METAR. Показывать направление ветра я буду в виде 3 вращающихся стрелок. Для этого я нарисую следующую SVG картинку.

Стрелки изначально направлены вниз, так как в метеорологии принято в качестве направления ветра указывать направление, откуда он дует, а не куда, а за нулевой угол принято направление на север, то есть вверх.

Содержимое SVG файла:

<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" viewBox="0 0 300 200" preserveAspectRatio="xMidYMid meet" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns="http://www.w3.org/2000/svg"> <defs> <symbol id="arrow" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <path fill="red" stroke="none" d="M30,0 L70,0 L50,100 z"/> </symbol> </defs> <use id="ULAA" x="15" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULMM" x="115" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULLI" x="215" y="15" width="70" height="70" xlink:href="#arrow" /> <g font-size="12" stroke="none" text-anchor="middle"> <text x="50" y="120" fill="black">ULAA</text> <text x="150" y="120" fill="black">ULMM</text> <text x="250" y="120" fill="black">ULLI</text> </g> </svg>

Здесь я определил символ стрелки в виде треугольника с идентификатором arrow и потом трижды вывел его на рисунке, снабдив подписью ICAO кода аэропорта. Вроде бы картинка готова и можно ее оживить, но ветер такая капризная штука, что может не дуть вообще и, соответственно, не иметь направления - это надо как то обозначить. Да и начальное неопределенное состояние до получения данных с сервера надо тоже как то обозначить. Поэтому несколько усложним картинку, накрывши для неопределенного состояния стрелки кругом со знаком вопроса.

Содержимое нового SVG файла:

<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" viewBox="0 0 300 200" preserveAspectRatio="xMidYMid meet" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns="http://www.w3.org/2000/svg"> <defs> <symbol id="arrow" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <path fill="red" stroke="none" d="M30,0 L70,0 L50,100 z"/> </symbol> <symbol id="x" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" /> <text x="50" y="75" font-size="70" stroke="none" text-anchor="middle" fill="black">?</text> </symbol> </defs> <use id="ULAA_a" x="15" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULMM_a" x="115" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULLI_a" x="215" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULAA_x" x="10" y="10" width="80" height="80" xlink:href="#x" /> <use id="ULMM_x" x="110" y="10" width="80" height="80" xlink:href="#x" /> <use id="ULLI_x" x="210" y="10" width="80" height="80" xlink:href="#x" /> <g font-size="12" stroke="none" text-anchor="middle"> <text x="50" y="110" fill="black">ULAA</text> <text x="150" y="110" fill="black">ULMM</text> <text x="250" y="110" fill="black">ULLI</text> <text id="ULAA_d" x="50" y="125" fill="black">?</text> <text id="ULMM_d" x="150" y="125" fill="black">?</text> <text id="ULLI_d" x="250" y="125" fill="black">?</text> <text id="ULAA_t" x="50" y="140" fill="black">?</text> <text id="ULMM_t" x="150" y="140" fill="black">?</text> <text id="ULLI_t" x="250" y="140" fill="black">?</text> <text id="st" x="150" y="175" fill="black">Server time:?</text> </g> </svg>

Здесь, помимо кругов с вопросами, я добавил еще текстовые поля, куда буду выводить дату и время, на которые актуальна информация, направление ветра в градусах, а так же собственное время сервера (это будет наиболее динамичная информация). В данный момент они тоже заполнены знаками вопроса.

Наверх

Реализация


PHP скрипт сервера (назовем его demo1.php):

<?php $sql_srv = "???"; //SQL server $sql_db = "???"; //SQL database $sql_usr = "???"; //SQL user $sql_psw = "???"; //SQL password $mysqli = new mysqli($sql_srv, $sql_usr, $sql_psw, $sql_db); $query = $mysqli->query("SELECT ikao, DATE_FORMAT(dt, '%d.%m.%Y %H:%i') AS dt, ". "wndd FROM metar_rt WHERE ikao='ULAA' OR ikao='ULMM' OR ikao='ULLI';"); header("Content-type: application/json"); echo "{\"data\":["; $f = false; while ($row = $query->fetch_assoc()) { if ($f) echo ","; else $f = true; echo json_encode($row); } $dt = new DateTime("now", new DateTimeZone("UTC")); $st = $dt->format("d.m.Y H:i"); echo "],\"st\":\"$st\"}"; $query->free(); $mysqli->close(); ?> Ну тут, собственно ничего интересного нет: выполняем запрос к базе данных, результат сериализуем в JSON, туда же подсовываем время сервера, вот и все. Естественно, вопорсы в имени SQL сервера, базы данных и т. д. нужно заменить на свои значения.

SVG файл картинки:

<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" viewBox="0 0 300 200" preserveAspectRatio="xMidYMid meet" onload="svgload_svg(evt);" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns="http://www.w3.org/2000/svg"> <defs> <script type="application/ecmascript"><![CDATA[ function svgload_svg(evt) { var w = window, w0 = null; while (w && !w.svgload_html && w != w0) { w0 = w; w = w.parent; } if (w) w.svgload_html(evt); } ]]></script> <symbol id="arrow" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <path fill="red" stroke="none" d="M30,0 L70,0 L50,100 z"/> </symbol> <symbol id="x" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" /> <text x="50" y="75" font-size="70" stroke="none" text-anchor="middle" fill="black">?</text> </symbol> </defs> <use id="ULAA_a" x="15" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULMM_a" x="115" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULLI_a" x="215" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULAA_x" x="10" y="10" width="80" height="80" xlink:href="#x" /> <use id="ULMM_x" x="110" y="10" width="80" height="80" xlink:href="#x" /> <use id="ULLI_x" x="210" y="10" width="80" height="80" xlink:href="#x" /> <g font-size="12" stroke="none" text-anchor="middle"> <text x="50" y="110" fill="black">ULAA</text> <text x="150" y="110" fill="black">ULMM</text> <text x="250" y="110" fill="black">ULLI</text> <text id="ULAA_d" x="50" y="125" fill="black">?</text> <text id="ULMM_d" x="150" y="125" fill="black">?</text> <text id="ULLI_d" x="250" y="125" fill="black">?</text> <text id="ULAA_t" x="50" y="140" fill="black">?</text> <text id="ULMM_t" x="150" y="140" fill="black">?</text> <text id="ULLI_t" x="250" y="140" fill="black">?</text> <text id="ULAA_v" x="50" y="155" fill="black">?</text> <text id="ULMM_v" x="150" y="155" fill="black">?</text> <text id="ULLI_v" x="250" y="155" fill="black">?</text> <text id="st" x="150" y="175" fill="black">Server time:?</text> </g> </svg> Тут я проделал то, что описывал в первой части статьи - добавил маленький скрипт, запускаемый после полной загрузки документа.

Ну и наконец, содержимое HTML страницы:

<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta charset="UTF-8"> <meta name="author" content="Рядков Сергей" /> <meta name="copyright" lang="ru" content="Рядков Сергей" /> <style type="text/css"> body { margin:0; padding:0; overflow:hidden; } </style> <meta http-equiv="content-language" content="ru" /> <script type="text/javascript" src="/js/jquery-2.1.0.min.js"></script> <script type="text/javascript" src="/js/jquery-migrate-1.2.1.min.js"></script> <script type="text/javascript"> var svg; window.svgload_html = function(evt) { svg = $(evt.target); setInterval(function() { $.get("demo1.php", function(data) {setdata(data);}); }, 60000); $.get("demo1.php", function(data) {setdata(data);}); }; function setdata(data) { for (var i = 0, l = data.data.length; i < l; ++i) { var dt = data.data[i].dt.split(" "); svg.find("#" + data.data[i].ikao + "_d").text(dt[0]); svg.find("#" + data.data[i].ikao + "_t").text(dt[1] + " UTC"); if (data.data[i].wndd !== null && data.data[i].wndd >= 0) { svg.find("#" + data.data[i].ikao + "_v").text(data.data[i].wndd + "°"); var a = svg.children("#" + data.data[i].ikao + "_a"); a.attr("transform", "rotate(" + data.data[i].wndd + "," + (+a.attr("x") + 35) + ",50)"); svg.children("#" + data.data[i].ikao + "_x").attr("display", "none"); } else { svg.find("#" + data.data[i].ikao + "_v").text("?"); svg.children("#" + data.data[i].ikao + "_x").attr("display", "block"); } } svg.find("#st").text("Server time: " + data.st + " UTC"); } </script> <title>Demo1</title> </head> <body> <embed width="100%" height="100%" src="demo1.svg" /> </body> </html> Здесь, после загрузки SVG файла, и далее по таймеру раз в минуту, мы выполняем AJAX запрос к серверу (функция $.get(...)), результаты которого в виде готового объекта JavaScript, передаем функции setdata(), которая в соостетствии с полученной информацией производит манипуляции с элементами SVG.

Результаты проделанной работы видно во фрейме ниже (если не видно - попробуйте обновить страницу. Помните, что я во введении писал про работу хостинга, куда я поместил данный пример?)

Посмотреть работающий пример на полном экране можно перейдя по ссылке

Если Вас вводит в ступор направление стрелки и значение угла в градусах под ней, вспомните, что я писал про направление ветра: значение в градусах показывает откуда он дует, а стрелка - куда.

Наверх

Заключение


Картинка, которая у нас получилась, выглядит несколько скучновато, поэтому в заключении немного приукрасим ее: добавим объем и заставим стрелки покачиваться, подобно реальному флюгеру. Что вышло в результате - показано ниже.

Посмотреть на полном экране можно перейдя по ссылке

Для желающих самостоятельно разобраться с последней картинкой, привожу содержимое SVG файла:

<?xml version="1.0" encoding="utf-8"?> <svg version="1.1" viewBox="0 0 300 200" preserveAspectRatio="xMidYMid meet" onload="svgload_svg(evt);" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns="http://www.w3.org/2000/svg"> <defs> <script type="application/ecmascript"><![CDATA[ function svgload_svg(evt) { var w = window, w0 = null; while (w && !w.svgload_html && w != w0) { w0 = w; w = w.parent; } if (w) w.svgload_html(evt); } ]]></script> <symbol id="arrow" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <g> <circle cx="40" cy="17" r="10" fill="red" stroke="none" /> <circle cx="60" cy="17" r="10" fill="red" stroke="none" /> <circle cx="50" cy="93" r="5" fill="red" stroke="none" /> <path fill="red" stroke="none" d="M40,7 L60,7 L70,17 L55,93 L45,93 L30,17 z"/> <circle cx="50" cy="50" r="20" fill="red" stroke="none" /> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="5,50,50" to="-5,50,50" calcMode="spline" id="ani1" keySplines=".5 0 .5 1" begin="0s; ani2.end" dur="3s" /> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="-5,50,50" to="5,50,50" calcMode="spline" id="ani2" keySplines=".5 0 .5 1" begin="ani1.end" dur="3s" /> </g> </symbol> <symbol id="scale" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <circle cx="50" cy="50" r="44" fill="none" stroke="blue" stroke-width="6" /> <circle cx="50" cy="6" r="6" fill="white" stroke="none" /> <circle cx="94" cy="50" r="6" fill="white" stroke="none" /> <circle cx="50" cy="94" r="6" fill="white" stroke="none" /> <circle cx="6" cy="50" r="6" fill="white" stroke="none" /> <g font-size="10" stroke="none" fill="black" text-anchor="middle"> <text x="50" y="10">N</text> <text x="94" y="54">O</text> <text x="50" y="98">S</text> <text x="6" y="54">W</text> </g> </symbol> <symbol id="x" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"> <circle cx="50" cy="50" r="45" fill="white" stroke="red" stroke-width="10" /> <text x="50" y="75" font-size="70" stroke="none" text-anchor="middle" fill="black">?</text> </symbol> <filter id="light" filterUnits="userSpaceOnUse" x="0" y="0" width="350" height="120"> <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/> <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/> <feSpecularLighting in="blur" surfaceScale="5" specularConstant=".75" specularExponent="20" lighting-color="#bbbbbb" result="specOut"> <fePointLight x="-5000" y="-10000" z="20000"/> </feSpecularLighting> <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut"/> <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic" k1="0" k2="1" k3="1" k4="0" result="litPaint"/> <feMerge> <feMergeNode in="offsetBlur"/> <feMergeNode in="litPaint"/> </feMerge> </filter> </defs> <rect x="0" y="0" width="300" height="200" fill="silver" stroke="none" /> <g filter="url(#light)"> <use x="3" y="3" width="94" height="94" xlink:href="#scale" /> <use x="103" y="3" width="94" height="94" xlink:href="#scale" /> <use x="203" y="3" width="94" height="94" xlink:href="#scale" /> <use id="ULAA_a" x="15" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULMM_a" x="115" y="15" width="70" height="70" xlink:href="#arrow" /> <use id="ULLI_a" x="215" y="15" width="70" height="70" xlink:href="#arrow" /> </g> <use id="ULAA_x" x="15" y="15" width="70" height="70" xlink:href="#x" /> <use id="ULMM_x" x="115" y="15" width="70" height="70" xlink:href="#x" /> <use id="ULLI_x" x="215" y="15" width="70" height="70" xlink:href="#x" /> <g font-size="12" stroke="none" text-anchor="middle"> <text x="50" y="110" fill="black">ULAA</text> <text x="150" y="110" fill="black">ULMM</text> <text x="250" y="110" fill="black">ULLI</text> <text id="ULAA_d" x="50" y="125" fill="black">?</text> <text id="ULMM_d" x="150" y="125" fill="black">?</text> <text id="ULLI_d" x="250" y="125" fill="black">?</text> <text id="ULAA_t" x="50" y="140" fill="black">?</text> <text id="ULMM_t" x="150" y="140" fill="black">?</text> <text id="ULLI_t" x="250" y="140" fill="black">?</text> <text id="ULAA_v" x="50" y="155" fill="black">?</text> <text id="ULMM_v" x="150" y="155" fill="black">?</text> <text id="ULLI_v" x="250" y="155" fill="black">?</text> <text id="st" x="150" y="175" fill="black">Server time:?</text> </g> </svg>

Наверх