Я только что закончил интеграцию 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/