2012. január 29.

Adat vizualizáció processing-gel

A Számítógépes Nyelvészeten korábban már szóba került a processing programozási környezet, amelyet azért terveztek, hogy egyszerűen és hatékonyan lehessen vizuális alkalmazásokat készíteni: egy java-ra épülő (pontosabban, a java alá épülő) nyelvről van szó, amely a számos grafikus megjelenítéssel kacsolatos feladatot megkönnyít a beépített funciók segítségével.


Mint már akkor Zoli elmondta, nem egy játékról van szó, hanem egy igen hatékony eszközről; ugyanezt Ben Fry (a nyelv egyik atyja), Visualizing Data c. könyvében úgy fogalmazza meg, hogy kész eszközök helyett a processing építőkockákat ad -- egyedi problémákhoz ugyanis szinte lehetetlen előre legyártott szabványos megoldásokat kitalálni.

A processing építőkockái valójában nem mások, mint a beépített funkciók -- valójában mindent a Java programnyelv végez, így nem is meglepő, hogy a szintaxis megegyező; és az sem, hogy bármikor "kinyúlhatunk" a processing nyelvből a Java funkcióihoz.

Mit szeretnénk tehát? A processing két dologban jó: a vizualizációban és az interaktivitásban; tehát -- hogy újra feltaláljuk a spanyolviaszt -- készítünk egy primitív tag-felhőt. A megoldás annyiban nem lesz szabványos, hogy kezdetben minden tag-et azonos méretben fogunk megjeleníteni; csak utóbb, a az egér mozgatásával dönti el a felhasználó, hogy mennyire szeretné "kiugrasztani" a gyakran előforduló elemeket. Tehát, lesz egy "áttekintő nézetünk" és egy "közelítő nézetünk".



 Áttekintő nézet -- minden szó egyforma méretű

  
A gyakori szavakat kiemeltük


OK. A legegyszerűbb processing program két funkcióból áll: a setup() és a draw(). A setup() induláskor fut le egyszer, mint neve is mutatja, itt érdemes elhelyezni minden kezdetben szükséges lépést. Pl., mi itt állítjuk be a rajzfelület méretét (800x600), itt töltjük be a betűtípust és itt olvassuk be az adat.txt-ből a szavakat, amelyeket majd később ki szeretnénk írni a képernyőre.

Ezután a draw() funkció fut le. Ez, mint neve is mutatja, alapvetően a megjelenítésért felel, de természetesen bármi mást is elhelyezhetünk itt. Kiemelt szerepe abban áll, hogy folyamatosan ismétlődik, vagyis miután a program mindent kirajzolt, azonnal újra kezdi. Hogy mire jó ez? Ha úgy képzeljük el a dolgok, mint az egymást követő filmkockákat, egyből értelmet nyer a dolog: ha a két kocka közt nincs eltérés, akkor nem veszünk észre semmit. Azonban ha van, akkor mozgásba lendül a kép. Pl., ha egy kirajzolt pont koordinátáit minden egyes funkció-híváskor megnöveljük egyel (x++;), akkor egy mozgó pont lesz az eredmény. A processing környezet tehát azt a könnyedséget adja, hogy nem kell magunknak megírnunk a "filmkockák kirajzolásáért" felelős részt, elég csak a képekkel foglalkoznunk.

Ezen a ponton érdemes megjegyezni, hogy a processing komolyan veszi a globális és lokális változók kérdését. Pl. a draw() funkción belül deklarált változók minden egyes újrahíváskor elvesznek, így ha pl. a mozgó képpont x koordinátáját az x változóban tárolnánk, azt mindenképpen globálisan (vagyis a funkción kívül) kell először deklarálnunk. A programunkban pl. ilyen az adat[], meret_szamlalo vagy a lastMX.

Az interaktivitás beépítését a processing azzal támogatja, hogy nagyon egyszerűvé teszi teszi a különböző események figyelését. Pl. minden alkalommal, amikor kattintunk, a mousePressed() funkció kerül meghívásra, majd visszatér a program oda, ahol abbahagyta a futást. Ugyanez a történik a mouseMoved() funkció esetén is. Az egérkurzor koordinátáit a mouseX ill. a mouseY változókból tudhatjuk meg.

Lássuk tehát, mi történik az interaktív szövegfelhőnk kódjában. Először deklarálunk néhány globális változót (mivel azt szeretnénk, hogy minden funkció elérje ezeket). Ezután elindul a setup(), ami betölti az adat.txt tartalmát (itt a szavak és a hozzájuk tartozó értékek találhatók).

Végül elindul a draw(), amely folyamatosan ismétlődik a program leálltáig. Először kirajzoljuk az "irányító felületet" -- ez az a része a képernyőnek, amelyen ha megmozdítjuk az egeret, akkor változik a szavak mérete. Majd kiszámoljuk (egy gyökvonással), hogy hány sorba és hány oszlopba rendezzük az adatainkat. Ezután elindítunk egy ciklust, amely szép sorban kiírja megfelelő helyekre az adat[] tömbben tárolt szavakat. Ha a szó maximális mérete nagyobb, mint az aktuálisan beállított szövegméret, akkor az utóbbit használjuk. Ha az aktuális szövegméret nagyobb, akkor a szó maximális méretét használjuk.

