21.3. SVG - масштабируемая векторная графика

We use cookies. Read the Privacy and Cookie Policy

Масштабируемая векторная графика (SVG) - это грамматика языка XML для описания графических изображений. Слово «векторная» в названии указывает на фундаментальное отличие от таких форматов растровой графики, как GIF, JPEG и PNG, где изображение задается матрицей пикселов. Формат SVG представляет собой точное, не зависящее от разрешения (отсюда слово «масштабируемая») описание шагов, которые необходимо выполнить, чтобы нарисовать требуемый рисунок. Вот пример простого SVG-изображения в текстовом формате:

<!-- Начало рисунка и объявление пространства имен -->

<svg xmlns="http://www.w3.org/2000/svg"

  viewBox="0 0 1000 1000"> <!-- Система координат рисунка -->

  <defs> <!-- Настройка некоторых определений -->

    <linearGradient id="fade"> <!-- Цветовой градиент с именем ''fade” -->

      <stop offset="0%" stop-color="#008"/> <!-- Начинаем с темно-синего -->

      <stop offset="100%" stop-color="#ccf"/><!--Заканчиваем светло-синим-->

    </linearGradient>

  </defs>

  <! --

    Нарисовать прямоугольник с тонкой черной рамкой и заполнить его градиентом

  -->

  <rect х="100” у="200" width="800" height="600" stroke="black"

     stroke-width="25" fill="url(#fade)"/>

</svg>

На рис. 21.1 показано графическое представление этого SVG-файла.

SVG - это довольно обширная грамматика умеренной сложности. Помимо простых примитивов рисования она позволяет воспроизводить произвольные кривые, текст и анимацию. Рисунки в формате SVG могут даже содержать JavaScript-сценарии и таблицы CSS-стилей, что позволяет наделить их информацией о поведении и представлении. В этом разделе показано, как с помощью клиентского JavaScript-кода (встроенного в HTML-, а не в SVG-документ) можно динамически создавать графические изображения средствами SVG. Приводимые здесь примеры SVG-изображений позволяют лишь отчасти оценить возможности формата SVG. Полное описание этого формата доступно в виде обширной, но вполне понятной спецификации, которая поддерживается консорциумом W3C и находится по адресу http://www.w3.org/TR/SVG/. Обратите внимание: эта спецификация включает в себя полное описание объектной модели документа (DOM) для SVG-документов. В данном разделе рассматриваются приемы манипулирования SVG-графикой с помощью стандартной модели XML DOM, а модель SVG DOM не затрагивается.

К моменту написания этих строк все текущие веб-броузеры, кроме IE, имели встроенную поддержку формата SVG (она также будет включена в IE9). В последних версиях броузеров отображать SVG-изображения можно с помощью обычного элемента <img>. Некоторые немного устаревшие броузеры (такие как Firefox 3.6) не поддерживают такую возможность и требуют использовать для этих целей элемент <object>:

<object data="sample.svg" type="image/svg+xml" width=''100" height="100"/>

При использовании в элементе <img> или <object> SVG можно рассматривать как еще один формат представления графических изображений, который, с точки зрения программиста на языке JavaScript, ничем особенным не выделяется. Гораздо больший интерес представляет сама возможность встраивания SVG-изображений непосредственно в документы и выполнения операций над ними. Поскольку формат SVG является грамматикой языка XML, изображения в этом формате можно встраивать непосредственно в XHTML-документы, как показано ниже:

<?xml version="1.0"?>

<!-- Объявить HTML как пространство имен по умолчанию, a SVG - с префиксом ''svg:" -->

<html xmlns="http://www.w3.org/1999/xhtmr'

     xmlns:svg="http://www.w3.org/2000/svg">

  <body>

    Это красный квадрат: <svg:svg width="10" height="10">

      <svg:rect x="0" y="0” width="10" height="10" fill="red"/>

    </svg:svg>

    Это голубой круг: <svg:svg width="10" height="10">

      <svg:circle cx="5" cy="5" r="5" fill="blue"/>

    </svg:svg>

  </body>

</html>

Этот прием можно использовать во всех текущих броузерах, кроме IE. Нарис. 21.2 показано, как Firefox отображает этот XHTML-документ.

Стандарт HTML5 сокращает количество различий между XML и HTML и позволяет вставлять разметку на языке SVG (и MathML) непосредственно в HTML-файлы, без объявления пространств имен или префиксов тегов:

