Vaadin + JFreeChart i dlaczego nie GAE

Zadanie na dziś wieczór… integrujemy Vaadin z JFreeChart i próbujemy wrzucić to na Google Apps Engine.

Pierwsza część jest banalnie prosta, a druga niemożliwa. Dlaczego? By odpowiedzieć na to pytanie musimy sięgnąć do pierwszego postu poświęconego Vaadin. Zacznę zatem od tyłu czyli tak zwanej „dupy strony”. Poza tym muszę się wyżalić na złego wujka Googla.

GAE wszystko fajnie, ale…

… ale jak zawsze coś nie tak. Wystarczy rzucić okiem na listę dozwolonych klas. Nadaremnie szukać tam BufferedImage i innych potrzebnych w aplikacji Vaadin i JFreeChart do generowania obrazków. Czyli niestety nie da się 🙁 jest kilka zgłoszeń na dodanie do listy klas awt czy swinga. Jednak zanim wujek Google wpadnie na ten pomysł to moje dzieci będą doktorat robiły.
Zatem mamy z głowy pierwszą rzecz. Vaadin ze swoimi dynamicznymi obrazkami nie ruszy na GAE. Przejdźmy do ciekawszego fragmentu…

Vaadin i JFreeChart

Na początek, dla odmian, trochę ponarzekam na JFreeChart. Nie wiem co jest, ale coś jest spartolone w designe tej biblioteki. Generalnie każdy rodzaj wykresu przyjmuje parametry we własnym DataSource i nie za bardzo idzie spłodzić coś co będzie wstanie wyprodukować jakiś wspólny typ. No cóż… nie z takimi rzeczami ludzie radzieccy sobie radzili. Generalnie dobrze mieć na podorędziu jakiś interfejs fabrykujący dane na różne sposoby.
Jeszcze lepiej mieć jakiś własny komponent do pobierania danych, który będzie wstanie wyprodukować je na różne sposoby. Do tego oczywiści przydają się jakieś fajne interfejsy. Poniżej dwa, które dostarczają danych do wykresu kołowego i wykresu słupkowego.

Listing 1. Interfejsy dostarczające danych

package pl.koziolekweb.vaadin.chart;

import org.jfree.data.general.DefaultPieDataset;

public interface PieChartDataSource {

	public DefaultPieDataset getPieDataset();

}
//...
package pl.koziolekweb.vaadin.chart;

import org.jfree.data.category.CategoryDataset;

public interface VerticalBarChartDataSource {

	CategoryDataset getCategoryDataset();

}

Ich implementacja załóżmy, że jest. Jakaś. W postaci komponentu Vaadin najlepiej. Samego komponentu nie przedstawię, bo jest różny w zależności od potrzeb klienta (jak potrzebujesz to pisz). Generalnie można ciągnąć dane na wiele różnych sposobów. XML, baza, ręczne wprowadzanie przez klienta, jako np. pliki CSV, ods, xls.
Co ciekawe im mniej GUI tym prościej. Trochę dlatego, że nie trzeba kombinować z walidatorami. Trochę dlatego, że więcej jest przykładów w necie pokazujących różne rodzaje integracji z JDBC/JPA. Do pom.xml dorzucamy jara z JFreeChart i można już jechać.

Wystarczy tylko to spiąć w jeden prosty program…

Listing 2. Przykładowy programik

package pl.koziolekweb.vaadin.chart;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DefaultPieDataset;

import pl.koziolekweb.vaadin.chart.pie.SimplePieChartDataInputCompisite;

import com.vaadin.Application;
import com.vaadin.terminal.StreamResource;
import com.vaadin.terminal.StreamResource.StreamSource;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.Window;

@SuppressWarnings("serial")
public class MyVaadinApplication extends Application {
	private Window window;
	private SimplePieChartDataInputCompisite dataSource;
	private Button generate;
	private Embedded imagePie;
	private Embedded imageBar;

	private StreamResource createStreamResource(StreamResource.StreamSource imagesource) {
		return new StreamResource(imagesource, "myimage.png", this);
	}

	@Override
	public void init() {
		window = new Window("My Vaadin Application");
		setMainWindow(window);
		dataSource = new SimplePieChartDataInputCompisite();
		window.addComponent(dataSource);
		generate = new Button("Generuj");
		generate.addListener(new ClickListener() {

			public void buttonClick(ClickEvent event) {
				makePie();
				makeBar();
			}

			private void makeBar() {
				StreamResource.StreamSource bar = new BarChart(dataSource.getCategoryDataset());
				StreamResource barimageresource = createStreamResource(bar);
				if (imageBar != null) {
					window.removeComponent(imageBar);
				}
				imageBar = new Embedded("Bar Image", barimageresource);
				window.addComponent(imageBar);
			}

			private void makePie() {
				StreamResource.StreamSource pie = new PieChart(dataSource.getPieDataset());
				StreamResource imageresource = createStreamResource(pie);
				if (imagePie != null) {
					window.removeComponent(imagePie);
				}
				imagePie = new Embedded("Pie Image", imageresource);
				window.addComponent(imagePie);
			}
		});
		window.addComponent(generate);
	}

}

