Я только что закончил интеграцию JFreeChart с Tapestry . Каждый раз, когда вы интегрируете библиотеку с Tapestry, вы полны похвалы за каркас. Это то, что вы редко можете сказать о других веб-фреймворках.
Я имел в виду два использования.
- В качестве возвращаемого значения из обработчика событий.
- В качестве компонента, который может быть использован для отображения JFreeChart и соответствующей карты изображений
Для отображения диаграммы недостаточно только экземпляра JFreeChart, вам также необходимо знать ширину и высоту изображения. Также в зависимости от типа формата изображения, который вы собираетесь использовать, требуется дополнительная информация, например, в случае качества формата JPEG.
Поэтому мы создаем абстрактную модель данных для хранения информации в одном месте.
public abstract class ChartModel { private JFreeChart chart; private int width; private int height; private ChartRenderingInfo info; public ChartModel(JFreeChart chart, int width, int height) { this.chart = chart; this.width = width; this.height = height; info = new ChartRenderingInfo(new StandardEntityCollection()); } public JFreeChart getChart() { return chart; } public int getWidth() { return width; } public int getHeight() { return height; } public ChartRenderingInfo getInfo() { return info; } public abstract String getFormat(); public abstract String getContentType(); }
Затем мы создаем конкретный класс для каждого конкретного формата изображения.
public class JPEGChartModel extends ChartModel { private float quality; public JPEGChartModel(JFreeChart chart, int width, int height, float quality) { super(chart, width, height); this.setQuality(quality); } @Override public String getFormat() { return "jpeg"; } @Override public String getContentType() { return "image/jpeg"; } public void setQuality(float quality) { this.quality = quality; } public float getQuality() { return quality; } } public class PNGChartModel extends ChartModel { private boolean encodeAlpha; private int compression; public PNGChartModel(JFreeChart chart, int width, int height, boolean encodeAlpha, int compression) { super(chart, width, height); this.setEncodeAlpha(encodeAlpha); this.setCompression(compression); } @Override public String getFormat() { return "png"; } @Override public String getContentType() { return "image/png"; } public void setEncodeAlpha(boolean encodeAlpha) { this.encodeAlpha = encodeAlpha; } public boolean isEncodeAlpha() { return encodeAlpha; } public void setCompression(int compression) { this.compression = compression; } public int getCompression() { return compression; } }
Изображение записывается в выходной поток с помощью службы рендеринга ChartWriter.
public interface ChartWriter { public void writeChart(OutputStream out, ChartModel chartModel) throws IOException; } public class ChartWriterImpl implements ChartWriter { private ChartRenderer renderer; public ChartWriterImpl(ChartRenderer renderer) { this.renderer = renderer; } public void writeChart(OutputStream out, ChartModel chartModel) throws IOException { renderer.render(chartModel, chartModel.getChart(), out); } }
Реализация делегирует рендеринг сервису ChartRenderer, который является сервисом стратегии, основанным на типе класса
public interface ChartRenderer { void render(ChartModel chartModel, JFreeChart jfreeChart, OutputStream out) throws IOException; } //JPEG Chart Renderer public class JPEGChartRenderer implements ChartRenderer { public void render(ChartModel chartModel, JFreeChart jfreeChart, OutputStream out) throws IOException { JPEGChartModel jpegChart = (JPEGChartModel) chartModel; ChartUtilities.writeChartAsJPEG(out, jpegChart.getQuality(), jfreeChart, chartModel.getWidth(), chartModel.getHeight(), jpegChart.getInfo()); } } //PNG Chart Renderer public class PNGChartRenderer implements ChartRenderer { public void render(ChartModel chartModel, JFreeChart jfreeChart, OutputStream out) throws IOException { PNGChartModel pngChart = (PNGChartModel) chartModel; ChartUtilities.writeChartAsPNG(out, jfreeChart, chartModel.getWidth(), chartModel.getHeight(), pngChart.getInfo(), pngChart.isEncodeAlpha(), pngChart.getCompression() ); } }
Теперь, когда базы покрыты, давайте перейдем к двум обычаям, которые мы запланировали.
В качестве возвращаемого значения из обработчика событий.
Это можно сделать, добавив ComponentEventResultProcessor для обработки ChartModel в качестве возвращаемого значения.
public class ChartResultProcessor implements ComponentEventResultProcessor<ChartModel> { public Response response; private ChartWriter chartWriter; public ChartResultProcessor(Response response, ChartWriter chartWriter) { this.response = response; this.chartWriter = chartWriter; } public void processResultValue(ChartModel chartModel) throws IOException { response.disableCompression(); OutputStream out = response.getOutputStream(chartModel.getContentType()); chartWriter.writeChart(out, chartModel); response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); } }
ChartResultProcessor делегирует отрисовку диаграммы ChartWriter.
Компонент для отображения JFreeChart и соответствующей карты изображений
Поскольку для отображения диаграммы в разных графических форматах требуются разные параметры, мы создаем абстрактный класс. Любой абстрактный класс компонента muct должен быть помещен в $ {application-package} .base. AbstractChart реализован в виде
@Import(library = "chart.js") public abstract class AbstractChart implements ClientElement { @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL, allowNull = false) private String clientId; @Parameter(required = true, allowNull = false) private JFreeChart chart; @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false) private int width; @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false) private int height; @Parameter private Object[] context; @Parameter(defaultPrefix = BindingConstants.LITERAL) private String zone; @Parameter private ToolTipTagFragmentGenerator toolTipTagGenerator; @Parameter(value = "false", defaultPrefix = BindingConstants.LITERAL) private boolean useMap; ToolTipTagFragmentGenerator defaultToolTipTagGenerator() { return new StandardToolTipTagFragmentGenerator(); } @Inject private ComponentResources resources; @Inject private JavaScriptSupport javaScriptSupport; @Inject private ChartWriter chartWriter; private String assignedClientId; private ChartModel internalChart; void setupRender() { assignedClientId = javaScriptSupport.allocateClientId(clientId); } boolean beginRender(MarkupWriter writer) { // Outer Div writer.element("div", "id", getClientId()); // Write image tag writer.element("img", "src", getImageURL()); // Add map if required if(useMap) { writer.attributes("useMap", "#" + getMapName()); } writer.end(); // Close img tag String selectMapURL = getSelectMapURL(); if(useMap) { createChart(); initializeChart(); writer.writeRaw(ChartUtilities.getImageMap(getMapName(), internalChart.getInfo(), toolTipTagGenerator, getURLTagGenerator(selectMapURL))); } writer.end();// Close Outer Div if(zone != null) { addJavaScript(selectMapURL); } return false; } private String getSelectMapURL() { return resources.createEventLink(ChartConstants.SELECT_MAP, context).toAbsoluteURI(); } private URLTagFragmentGenerator getURLTagGenerator(final String url) { return new URLTagFragmentGenerator() { public String generateURLFragment(String text) { String[] parts = text.split("\\?"); return String.format("href='%s?%s'", url, parts[1]); } }; } private String getImageURL() { return resources.createEventLink(ChartConstants.SHOW_CHART, context).toAbsoluteURI(); } private String getMapName() { return getClientId() + "_map"; } public String getClientId() { return assignedClientId; } private void addJavaScript(String url) { JSONObject params = new JSONObject(); params.put("zone", zone); params.put("id", getMapName()); params.put("url", url); javaScriptSupport.addInitializerCall("mapToZone", params); } @OnEvent(ChartConstants.SHOW_CHART) Object showChart() { createChart(); return internalChart; } private void createChart() { internalChart = createChart(chart, width, height); } private void initializeChart() { OutputStream out = new DummyOutputStream(); try { chartWriter.writeChart(out, internalChart); } catch(IOException e) { throw new RuntimeException("Could not write chart : ", e); } } protected abstract ChartModel createChart(JFreeChart chart, int width, int height); }
Есть один взлом. Карта изображения генерируется ChartRenderingInfo, которую можно получить только после создания диаграммы. Таким образом, мы должны создать диаграмму дважды, один раз на этапе рендеринга, чтобы получить карту, и другой на этапе действия для рендеринга диаграммы. Поскольку вывод не требуется на этапе рендеринга, мы используем DummyOutputStream.
public class DummyOutputStream extends OutputStream { @Override public void write(int b) throws IOException { } }
Следует также отметить, что области в imagemap должны быть связаны с зоной (если она есть). Это делается по сценарию
Tapestry.Initializer.mapToZone = function(spec) { $A($(spec.id).childNodes).each(function(e) { if(e.tagName == "AREA") { Event.observe($(e), "click", function(event){ event.preventDefault(); var zoneManager = Tapestry.findZoneManagerForZone(zone); if(zoneManager != null) { zoneManager.updateFromURL(e.href); } }); } }); };
JPEGChart & PNGChart наследует большую часть функциональности от AbstractChart.
public class JPEGChart extends AbstractChart { @Parameter(value = "0.9", defaultPrefix = BindingConstants.LITERAL, allowNull = false) private float quality; @Override protected ChartModel createChart(JFreeChart chart, int width, int height) { return new JPEGChartModel(chart, width, height, quality); } } public class PNGChart extends AbstractChart { @Parameter(value = "false", defaultPrefix = BindingConstants.LITERAL, allowNull = false) private boolean encodeAlpha; @Parameter(value = "0", defaultPrefix = BindingConstants.LITERAL, allowNull = false) private int compression; @Override protected ChartModel createChart(JFreeChart chart, int width, int height) { return new PNGChartModel(chart, width, height, encodeAlpha, compression); } }
И, наконец, класс модуля
public class ChartModule { public static void bind(ServiceBinder binder) { binder.bind(ChartWriter.class, ChartWriterImpl.class); } public ChartRenderer buildChartRender(StrategyBuilder builder, @SuppressWarnings("rawtypes") Map<Class, ChartRenderer> chartRenderers) { return builder.build(ChartRenderer.class, chartRenderers); } @Contribute(ComponentEventResultProcessor.class) public void provideResultProcessors( @SuppressWarnings("rawtypes") MappedConfiguration<Class,ComponentEventResultProcessor> configuration) { configuration.addInstance(ChartModel.class, ChartResultProcessor.class); } @Contribute(ChartRenderer.class) public void provideChartRenderers( @SuppressWarnings("rawtypes") MappedConfiguration<Class, ChartRenderer> configuration) { configuration.addInstance(JPEGChartModel.class, JPEGChartRenderer.class); configuration.addInstance(PNGChartModel.class, PNGChartRenderer.class); } }
ChartConstants реализован как
public class JPEGChart extends AbstractChart { @Parameter(value = "0.9", defaultPrefix = BindingConstants.LITERAL, allowNull = false) private float quality; @Override protected ChartModel createChart(JFreeChart chart, int width, int height) { return new JPEGChartModel(chart, width, height, quality); } }
Полный исходный код здесь
От http://tawus.wordpress.com/2011/07/30/tapestry-jfreechart-integration/