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’а обновлять изображение. В моём случае это необходимо когда пользователь голосует и надо обновить распределение голосов на графике.
Читайте также:
- Поддерживаемые в Opera объекты и параметры
- Кариев Ч.А. — подробный учебник
- Коллекция SVG-изображений для RPG-игры
- Фотошопные эффекты
- Обзоры и плагины для IE от Adobe