KAPITOLA 11 Kreslení v prohlížeči Možná budete chtít ve svých webových aplikacích dynamicky zobrazovat diagramy, grafy nebo jinou vizuální reprezentaci dat. Samozřejmě můžete data odeslat na server a nechat jej vygenerovat potřebné obrázky, ale proč se namáhat, když můžete nechat kreslit prohlížeč? Je k dispozici několik funkcí, které vám umožní kreslit grafické prvky v prohlížeči. Tato kapitola vám představí dvě technologie v moderních webových prohlížečích, které vám umožní kreslit vektoru grafiku – SVG (Scalable Vector Graphics) a VML (Vector Markup Language). Rovněž uvidíte komponenty třetích stran, které používají jednu z uvedených technologií a umožní vám kreslit diagramy, grafy a jiné grafické prvky. Nakonec se dozvíte o novém elementu jazyka HTML 5 pro kreslení na vašich stránkách.
Jazyk SVG Jak víte z předchozí kapitoly, běžné grafické soubory typu GIF, JPG a PNG obsahují binární data. Jsou navrženy tak, že se zobrazí s jejich původními rozměry. Takové obrázky nelze zvětšovat nebo zmenšovat, aniž by nedošlo ke ztrátě kvality obrázku. Naproti tomu vektorové obrázky je možné zvětšovat a zmenšovat bez ztráty kvality. Toho dosahují definováním jednotlivých prvků obrázku – čar, tvarů, barev, přechodů a dalších prvků, které společně tvoří výsledný obrázek. Příkladem vektorové grafiky jsou soubory písem. Je možné měnit velikost písma bez ztráty kvality. Kdykoliv změníte velikost písma, přepočítá se zobrazení písma podle čar a tvarů, které tvoří jednotlivá písmena.
K1743.indd 323
30.3.2010 10:16:34
324
Část III: Prezentace
SVG (Scalable Vector Graphics) je doporučení konsorcia W3C (http://www.w3.org/Graphics/SVG/) popisující speciální formát XML pro zobrazení dvourozměrné vektorové grafiky ve webovém prohlížeči. Vztah SVG a grafického obsahu lze srovnat se vztahem jazyka XHTML k textovému obsahu. Ve skutečnosti jazyk SVG umí spolupracovat, stejně jako jazyk XHTML, s dalšími technologiemi webových prohlížečů, třeba s jazykem CSS nebo modelem DOM. Jazyk SVG vznikl k tomu, aby vývojářům nabídl nativní podporu pro tento typ grafického obsahu, který dříve mohli kreslit jen pomocí doplňku Flash Player od společnosti Adobe. V současnosti se s podporou jazyka SVG setkáte v prohlížečích Firefox 1.5 a novějším, Safari 3 a novějším, Opera 9.5 a novějším a v Google Chrome. Na webu se s touto technologií setkáte zřídka, protože ji nepodporuje prohlížeč Internet Explorer a převládá doplněk Flash Player. Kód jazyka SVG je možné vkládat přímo do dokumentu XHTML nebo se na něj odkážete jako na obrázkový soubor s příponou .svg v elementu img. Element svg obsahuje elementy popisující obrázek, má tedy roli plátna pro kreslení. Uvnitř plátna můžete vybírat z nesčetného množství elementů popisujících obrázek, který chcete zobrazit. Kromě kreslení tvarů a čar můžete nahrávat externí obrázky, zobrazovat text a nastavovat přechody libovolným elementům na plátně.
Tvorba obrázkových souborů SVG Nejprve se podíváme na jednoduchý soubor s obrázkem SVG. Výpis 11.1 ukazuje obsah souboru s obrázkem SVG, který vytváří přechod, několik tvarů, čar a nějaký text. Uložte tento zdrojový kód do souboru s názvem my-vector.png a otevřete jej v prohlížeči s podporou technologie SVG. Měli byste vidět stránku z obrázku 11.1. Výpis 11.1. Jednoduchý soubor s obrázkem SVG. <svg width=“1024“ height=“768“ version=“1.1“ xmlns=“http://www.w3.org/2000/svg“>
<stop offset=“0“ stop-color=“#000000“ /> <stop offset=“1“ stop-color=“#cccccc“ /> Ukázkový text
Obrázek 11.1. Jednoduchý soubor s obrázkem SVG.
K1743.indd 325
30.3.2010 10:16:34
326
Část III: Prezentace
Jazyk SVG v kódu jazyka HTML Samostatné soubory s kódem jazyka SVG fungují, ale neumožňují dynamické zobrazování. Když definujete obrázky přímo v dokumentu HTML, můžete dynamicky přistupovat k jednotlivým elementům plátna přes model DOM. Výpis 11.2 ukazuje, jak popsat obrázek zobrazený na obrázku 11.2 pomocí jazyka XHTML bez odkazů na externí soubory. Kód z výpisu 11.2 musíte uložit do souboru s příponou .xhtml, aby tak prohlížeč analyzoval kód jazyků XML a HTML současně. Syntaktický analyzátor jazyka XML je nezbytný pro zobrazení obrázku SVG a prohlížeč jej používá, jen když si je absolutně jistý, že dokument obsahuje kód v jazyku XML. Toho lze dosáhnout nastavením přípony .xhtml nebo donucením webového serveru, aby odesílal typ MIME application/xhtml+xml. Tento typ MIME byste měli používat pro všechny dokumenty XHTML místo obvyklého text/html určeného pro dokumenty HTML. Jediným důvodem, proč nepoužívat tento typ MIME, je to, že prohlížeč IE jej nerozpozná a odmítne zobrazit stránku. Výpis 11.2. Jednoduchý obrázek SVG popsaný ve stránce XHTML. Ukázka SVG Ukázka SVG
<svg width=“1024“ height=“768“ version=“1.1“ xmlns=“http://www.w3.org/2000/svg“> <stop offset=“0“ stop-color=“#000000“ /> <stop offset=“1“ stop-color=“#cccccc“ /> Ukázkový text
Tento přístup však není vhodný pro všechny moderní webové prohlížeče. Prohlížeč IE nerozpozná příponu souboru .xhtml a typ MIME application/xhtml+xml. Přestože prohlížeč IE nepodporuje jazyk SVG, stále chcete dokumenty XHTML nahrát do prohlížeče. Je nutné jiné řešení.
Specifikace kódu jazyka SVG prostřednictvím JavaScriptu Naštěstí je možné psát standardní stránky HTML a současně používat obrázky SVG, a to dynamickým přidáváním elementů jazyka SVG do stránky pomocí JavaScriptu. Musíte však zajistit, že ele-
K1743.indd 326
30.3.2010 10:16:35
Kapitola 11: Kreslení v prohlížeči
327
menty SVG budete zpracovávat analyzátorem jazyka XML. Výpis 11.3 ukazuje, jak pomocí JavaScriptu dynamicky vytvořit obrázek z obrázku 11.1 . Výpis 11.3. Jednoduchý obrázek SVG popsaný jazykem JavaScript. <meta http-equiv=“Content-Type“ content=“text/html; charset=utf-8“ /> Ukázka SVG Ukázka SVG
<script type=“text/javascript“> // // // // // //
Metoda document.createElementNS vytváří nový element pomocí zadaného oboru názvů - v tomto případě se jedná o obor názvů SVG, na který se odkážete adresou URL. Podle druhého argumentu se vytváří element svg. Protože obory názvů jsou součástí jazyků XHTML a XML, zobrazí tento element analyzátor jazyka XML - to přesně potřebujete.
var svg = document.createElementNS(‘http://www.w3.org/2000/svg‘, ‘svg‘); svg.setAttribute(‘width‘, ‘1024‘); svg.setAttribute(‘height‘, ‘768‘); // Dynamicky vytvoří element circle a vloží jej do dříve // definovaného plátna <svg>. var circle = document.createElementNS(‘http://www.w3.org/2000/svg‘, ‘circle‘); circle.setAttribute(‘cx‘, ‘350‘); circle.setAttribute(‘cy‘, ‘120‘); circle.setAttribute(‘r‘, ‘50‘); circle.setAttribute(‘stroke‘, ‘#336699‘); circle.setAttribute(‘stroke-width‘, ‘25‘); circle.setAttribute(‘fill‘, ‘#003300‘); svg.appendChild(circle); // TODO: Vytvořit další vektorové elementy stejným způsobem. // Přidáním elementu svg ke stránce zobrazíte obrázek. document.body.appendChild(svg);
Kreslení pomocí jazyka VML V roce 1998 požádala společnost Microsoft konsorcium W3C, aby zvážilo jako doporučení jejich vlastní formát vektorové grafiky založený na jazyku na bázi s názvem VML (Vector Markup Language). Protože společnosti Adobe, Sun a další také požádaly o schválení svých podobných formátů, společnost W3C se rozhodla, že vytvoří vlastní formát pro všechny, z nějž se nakonec v roce 2002 stalo doporučení SVG, které jsme si popsali v předchozí části kapitoly. Vývojáři však mají smůlu,
K1743.indd 327
30.3.2010 10:16:35
328
Část III: Prezentace
protože společnost Microsoft toto doporučení SVG nepřijala (dokonce ani v prohlížeči IE 8) a neustále vylepšuje svůj vlastní formát VML. Jazyk VML podporují všechny prohlížeče Internet Explorer už od verze 5, žádné jiné prohlížeče jej však nepodporují. Univerzální knihovna vektorové grafiky by tedy musela podporovat jazyk SVG i VML, aby představovala přijatelné řešení pro vývojáře. Jazyk VML je výrazně podobný jazyku SVG a nabízí téměř shodné možnosti. Stručnější styl zápisu jazyka VML však vede k menší velikosti souborů než srovnatelný kód v jazyku SVG. Zajímavé je, že elementy jazyka VML lze pozicovat jazykem CSS a nemusí být uloženy v obklopujícím elementu, který funguje jako plátno. Elementy VML tak můžete přidávat přímo do stránky na libovolné místo. Výpis 11.4 ukazuje, jak zapsat obrázek z obrázku 11.1 do stránky HTML pomocí jazyka VML. Výpis 11.4. Jednoduchý obrázek VML definovaný uvnitř stránky HTML. <meta http-equiv=“Content-Type“ content=“text/html; charset=utf-8“ /> Ukázka VML <style>v\: * {behavior:url(#default#VML); display: inline-block;} <xml:namespace ns=“urn:schemas-microsoft-com:vml“ prefix=“v“ /> Ukázka VML
<span style=“font-size: 50px; font-weight: bold; color: #333333;“> Ukázkový text
K1743.indd 328
30.3.2010 10:16:35
Kapitola 11: Kreslení v prohlížeči
329
Vytváření dynamických grafů pomocí znovupoužitelné knihovny pro kreslení Viděli jste, jak moderní webové prohlížeče podporují vektorovou grafiku založenou na jazyku XML, ale prohlížeč IE používá jiný formát než ostatní prohlížeče. Abyste mohli ve svých webových aplikacích používat vektorovou grafiku, musíte najít jednoduchý způsob, jak ji definovat pro tyto dva odlišné modely. Často potřebujete knihovny jazyka JavaScript, které odstraňují rozdíly mezi prohlížeči, jak vysvětlovala kapitola 2. V tomto případě můžete použít knihovnu Raphaël, což je znovupoužitelná knihovna pro JavaScript, kterou naprogramoval Dmitry Barnovskiy. Raphaël zjednodušuje tvorbu vlastní vektorové grafiky, protože nabízí univerzální a jednoduché rozhraní API. Knihovna vybírá vhodnou techniku pro kreslení vektorové grafiky v závislosti na aktuálním webovém prohlížeči – SVG nebo VML. Knihovnu můžete stáhnout z domovských stránek projektu na internetové adrese http://raphaeljs.com/, kde rovněž najdete spoustu příkladů. Komprimovaná knihovna má přibližně 53 KB. Jak jste viděli, každý grafický objekt jazyka SVG a VML je také objektem modelu DOM, což znamená, že u něj můžete zpracovávat události nebo jej dynamicky upravit jako jakýkoliv jiný element stránky. Příklad – předpokládejme, že budete chtít koncovým uživatelům prezentovat data pomocí čárového grafu. Data se neustále mění – můžete třeba Ajaxem načítat nová data ze serveru každých 30 sekund. Když přijdou nová data, budete chtít změnit graf. Obrázek 11.2 ukazuje, co by se mohlo uživatelům zobrazit. Klepnutí na odkaz Získat nová data simuluje příjem nových dat ze serveru a aktualizuje graf. Po přejetí ukazatelem myši nad body grafu, zvýrazníte daný bod a ukážete přesnou hodnotu.
Obrázek 11.2. Dynamická aktualizace grafu.
K1743.indd 329
30.3.2010 10:16:35
330
Část III: Prezentace
Výpis 11.5 ukazuje, jak dynamicky vytvořit graf z obrázku 11.2 na stránce HTML, a to pomocí knihovny Raphaël, která zaručí kompatibilitu mezi prohlížeči. Nejprve vytvoříte prázdné plátno a potom nakreslíte mřížku a přidáte popisky x-ových a y-ových os. Potom zanesete data a zobrazíte mezi nimi čáry. Když uživatel klepne na tlačítko Získat nová data, prohlížeč náhodně vygeneruje nová data, smaže současné čáry a body z plátna a nahradí je novými. Výpis 11.5. Kreslení dynamického grafu pomocí knihovny Raphaël. Dynamický graf <meta http-equiv=“content-type“ content=“text/html;charset=utf-8“ /> Dynamický graf
<script type=“text/javascript“ src=“$.js“> <script type=“text/javascript“ src=“raphael.js“> <script type=“text/javascript“> // Definuje nový konstruktor pro kreslení grafu. var Graph = function(input) { // Očekává popisky pro x-ovou osu ve tvaru pole, samotné // hodnoty jako pole, šířku a výšku plátna a element, do // kterého přidáte toto plátno. this.labels = input.labels || []; this.data = input.data || []; this.width = input.width || 600; this.height = input.height || 300; this.element = input.element || $.Elements.create(„div“); // Vytvoří nové univerzální plátno pomocí knihovny // Raphaël. Vlastnost paper představuje objekt, na kterém // budete moct později volat metody pro změnu plátna. this.paper = Raphael(this.element, this.width, this.height); // Stanovení maximální hodnoty z pole dat, zaokrouhlené na // stovky - tato hodnota bude maximální hodnotou y-ové osy // grafu. this.maximumDataValue = Math.ceil(Math.max.apply(Math, this.data) / 100) * 100; // Metoda buildGrid kreslí mřížku na plátno včetně popisků
K1743.indd 330
30.3.2010 10:16:36
Kapitola 11: Kreslení v prohlížeči
331
// os a vrátí souřadnice x a y pozice mřížky (včetně místa // na popisky) a šířku a výšku mřížky (bez popisků). this.buildGrid = function() { // Definuje výšku a šířku pro popisky os. var xLabelHeight = 20, yLabelWidth = 20; // Stanoví souřadnice x a y mřížky a šířku a výšku mřížky // (bez popisků os). var x = yLabelWidth, y = 20; var width = this.width - yLabelWidth; var height = this.height - xLabelHeight - y; // Vypočítá, kolik čar mřížky v každém směru zobrazit. var horizLines = this.data.length * 2; var vertLines = (this.maximumDataValue / 10); // Mřížku nakreslí ve světle šedé barvě (barva #ccc) // pomocí knihovny Raphaël. this.paper.drawGrid(x, y, width, height, horizLines, vertLines, „#ccc“); // Funkce drawXLabels vytvoří a rozmístí popisky x-ové // osy grafu. var drawXLabels = function() { for (var index = 0, length = this.data.length; index < length; index++) { var x = yLabelWidth + ((index / this.data.length) * width) + (width/(2 * this.data.length)); var y = this.height - (xLabelHeight/2); // Knihovna Raphaël nakreslí text popisku na // plátno. this.paper.text(x, y, this.labels[index]).attr({ „font“: ‘10px „Arial“‘, stroke: „none“, fill: „#000“ }); } }.call(this); // Funkce drawYLabels vytvoří a umístí popisky y-ové // osy na základě dříve vypočítané maximální hodnoty, // zaokrouhlené na stovky - to vám umožní použít pěkně // zaokrouhlená čísla jako popisky os. var drawYLabels = function() { for (var index = 0, length = vertLines; index <= length; index++) { var labelText = (index * this.maximumDataValue) / vertLines; var labelPosition = height - (vertLines * index * height / this.maximumDataValue) + y; // Knihovna Raphaël nakreslí text popisky na // plátno. this.paper.text(yLabelWidth / 2, labelPosition, labelText).attr({ „font“: ‘10px „Arial“‘, stroke: „none“, fill: „#000“ });
K1743.indd 331
30.3.2010 10:16:36
332
Část III: Prezentace } }.call(this); // Vrátí souřadnice x a y počátku mřížky na plátně, // spolu se šířkou a výškou mřížky. return { x: x, y: y, width: width, height: height } } // Zavolá metodu buildGrid, návratovou hodnotu uloží // do vlastnosti grid. this.grid = this.buildGrid(); // Metoda drawPath zakreslí body a čáry do mřížky. this.drawPath = function() { // Když kreslíte čáru knihovnou Raphaël, vytvoříte cestu // a nastavíte ji šířku pomocí hodnoty stroke-width. var pathAttributes = { stroke: „#333“, „stroke-width“: 4, „stroke-linejoin“: „round“ }; this.path = this.paper.path(pathAttributes); // Vytvoří pole pro uložení odkazů na body, text // a tvary, které se chystáte nakreslit do mřížky. this.points = []; this.text = []; this.rects = []; // Prochází jednotlivé položky pole dat. for (var index = 0, length = this.data.length; index < length; index++) { // Vypočítá souřadnice x a y pozice bodu ve // mřížce, který bude odpovídat aktuální položce. var x = this.grid.x + (index * (this.grid.width / this.data.length)) + (this.grid.width / (2 * this.data.length)); var y = this.grid.y + this.grid.height (this.data[index] * this.grid.height / this.maximumDataValue); // První položka bude mít bod, do kterého nepovede // čára. K ostatním bodům povedou čáry. if (index == 0) { this.path.moveTo(x, y, 10); } else { this.path.lineTo(x, y, 10); } // // // //
K1743.indd 332
Funkce drawPoints zakreslí body do mřížky, ke všem bodům přidá popisky, které se zobrazí, když uživatel najede ukazatelem myši nad neviditelnou obdélníkovou oblast okolo něj. Díky tomu nemusí
30.3.2010 10:16:36
Kapitola 11: Kreslení v prohlížeči
333
// uživatel vyhledávat malé body ve mřížce, aby // uviděl s nimi spojené textové popisky. var drawPoints = function(){ // Do mřížky nakreslí neviditelný obdélník // okolo bodu představujícího aktuální hodnotu. // Hodnota průhlednosti 0 učiní obdélník // neviditelným, ale stále zůstane v mřížce. var rect = this.paper.rect(this.grid.x + (this.grid.width * index / this.data.length), this.grid.y, (this.grid.width * (index + 1) / this.data.length), this.grid.height) .attr({stroke: „none“, fill: „#fff“, opacity: 0}); // Přidá objekt obdélníku do pole. this.rects.push(rect); // Nakreslí bod představující aktuální // hodnotu do mřížky a přidá jej do pole // všech bodů. var point = this.paper.circle(x, y, 5).attr({ ‘fill‘: „#333“ }); this.points.push(point); // Nakreslí textový popisek nad bod a přidá // jej do pole popisků. var text = this.paper.text(x, y - 15, this.data[index]) .attr({ „font“: ‘10px „Arial“‘, stroke: „none“, fill: „#000“ }); this.text.push(text); // Protože elementy XML, které představují // tvary nebo objekty na plátně, lze ovlivnit // modelem DOM, můžete použít metody modelu DOM, // například insertAfter jako u elementů HTML. text.insertAfter(dot); // Textový popisek schováte. text.hide(); // K elementům plátna můžete přidávat události, // protože se chovají stejně jako běžné // elementy HTML. Pomocí knihovny Raphaël dynamicky // změníte barvu a velikost bodu a zobrazíte // textový popisek, když uživatel přesune // ukazatel myši na neviditelný obdélník // obklopující daný bod. rect.mouseover(function(){ point.attr({ „fill“: „#999“, „r“: 7 }); text.show(); }); // Pokud uživatel přesune ukazatel myši mimo
K1743.indd 333
30.3.2010 10:16:36
334
Část III: Prezentace // obdélník obklopující bod, opět schováte text // a bodu vrátíte původní velikost a barvu. rect.mouseout(function(){ point.attr({ „fill“: „#333“, „r“: 5 }); text.hide(); }); }.call(this) } } // Zavolá právě definovanou metodu drawPath. this.drawPath(); // Metoda replaceData odstraní vektory z mřížky, mřížku // ponechá netknutou, takže do ní můžete vložit nová data. this.replaceData = function(data) { // Nová data předáte metodě jako argument a současná // data nahradíte těmito novými daty. this.data = data; // Odstraní čáru ze mřížky. this.path.remove(); // Body se odstraní z mřížky jeden po druhém. for (var index = 0, length = this.points.length; index < length; index++) { this.points[index].remove(); } // Z mřížky odstraní textové popisky. for (var index = 0, length = this.text.length; index < length; index++) { this.text[index].remove(); } // Odstraní neviditelné obdélníky z mřížky. for (var index = 0, length = this.rects.length; index < length; index++) { this.rects[index].remove(); } // Provede metodu drawPath, která opět nakreslí čáru, // body a textové popisky do mřížky – tentokrát // použije nová data. this.drawPath(); } } // Vytvoří objekt třídy Graph, předá mu data, popisky // a element modelu DOM, do kterého se umístí nový graf. var myGraph = new Graph({ labels: [2004, 2005, 2006, 2007, 2008, 2009], data: [0, 47, 32, 100, 78, 89], element: document.getElementById(„canvas“) });
K1743.indd 334
30.3.2010 10:16:37
Kapitola 11: Kreslení v prohlížeči
335
// Do stránky vloží tlačítko „Získat nová data,“ když na // něj uživatel klepne, vygenerujete novou sadu náhodných // dat. To simuluje získání nových skutečných dat ze serveru // Ajaxem. var getNewData = $.Elements.create(„a“); getNewData.innerHTML = „Získat nová data“; document.getElementById(„button“).appendChild(getNewData); $.Events.add(getNewData, „click“, function(e) { e.preventDefault(); // Generuje novou sadu náhodných dat, všechny hodnoty // budou z rozmezí 0 až 100. var data = []; for (var index = 0, length = 5; index < length; index++) { data.push(Math.round(Math.random() * 100)); } // Zavolá metodu replaceData objektu třídy Graph, která // zobrazí novou sadu dat do mřížky. myGraph.replaceData(data); });
Měli byste se seznámit se všemi funkcemi, které tato knihovna nabízí. Můžete je použít, kdykoliv budete zobrazovat vektorovou grafiku dynamicky ve svých RIA aplikacích.
Značka