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.