Az utolsó két funkció az egér mozgatását és az egérkattintást figyeli. Ha megmozdítjuk az egeret ÉS a kurzor a "kezelőfelület" felett áll, akkor növeljük vagy csökkentjük a szöveg méretét (attól függően, hogy jobbra vagy balra húztuk az egeret). Ha kattintunk, akkor egy új szöveg-elrendezést kérünk -- ezt egyszerűen úgy érjük el, hogy megkeverjük az adat[] tömböt.


PFont f;
float szorzo=1.5; // ez a szam hatassal van a szoveg meretere
String[] adat = new String[100]; // itt taroljuk a szavakat
int[] meret = new int[100]; // itt taroljuk a szavakhoz tartozo gyakorisag-szamokat
void setup() {
size(800, 600);
smooth();
f = loadFont("FreeSerifBold-48.vlw");
// betoltjuk az adatokat es megkeverjuk
adat = loadStrings("adat.txt");
Collections.shuffle(Arrays.asList(adat));
// szetbontjuk a szora es a meretre
for (int i=0; i<adat.length;i++) {
String temp2[]=split(adat[i],"\t");
adat[i]=temp2[0];
meret[i]=int(temp2[1]);
}
}
// errol a szovegmeretrol kezdjuk a kirajzolast
int meret_szamlalo=18;
void draw() {
background(0);
// itt rajzoljuk ki az iranyito-teruletet
fill(255);
textFont(f,66);
rect(60,500,300,50);
text("<",40,540);
text(">",380,540);
fill(255,55,179);
//kiszamoljuk, hany sor es hany oszlop kell (tor mint tores)
int tor=floor(sqrt(adat.length))+1;
//kiszamoljuk a szavak kozti tavolsagot
int tavX=width/(tor+1);
int tavY=height/(tor+1);
textAlign(CENTER);
// kirajzoljuk a szavakat
for(int i=1;i<=tor;i++) {
for(int c=1;c<=tor;c++) {
int index=(c-1)*tor+(i-1);
if ( index <= adat.length-1) {
// ha az aktulisan beallitott szovegmeret nem nagyobb, mint a szo merete, akkor azzal rajzoljuk ki
if(meret_szamlalo < meret[index]) {
textFont(f,meret_szamlalo*szorzo);
}
// ha elerte a maximalis meretet, akkor annal maradunk
else {
textFont(f,meret[index]*szorzo);
}
//es vegul kiirjuk
text(adat[index],tavX*i,tavY*c);
}
}
}
}
// ez a funkcio figyeli, hogy modositunk-e a szavak mereten az eger mozgatasaval (az iranyito feluleten)
int lastMX,lastMY;
void mouseMoved() {
if (mouseX > 60 && mouseY > 500 && mouseX < 360 && mouseY < 550) {
if (mouseX > lastMX) {
meret_szamlalo=meret_szamlalo+8;
}
if (mouseX < lastMX) {
meret_szamlalo=meret_szamlalo-8;
}
}
lastMX=mouseX;
lastMY=mouseY;
}
// kattintassal tudjuk megkeverni a szavakat
void mousePressed() {
Collections.shuffle(Arrays.asList(adat));
}
view raw felho2.pde hosted with ❤ by GitHub


Végül néhány megjegyzés:

1. A virtuális gép és a grafikus felület sajnos lelassítja a program futását. Nagyon. Ezért az egymásba ágyazott ciklusokkal, nagy tömbökkel és minden hasonlóval csak nagyon óvatosan szabad bánni. Szerencsére a bonyolultabb grafikus műveletekhez vannak beépített funkciók.

2. A Java típusai sokkal finnyásabbak, mint mondjuk a python-éi. A float nem változik automatikusan integerré, a string-et nem lehet csak úgy pikk-pakk karakterekké szedni, stb... A tömbök sem túl rugalmasak: deklaráláskor el kell döntenünk, milyen hosszúak legyenek, tehát nem tudjuk őket a végtelenségig bővíteni. Többdimenziós tömbök léteznek, de egy tömbnek csak egy eleme már nem lehet egy újabb tömb. Dictionary/hash típus nincsen.

4. A programok könnyen konvertálhatók java appletté, illetve az processing.js javascript könyvtár segítségével közvetlenül böngészőből is futtathatók.

3. Az oktató anyagok és a dokumentáció jók; sok kiegészítő könyvtár is készült már.


Folyt. köv.

1 megjegyzés:

Gerő Dávid írta...

Tetszik a példa program ötlete! Hasznos és inspiratív! - Újra megjött a kedvem a processinghez ;)