<!DOCTYPE html>

<html>

  <body>

    Это красный квадрат: <svg width="10" height="10">

      <rect x="0" y="0" width="10" height="10" fill="red"/>

    </svg>

    Это голубой круг: <svg width="10" height="10">

      <circle cx="5" cy="5" r="5" fill="blue"/>

    </svg>

  </body>

</html>

На момент написания этих строк непосредственное встраивание SVG-изображе-ний в разметку HTML поддерживали только самые последние версии броузеров.

Так как формат SVG - это грамматика языка XML, рисование SVG-изображений заключается просто в использовании модели DOM для создания соответствующих XML-элементов. В примере 21.2 приводится реализация функции pieChart(), которая создает SVG-элементы для воспроизведения круговой диаграммы, подобной той, что показана на рис. 21.3.

Рис. 21.3. Круговая диаграмма в формате SVG, построенная JavaScript-сценарием

Пример 21.2. Рисование круговой диаграммы средствами JavaScript и SVG

/* *

* Создает элемент <svg> и рисует в нем круговую диаграмму.

* Аргументы:

* data: массив чисел для диаграммы, по одному для каждого сектора.

* width,height: размеры SVG-изображения в пикселах

* сх, су, г: координаты центра и радиус круга

* colors: массив цветов в формате HTML, по одному для каждого сектора

* labels: массив меток для легенды, по одной для каждого сектора

* 1х, 1у: координаты левого верхнего угла легенды диаграммы

* Возвращает:

* Элемент <svg>, хранящий круговую диаграмму.

* Вызывающая программа должна вставить возвращаемый элемент в документ.

*/