class PieChart implements StreamSource {
	private ByteArrayOutputStream imagebuffer = null;
	private DefaultPieDataset dataset;

	public PieChart(DefaultPieDataset dataset) {
		this.dataset = dataset;
	}

	public InputStream getStream() {
		JFreeChart chart = ChartFactory.createPieChart3D("Przykład", dataset, false, false, false);
		BufferedImage image = chart.createBufferedImage(300, 300);
		try {
			imagebuffer = new ByteArrayOutputStream();
			ImageIO.write(image, "png", imagebuffer);

			return new ByteArrayInputStream(imagebuffer.toByteArray());

		} catch (IOException e) {
			return null;
		}

	}
}

class BarChart implements StreamSource {
	private ByteArrayOutputStream imagebuffer = null;
	private CategoryDataset dataset;

	public BarChart(CategoryDataset dataset) {
		this.dataset = dataset;
	}

	public InputStream getStream() {
		JFreeChart chart = ChartFactory.createBarChart("Bar Chart Demo", "kategorie", "Wartości", dataset,
				PlotOrientation.VERTICAL, false, false, false);
		BufferedImage image = chart.createBufferedImage(300, 300);
		try {
			imagebuffer = new ByteArrayOutputStream();
			ImageIO.write(image, "png", imagebuffer);

			return new ByteArrayInputStream(imagebuffer.toByteArray());

		} catch (IOException e) {
			return null;
		}

	}
}

Podsumowanie

Jak widać całość jest stosunkowo prosta. Obecnie pracuję nad zestawem uniwersalnych komponentów pozwalających na umieszczanie różnego typu wykresów w aplikacji. Będzie to dość proste rozwiązanie w rodzaju klas BarChart i PieChart wzbogacone jednak o jakieś dodatkowe featury w stylu przyciski „generuj” czy uploader danych dla plików. Pożyjemy zobaczymy…

ps. 8 października ukazał się Maven 3. Coś cicho jest o tym w Polsko-Javaowej blogosferze… Od jutra testuję nowego mavena.

ps2. Dla masochistów pozostawiam odkrywanie uroków spinania powyższego z EJB3.1. Da się, ale dobre oznacza niebanalne.

2 myśli na temat “Vaadin + JFreeChart i dlaczego nie GAE

  1. Witam,

    Śledzę twoje wpisy odnośnie Vaadina i widzę, że rozpoznajesz go nie tylko pod kątem bloga, ale (sądząc po tekście) używasz komercyjnie. Mam pytanie odnośnie wydajności tego rozwiązania. Chodzi mi szczególnie o obsługę wszystkich zdarzeń po stronie serwera. Wiem, że Vaadin potrafi je kolejkować i wysyłać w „odpowiednim” momencie, ale ciekawi mnie jak duży ma to wpływ na wydajność i „responsywność” bardziej rozbudowanego systemu.

  2. @Klacia, zdefiniuj „bardziej rozbudowany system”. Jeżeli mówimy o np. tworzeniu wykresów tak jak tutaj to przy trzech komponentach i 10 usera na krzyż nawet nie zauważysz, że coś się tnie. Jeżeli będziesz ściągał np. notowania giełdowe z WGPW, nie będziesz kombinował za bardzo i zrobisz to w rozsądny sposób (jeden serwis na aplikację, a nie usera) to będzie śmigać o ile zrównoważysz sobie serwer tak jak bozia przykazała proporcjonalnie do ilości userów. Jeżeli jednak będziesz chciał zrobić sobie np. społecznościówkę na samym Vaadin to nie wróżę przyszłości.

    Generalnie Vaadin dostarcza GUI i tylko to powinien robić. Na pewnym poziomie skomplikowania i rozrostu można ograniczyć część w Vaadin do klienta kilku kolejek MQ, który pozwala na podglądanie ich zawartości plus jakieś duperelne podglądanie bazy danych plus wstawianie jakiś wartości jeżeli już musisz robić to ręcznie. Cała logika przechodzi wtedy do normalnych rozwiązań typu Spring, EJB. W takim wypadku kolejkowanie po stronie Vaadin jest tylko czymś co pozwala na uproszczenie aplikacji i „automagicznie” rozwiązuje problemy z komunikacją.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax