Введение в SVG и пример - pie chart

SVG (scalable vector graphics) это векторный формат графики подобно EPS, анимации и интерактива с пользователем, разрабатываемый в W3C. Внутри файл не бинарный а обычный XML, описывающий объекты, их эффекты и поведение.

Векторная графика в общем нужна при изменении размера изображения без потери качества, например в полиграфии. В web я это вижу в резиновых сайтах, где размеры блоков установлены в %, размер шрифтов в em, а лого - в SVG. Пересели на более хороший монитор - всё изменилось пропорционально. Практические примеры - иконки, графики, карты, логотипы, интерфейсы. Ниже я привожу пример такого Pie-chart'а.

Интеграция

HTML - Inline

Поскольку SVG по сути XML, то его можно сразу inline-стилем описывать в XHTML-структуре. Однако как я уже убедился, XHTML1.1 doctype подразумевает что MIME документа уже не text-plain. А "ослик" IE6 не понимает XHTML в принципе, с другой стороны Firefox использует два парсера, и если MIME не application/xhtml+xml, то inline SVG не будет распознан. Это палка с двумя концами - IE и FF.

<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200">
<circle cx="150" cy="100" r="50" />
</svg>


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

<object type="image/svg+xml" data="./mySVGimage.svg" name="owMain" width="400" height="150"></object>

CSS и Javascipt

Как вы увидите ниже - поскольку SVG очень тесно связан с HTML/XML, то естественно что на графические объекты можно не только описывать при помощи CSS, но и писать Javascript-функции на всевозможные onclick и тп. event'ы (отсюда и интерактивность).

<circle onclick="circle_click(evt)" cx="300" cy="225" r="100" fill="red"/>

Конвертирование и редакторы

Конвертировать SVG можно и в png/jpeg, но для этого фактически надо проделать работу обработчика. В PHP этим занимается PEAR XML_svg2image библиотека. Ещё есть сервис по конвертированию растрового изображения в векторный (правда там EPS). Из редакторов - есть Inkscape и Adobe Illustrator, Corel Draw. Самый эффективным способ - использование Imagemagick:

$ convert ok.svg ok.png

Графика

Примитивы

Всякое рисование начинается с определения пространства (двумерного), разделения на координаты (в единицах с плавающей точкой или процентах) и введения примитивных конструкций:

  • line - прямая линия. x1, y1, x2, y2 - координаты
  • polyline - ломанная линия. points - перечисление координат точек
  • rect - прямоугольник. x,y, width, height, rx, ry - верхний левый угол, размеры, радиусы углов
  • polygone - многоугольник. Похож на polyline. points - координаты точек
  • circle - круг. cx, cy, r - координаты центра и радиус
  • ellipse. cx, cy, rx, ry - координаты центра и радиусы
  • text - текстовая надпись. Очень неудобная, потому что без переноса строк и с абсолютным позиционированием. x,y, font-family, font-size
  • tspan - может описывать слова внутри text-элемента. Например сдвигать или раскрашивать
  • tref - повторное использование text-элемента с указанным id
    <defs><text id="myText">Простой текст</text></defs>
    <tref xlink:href="#myText" x="50" y="50"></tref>
  • textPath - текст на path-кривой, связанный по id<defs><path id="myPath" d="M25,75 C25,15 175,15 175,75" /></defs>
    <text><textPath xlink:href="#myPath" startOffset="50%">Водка Vinogradoff</textPath></text>
  • image - растровое изображение
    <image image-rendering="optimizeSpeed" xlink:href"background.jpg" width="100%" height="100%" preserveAspectRatio="xMidYMid slice" filter="url(#blurpane)"/>
Параметры и стили

Как и в html, одни линии мало кому нужны - их надо закрашивать, указывать цвета, и всё это делается параметрами

  • fill, fill-rule - заливка. Например "none",blue, indigo.
  • stroke, stroke-linecap, stroke-linejoin, stroke-dasharray, stroke-dashoffset, stroke-width - граница, аналог border. Вместе stroke-width.
  • font-family, font-size, font-style, font-weight - шрифты для text-элементов
  • text-anchor