function pieChart(data, width, height, cx, су, r, colors, labels, lx, ly) {

  // Пространство имен XML для элементов svg

  var svgns = "http://www.w3.org/2000/svg";

  // Создать элемент <svg>, указать размеры в пикселах и координаты

  var chart = document.createElementNS(svgns, "svg:svg");

  chart.setAttribute("width", width);

  chart.setAttribute("height", height);

  chart.setAttribute("viewBox", "0 0 ” + width + " " + height);

  // Сложить вместе все значения, чтобы получить общую сумму всей диаграммы

  var total = 0;

  for(var і = 0; і < data.length; і++) total += data[і];

  // Определить величину каждого сектора. Углы измеряются в радианах,

  var angles = []

  for(var і = 0; і < data.length; і++) angles[i] = data[i]/total*Math.PI*2;

  // Цикл по всем секторам диаграммы,

  startangle = 0;

  for(var і = 0; і < data.length; i++) {

    // Точка, где заканчивается сектор

    var endangle = startangle + angles[i];

    // Вычислить координаты точек пересечения радиусов, образующих сектор,

    // с окружностью. В соответствии с выбранными формулами углу 0 радиан

    // соответствует точка в самой верхней части окружности,

    // а положительные значения откладываются от нее по часовой стрелке.

    var х1 = сх + r * Math.sin(startangle);

    var y1 = су - r * Math.cos(startangle);

    var x2 = cx + r * Math.sin(endangle);

    var y2 = су - r * Math.cos(endangle);

    // Это флаг для углов, больших половины окружности.

    // Он необходим SVG-механизму рисования дуг

    var big = 0;

    if (endangle - startangle > Math.PI) big = 1;

    // Мы описываем сектор с помощью элемента <svg:path>.

    // Обратите внимание, что он создается вызовом createElementNS()

    var path = document.createElementNS(svgns, "path");

    // Эта строка хранит информацию о контуре, образующем сектор

    var d = "М " + сх + + су + // Начало в центре окружности

      " L " + х1 + "," + у1 +  // Нарисовать линию к точке (х1,у1)

      " А " + r + "," + r +    // Нарисовать дугу с радиусом r

      " 0 " + big + " 1 " +    // Информация о дуге...

      х2 + "," + у2 +          // Дуга заканчивается в точке (х2,у2)

      Z";                      // Закончить рисование в точке (сх,су)

    // Теперь установить атрибуты элемента <svg:path>

    path.setAttribute("d", d);               // Установить описание контура

    path.setAttribute("fill", colors[i]);    // Установить цвет сектора

    path.setAttribute("stroke", "black");    // Рамка сектора - черная

    path.setAttribute("stroke-width”, "2");  // 2 единицы толщиной

    chart.appendChild(path);                 // Вставить сектор в диаграмму

    // Следующий сектор начинается в точке, где закончился предыдущий

    startangle = endangle;

    // Нарисовать маленький квадрат для идентификации сектора в легенде

    var icon = document.createElementNS(svgns, "rect");

    icon.setAttribute("x”, lx);           // Координаты квадрата

    icon.setAttribute("y", ly + ЗО*і);

    icon.setAttribute("width", 20);       // Размер квадрата

    icon.setAttribute("height", 20);

    icon.setAttribute("fill", colors[i]); // Тем же цветом, что и сектор

    icon.setAttribute("stroke", "black"); // Такая же рамка,

    icon.setAttribute("stroke-width", "2");

    chart.appendChild(icon);              // Добавить в диаграмму

    // Добавить метку правее квадрата

    var label = document.createElementNS(svgns, "text");

    label.setAttribute("x", lx + 30); // Координаты текста

    label.setAttribute("y", ly + ЗО*і + 18);

    // Стиль текста можно также определить посредством таблицы CSS-стилей

    label.setAttribute("font-family", "sans-serif");

    label.setAttribute("font-size", "16");

    // Добавить текстовый DOM-узел в элемент <svg:text>

    label.appendChild(document.createTextNode(labels[і]));

    chart.appendChild(label); // Добавить текст в диаграмму

  }

  return chart;

}

Программный код в примере 21.2 относительно прост. Здесь выполняются некоторые математические расчеты для преобразования исходных данных в углы секторов круговой диаграммы. Однако основную часть примера составляет программный код, создающий SVG-элементы и выполняющий настройку их атрибутов. Чтобы этот пример мог работать в броузерах, не полностью поддерживающих стандарт HTML5, здесь формат SVG интерпретируется как грамматика XML и вместо метода createElement() используются пространство имен SVG и метод createElementNS().

Самая малопонятная часть этого примера - программный код, выполняющий рисование сектора диаграммы. Для отображения каждого сектора используется тег <svg:path>. Этот SVG-элемент описывает рисование произвольных фигур, состоящих из линий и кривых. Описание фигуры определяется атрибутом d элемента <svg:path>. Основу описания составляет компактная грамматика символьных кодов и чисел, определяющих координаты, углы и прочие значения. Например, символ М означает «move to» (переместиться в точку), и вслед за ним должны следовать координаты X и У точки. Символ L означает «line to» (рисовать линию до точки); он рисует линию от текущей точки до точки с координатами, которые следуют далее. Кроме того, в этом примере используется символьный код А, который рисует дугу (arc). Вслед за этим символом следуют семь чисел, описывающих дугу. Точное описание нас здесь не интересует, но вы можете найти его в спецификации по адресу http://www.w3.org/TR/SVG/.

Обратите внимание, что функция pieChart() возвращает элемент <svg>, содержащий описание круговой диаграммы, но она не вставляет этот элемент в документ. Предполагается, что это будет делать вызывающая программа. Диаграмма, изображенная на рис. 21.3, была создана с помощью следующего файла:

<html>

<head>

  <script src="PieChart.js"x/scr.ipt>

</head>

<body onload="document.body.appendChild(

                pieChart([12, 23, 34, 45], 640, 400, 200, 200, 150,

                ['красный','синий’,'желтый','зеленый'],

                ['Север','Юг', 'Восток', 'Запад'], 400, 100));

             ">

</body>

</html>

В примере 21.3 демонстрируется создание еще одного SVG-изображения: в нем формат SVG используется для отображения аналоговых часов (рис. 21.4). Однако вместо создания дерева SVG-элементов с самого начала, в нем используется статическое SVG-изображение циферблата, встроенное в HTML-страницу. Это статическое изображение включает два SVG-элемента <line>, представляющих часовую и минутную стрелки. Обе линии направлены вверх, в результате чего статическое изображение часов показывает время 12:00. Чтобы превратить это изображение в действующие часы, в примере используется сценарий на языке JavaScript, устанавливающий атрибут transform каждого элемента <liпе> и поворачивающий их на углы, соответствующие текущему времени.

Обратите внимание, что в примере 21.3 разметка SVG встроена непосредственно в файл HTML5 и в ней не используются пространства имен XML, как в XHTML-файле выше. Это означает, что данный пример будет работать только в броузерах, поддерживающих возможность непосредственного встраивания разметки SVG. Однако если преобразовать HTML-файл в XHTML, тот же самый прием будет работать и в старых броузерах с поддержкой SVG.

Пример 21.3. Отображение времени посредством манипулирования SVG-изображением

<! D0CTYPE НТМ1_>

<html>

<head>

  <title>Analog Clock</title>

  <script>

    function updateTime() { // Обновляет SVG-изображение часов

                            // в соответствии с текущим временем

      var now = new Date();       // Текущее время

      var min = now.getMinutes(); // Минуты

      var hour = (now.getHours() % 12) + min/60; // Часы с дробной частью

      var minangle = min*6;      // 6 градусов на минуту

      var hourangle = hour*30;   // 30 градусов на час

      // Получить SVG-элементы стрелок часов

      var minhand = document.getElementById("minutehand");

      var hourhand = document.getElementByldC'hourhand”);

      // Установить в них SVG-атрибут для перемещения по циферблату

      minhand.setAttribute("transform", "rotate(" + minangle + ",50,50)");

      hourhand.setAttribute("transform", "rotate(" + hourangle + ”,50,50)");

      // Обновлять показания часов 1 раз в минуту setTimeout(updateTime, 60000);

    }

  </script>

  <style>

    /* Все следующие CSS-стили применяются к SVG-элементам, объявленным ниже */

    #clock { /* общие стили для всех элементов часов */

      stroke: black; /* черные линии */

      stroke-linecap: round; /* с закругленными концами */

      fill: #ееf; /* на светлом, голубовато-сером фоне */

    }

    #numbers { /* стиль отображения цифр на циферблате */

      font-family: sans-serif;

      font-size: 7pt;

      font-weight: bold;

      text-anchor: middle;

      stroke: none; fill: black;

    }

  </style>

</head>

<body onload="updateTime()">

  <!-- viewBox - система координат, width и height - экранные размеры -->

  <svg id="clock" viewBox="0 0 100 100” width="500" height="500">

    <defs> <!-- Определить фильтр для рисования теней -->

      <filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">

        <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur" />

        <fe0ffset in="blur" dx="1" dy="1" result="shadow" />

        <feMerge>

          <feMergeNode in="SourceGraphic"/xfeMergeNode in="shadow"/>

        </feMerge>

      </filter>

    </defs>

    <circle id="face" cx="50" cy="50" r="45"/> <!-- циферблат -->

    <g id="ticks"> <!-- 12 часовых меток -->

      <line x1='50' y1='5.000' x2='50.00‘ y2='10.00'/>

      <line x1='72.50' y1='11.03' x2=’70.00' y2='15.36'/>

      <1ine x1='88.97' y1='27.50' x2='84.64' y2='30.00'/>

      <line x1='95.00' y1='50.00' x2='90.00' y2='50.00'/>

      <line х1 = ‘88. 97' у1 = '72. 50' х2='84.64' у2='70.00'/>

      <line х1 = '72. 50' у 1 ='88. 97' х2='70.00' у2='84.64'/>

      <line х1 = '50.00’ у 1 ='95.00' х2='50.00' у2='90.007>

      <line х1 = '27.50' у1 = '88.97' х2='30.00' у2='84.64'/>

      <line х1 = ' 11.03' у1 = '72. 50' х2=' 15.36' у2='70.00'/>

      <line х1='5.000' у1='50.00' х2='10.00' у2='50.00'/>

      <line х1='11.03' у1='27.50' х2='15.36' у2='30.00'/>

      <line х1='27.50' у1='11.03' х2='30.00' у2='15.36/>

    </g>

    <g id="numbers"> <!-- Числа в основных направлениях -->

      <text х="50" y=”18">12</text>

      <text х="85" y="53">3</text>

      <text х="50" y="88">6</text>

      <text х="15" y="53">9</text>

    </g>

    <!-- Нарисовать стрелки, указывающие вверх. Они вращаются сценарием. -->

    <g id=''hands" filter="url(#shadow)"> <!-- Добавить тени к стрелкам -->

      <line id="hourhand" x1="50" у 1="50" х2="50" у2=''24"/>

      <line id="minutehand" х1="50" у1="50" х2="50" у2="20”/>

    </g>

  </svg>

</body>

</html>