Для заливки используются тэги:

  • pattern
  • linearGradient
  • radialGradient

Эти параметры можно все объединить в один аналог CSS и записать в inline-стиле:
style="stroke-width:1; stroke:blue; fill:none"

Группы и кривые Безье

Элементы можно и нужно группировать друг в друга. Кроме того что tspan устанавливаетсявнутрь text-элемента, группирование происходи благодаря <g> элементу.

Кривые Безье это плавные переходные линии, задающиеся по точкам. В SVG для этого существует элемент path, у которого подобно ломанной линии указываются координаты. Рядом с координатами могут стоять буквы, обозначающие свойства линии. Большие буквы говорят об абсолютном позиционировании, маленькие об относительном<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200">

  • M - начало кривой (x,y)
  • Z - конец кривой (без координат)
  • L -прямая линия (x,y)
  • H - горизонтальная линия
  • V - вертикальная линия
  • Q - квадратичная кривая по одной точке
  • T - продолжение кривой с отражением предыдущей точки - упрощает рисование повторяющихся ритмов
  • С - собственно кривая Безье третьего порядка по двум точкам
  • S - упрощённая версия C
  • A - эллиптическая кривая (радиусы, поворот)

Трансформации и возможности

Объекты в SVG пожно искажать, крутить и перемещать при помощи фильтров, которые указываются в качестве параметров:

  • translate - перенос объекта
  • rotate -вращение
  • scale - масштабирование
  • scewX, scewY - искажение
  • matrix - смешанная трансформация

SVG поддерживает фильтры с эффектами освещения. А кроме статичных изображени есть возможность, анимации и интерактивности с пользователем. Например тетрис или нашумевший Microsoft Table и Silverlight реализован программно в SVG , причём при большом желании - заработало и видео (формата Ogg Theora=SVGT). Конечно последние возможности ещё не реализованы во всех браузерах, но первооткрыватели типа Opera имеются.

Тортовый график на SVG и PHP/DB

Несомненно Google API - очень удобная штука. Но не стоит забывать что всё-таки это внешний сервис, пусть и надёжный. Коммерческие разработки не любят рисковать, поэтому генерация графиков всё-таки должно быть локальным (если они не статические).

Генерировать изображение я буду с помощью php, на основе полученных из БД данных для отображения результатов опроса (poll). Поскольку GD-библиотека хоть и понимает размер SWF, генерирует только бинарные файлы. Поэтому генерировать прийдётся XML: header("Content-Type: image/svg+xml");

Посмотрим к чему надо стремится (пока без крутой анимации)..

Начнём с резинового расположения двух эллипсов? Сектора видимо идут в порядке убывания по часовой стрелке, что-бы уменьшить количество градиента на мелких участках. Цвет сектора видимо расчитывается пропорционально числу кусочков - тут то и возникает проблема. Как оказывается, сектор нельзя просто-так залить с двумя линиями и существующим эллипсом - фигура должна быть точно очерчена, поэтому надо рисовать всё по отдельности при помощи path, а ellipse нам не понадобится.

Тригонометрия цвета

Для вычисления в цикле координат path-элемента необходимо вспомнить немного математики. Поскольку окружность - частный случай эллипса то в формулах есть много схожего, что нам очень пригодится:

x=cos(angle)*radius; y=sin(angle)*radius; //circle
x=cos(angle)*rx; y=sin(angle)*ry; //ellipse

Для эллипса же радиус просто заменяется на большую и малую полуось соответсвенно. Дальше создаём цикл, где вычисляем процентное распределение данных, градус длясектора с path. Вчисляем координаты для арки, для этого используем "сдвиг градуса" что-бы отрисовка всех секторов шла вплотную.

RGB куб, можно по разному резать, делать проекции и вводить свои системы координат типа CMYK и HSL. Разные оттенки выбранной оранжевой заливки получаются при разбиении отрезка между выбранной точкой-цветом и вершиной белого цвета (255,255,255).

if ($intTotalValue) // если сумма значений голосования больше нуля то можно делить и рисовать
foreach((array) $Data as $key=>$recEntry){
$Data[$key]->percent=$recEntry->value/$intTotalValue;
$Data[$key]->color[0]=round($graph->fill[0]+($key/count($Data)*(255-$graph->fill[0])));
$Data[$key]->color[1]=round($graph->fill[1]+($key/count($Data)*(255-$graph->fill[1])));
$Data[$key]->color[2]=round($graph->fill[2]+($key/count($Data)*(255-$graph->fill[2])));

$Data[$key]->degree=360*$Data[$key]->percent;
$Data[$key]->start['x']=$graph->cx+round(cos(deg2rad($intDegreeShift)) * $graph->rx,3);
$Data[$key]->start['y']=$graph->cy+round(sin(deg2rad($intDegreeShift)) * $graph->ry,3);
$Data[$key]->end['x']=$graph->cx+round(cos(deg2rad($intDegreeShift+$Data[$key]->degree)) * $graph->rx,3);
$Data[$key]->end['y']=$graph->cy+round(sin(deg2rad($intDegreeShift+$Data[$key]->degree)) * $graph->ry,3);
$intDegreeShift+=$Data[$key]->degree; //increase degree shift

$boolIsLargeArc=$Data[$key]->degrees>180? 1 : 0;
echo "\n".'<path d="M'.$graph->cx.','.$graph->cy.' L'.$Data[$key]->start['x'].','.$Data[$key]->start['y'].' A'.$graph->rx.','.$graph->ry.' 0 '.$boolIsLargeArc.',1 '.$Data[$key]->end['x'].','.$Data[$key]->end['y'].' z" style="stroke:black;stroke-width: 0;fill:rgb('.implode(',',$Data[$key]->color).');fill-opacity: 1;"/>';
}

Если посмотреть на график внимательно, то видно что объёмность делается при помощи градиента, который в SVG мы сначала зальём в качестве фона, а поверх будем накладывать уже конкретные цвета сектора с прозрачностью:

<defs>
<linearGradient id="MyGradient">
<stop offset="5%" stop-color="rgb(65,65,65)"/>
<stop offset="35%" stop-color="rgb(200,200,200)"/>
<stop offset="95%" stop-color="rgb(65,65,65)"/>
</linearGradient>
</defs>

Основные проблемы

Но проблема остаётся и с закрашиванием пограничных секторов - закрашивать переднюю область поверх градиента надо только у тех областей, которые до половины торта идут

 

Абсолютный текст

Кроме того вопрос возникает как располагать текстовые описания секторов? Google попросту ведёт линии с середин секторов, даже если текст не умещается в одну строчку. В SVG позиционировать текст надо от верхнего левого угла - что уже проблема, поскольку ширины мы не знаем. Выхода два - отказаться от относительного позиционирования текста и сделать выноски цветом, либо использовать текст с фиксированной шириной (monospace, Courier) и на этой основе расчитывать длину в пикселях и положение на лету. Благо я наткнулся на параметр text-anchor:end, который странным образом развернул текст как надо.

Интерактивность и будущее

Благодаря интеграции с javascript'ом, можно соответсвенно в реальном времени при помощи AJAX'а обновлять изображение. В моём случае это необходимо когда пользователь голосует и надо обновить распределение голосов на графике.

Читайте также:

Комментарии

  • Сергей
    avatar За PEAR XML_svg2image спасибо... Ток он, как я понял, с 2002 года не поддерживается...
    Вы его сами не пробовали?
    Ответить
  • ВамМоеИмяНеВажно
    avatar в IE ваш сайт выглядит несколько иначе, чем в FF, а вообще не впечатлило, сори, просто повторяете широко распространенные рассуждения выбирая из общей массы на свой вкус...
    Ответить