📄 simplechartengine.java
字号:
/* SimpleChartEngine.java
{{IS_NOTE
Purpose:
Description:
History:
Tue Aug 1 10:30:48 2006, Created by henrichen
}}IS_NOTE
Copyright (C) 2006 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under GPL Version 2.0 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul.impl;
import org.zkoss.zul.*;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.image.AImage;
import org.zkoss.util.TimeZones;
import org.zkoss.lang.Strings;
import org.zkoss.lang.Objects;
import org.jfree.chart.*;
import org.jfree.chart.encoders.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.entity.*;
import org.jfree.data.general.*;
import org.jfree.data.category.*;
import org.jfree.data.xy.*;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.util.TableOrder;
import java.util.List;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.TimeZone;
import java.io.ByteArrayOutputStream;
import java.awt.image.BufferedImage;
import java.awt.Paint;
import java.awt.Color;
import java.util.Iterator;
/**
* A Facade Chart engine implemented with JFreeChart.
*
* <p>This is the JFreeChart base chart engine implementation. All chart would
* support drilldown by providing Area hot spots. Each Area would callback to
* {@link org.zkoss.zul.event.ChartAreaListener} class that application developers
* can do processing on each area.</p>
*
* @author henrichen
*/
public class SimpleChartEngine implements ChartEngine {
//as long as the series name is not set
private static final String DEFAULT_HI_LO_SERIES = "High Low Data";
//caching chartImpl if type and 3d are the same
private boolean _threeD;
private String _type;
private ChartImpl _chartImpl; //chart implementaion
/**
* create specific type of chart drawing engine. This implementation use
* JFreeChart engine.
*/
private ChartImpl getChartImpl(Chart chart) {
if (Objects.equals(chart.getType(), _type) && _threeD == chart.isThreeD()) {
return _chartImpl;
}
if (Chart.PIE.equals(chart.getType()))
_chartImpl = chart.isThreeD() ? new Pie3d() : new Pie();
else if (Chart.RING.equals(chart.getType()))
_chartImpl = new Ring();
else if (Chart.BAR.equals(chart.getType()))
_chartImpl = chart.isThreeD() ? new Bar3d() : new Bar();
else if (Chart.LINE.equals(chart.getType()))
_chartImpl = chart.isThreeD() ? new Line3d() : new Line();
else if (Chart.AREA.equals(chart.getType()))
_chartImpl = new AreaImpl();
else if (Chart.STACKED_BAR.equals(chart.getType()))
_chartImpl = chart.isThreeD() ? new StackedBar3d() : new StackedBar();
else if (Chart.STACKED_AREA.equals(chart.getType()))
_chartImpl = new StackedArea();
else if (Chart.WATERFALL.equals(chart.getType()))
_chartImpl = new Waterfall();
else if (Chart.POLAR.equals(chart.getType()))
_chartImpl = new Polar();
else if (Chart.SCATTER.equals(chart.getType()))
_chartImpl = new Scatter();
else if (Chart.TIME_SERIES.equals(chart.getType()))
_chartImpl = new TimeSeries();
else if (Chart.STEP_AREA.equals(chart.getType()))
_chartImpl = new StepArea();
else if (Chart.STEP.equals(chart.getType()))
_chartImpl = new Step();
else if (Chart.HISTOGRAM.equals(chart.getType()))
_chartImpl = new Histogram();
else if (Chart.CANDLESTICK.equals(chart.getType()))
_chartImpl = new Candlestick();
else if (Chart.HIGHLOW.equals(chart.getType()))
_chartImpl = new Highlow();
else
throw new UiException("Unsupported chart type yet: "+chart.getType());
_threeD = chart.isThreeD();
_type = chart.getType();
return _chartImpl;
}
//-- ChartEngine --//
public byte[] drawChart(Object data) {
Chart chart = (Chart) data;
ChartImpl impl = getChartImpl(chart);
JFreeChart jfchart = impl.createChart(chart);
Plot plot = (Plot) jfchart.getPlot();
float alpha = (float)(((float)chart.getFgAlpha()) / 255);
plot.setForegroundAlpha(alpha);
alpha = (float)(((float)chart.getBgAlpha()) / 255);
plot.setBackgroundAlpha(alpha);
int[] bgRGB = chart.getBgRGB();
if (bgRGB != null) {
plot.setBackgroundPaint(new Color(bgRGB[0], bgRGB[1], bgRGB[2], chart.getBgAlpha()));
}
int[] paneRGB = chart.getPaneRGB();
if (paneRGB != null) {
jfchart.setBackgroundPaint(new Color(paneRGB[0], paneRGB[1], paneRGB[2], chart.getPaneAlpha()));
}
//callbacks for each area
ChartRenderingInfo jfinfo = new ChartRenderingInfo();
BufferedImage bi = jfchart.createBufferedImage(chart.getIntWidth(), chart.getIntHeight(), BufferedImage.TRANSLUCENT, jfinfo);
//remove old areas
chart.getChildren().clear();
int j = 0;
String preUrl = null;
for(Iterator it=jfinfo.getEntityCollection().getEntities().iterator();it.hasNext();) {
ChartEntity ce = ( ChartEntity ) it.next();
final String url = ce.getURLText();
//workaround JFreeChart's bug (skip replicate areas)
if (url != null) {
if (preUrl == null) {
preUrl = url;
} else if (url.equals(preUrl)) { //start replicate, skip
break;
}
}
Area area = new Area();
area.setParent(chart);
area.setCoords(ce.getShapeCoords());
area.setShape(ce.getShapeType());
area.setId("area_"+chart.getId()+'_'+(j++));
if (chart.isShowTooltiptext() && ce.getToolTipText() != null) {
area.setTooltiptext(ce.getToolTipText());
}
area.setAttribute("url", ce.getURLText());
impl.render(chart, area, ce);
if (chart.getAreaListener() != null) {
try {
chart.getAreaListener().onRender(area, ce);
} catch (Exception ex) {
throw UiException.Aide.wrap(ex);
}
}
}
//clean up the "LEGEND_SEQ"
//used for workaround LegendItemEntity.getSeries() always return 0
//used for workaround TickLabelEntity no information
chart.removeAttribute("LEGEND_SEQ");
chart.removeAttribute("TICK_SEQ");
try {
//encode into png image format byte array
return EncoderUtil.encode(bi, ImageFormat.PNG, true);
} catch(java.io.IOException ex) {
throw UiException.Aide.wrap(ex);
}
}
//-- utilities --//
/**
* transfer a PieModel into JFreeChart PieDataset.
*/
private PieDataset PieModelToPieDataset(PieModel model) {
DefaultPieDataset dataset = new DefaultPieDataset();
for (final Iterator it = model.getCategories().iterator(); it.hasNext();) {
Comparable category = (Comparable) it.next();
Number value = model.getValue(category);
dataset.setValue(category, value);
}
return dataset;
}
/**
* transfer a CategoryModel into JFreeChart PieDataset.
*/
private PieDataset CategoryModelToPieDataset(CategoryModel model) {
DefaultPieDataset dataset = new DefaultPieDataset();
Comparable defaultSeries = null;
int max = 0;
for (final Iterator it = model.getKeys().iterator(); it.hasNext();) {
final List key = (List) it.next();
Comparable series = (Comparable) key.get(0);
if (defaultSeries == null) {
defaultSeries = series;
max = model.getCategories().size();
}
if (!Objects.equals(defaultSeries, series)) {
continue;
}
Comparable category = (Comparable) key.get(1);
Number value = (Number) model.getValue(series, category);
dataset.setValue(category, value);
if (--max == 0) break; //no more in this series
}
return dataset;
}
/**
* transfer a CategoryModel into JFreeChart CategoryDataset.
*/
private CategoryDataset CategoryModelToCategoryDataset(CategoryModel model) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (final Iterator it = model.getKeys().iterator(); it.hasNext();) {
final List key = (List) it.next();
Comparable series = (Comparable) key.get(0);
Comparable category = (Comparable) key.get(1);
Number value = (Number) model.getValue(series, category);
dataset.setValue(value, series, category);
}
return dataset;
}
/**
* transfer a XYModel into JFreeChart XYSeriesCollection.
*/
private XYDataset XYModelToXYDataset(XYModel model) {
XYSeriesCollection dataset = new XYSeriesCollection();
for (final Iterator it = model.getSeries().iterator(); it.hasNext();) {
final Comparable series = (Comparable) it.next();
XYSeries xyser = new XYSeries(series, model.isAutoSort());
final int size = model.getDataCount(series);
for(int j = 0; j < size; ++j) {
xyser.add(model.getX(series, j), model.getY(series, j), false);
}
dataset.addSeries(xyser);
}
return dataset;
}
/**
* transfer a XYModel into JFreeChart DefaultTableXYDataset.
*/
private TableXYDataset XYModelToTableXYDataset(XYModel model) {
DefaultTableXYDataset dataset = new DefaultTableXYDataset();
for (final Iterator it = model.getSeries().iterator(); it.hasNext();) {
final Comparable series = (Comparable) it.next();
XYSeries xyser = new XYSeries(series, false, false);
final int size = model.getDataCount(series);
for(int j = 0; j < size; ++j) {
xyser.add(model.getX(series, j), model.getY(series, j), false);
}
dataset.addSeries(xyser);
}
return dataset;
}
/**
* transfer a XYModel into JFreeChart TimeSeriesCollection.
*/
private XYDataset XYModelToTimeDataset(XYModel model, Chart chart) {
TimeZone tz = chart.getTimeZone();
if (tz == null) tz = TimeZones.getCurrent();
String p = chart.getPeriod();
if (p == null) p = Chart.MILLISECOND;
Class pclass = (Class) _periodMap.get(p);
if (pclass == null) {
throw new UiException("Unsupported period for Time Series chart: "+p);
}
TimeSeriesCollection dataset = new TimeSeriesCollection(tz);
for (final Iterator it = model.getSeries().iterator(); it.hasNext();) {
final Comparable series = (Comparable) it.next();
final org.jfree.data.time.TimeSeries tser =
new org.jfree.data.time.TimeSeries((String)series, pclass);
final int size = model.getDataCount(series);
for(int j = 0; j < size; ++j) {
final RegularTimePeriod period = RegularTimePeriod.createInstance(
pclass, new Date(model.getX(series, j).longValue()), tz);
tser.addOrUpdate(period, model.getY(series, j));
}
dataset.addSeries(tser);
}
return dataset;
}
private static Map _periodMap = new HashMap(10);
static {
_periodMap.put(Chart.MILLISECOND, org.jfree.data.time.Millisecond.class);
_periodMap.put(Chart.SECOND, org.jfree.data.time.Second.class);
_periodMap.put(Chart.MINUTE, org.jfree.data.time.Minute.class);
_periodMap.put(Chart.HOUR, org.jfree.data.time.Hour.class);
_periodMap.put(Chart.DAY, org.jfree.data.time.Day.class);
_periodMap.put(Chart.WEEK, org.jfree.data.time.Week.class);
_periodMap.put(Chart.MONTH, org.jfree.data.time.Month.class);
_periodMap.put(Chart.QUARTER, org.jfree.data.time.Quarter.class);
_periodMap.put(Chart.YEAR, org.jfree.data.time.Year.class);
}
/**
* transfer a HiLoModel into JFreeChart DefaultOHLCDataset
*/
private OHLCDataset HiLoModelToOHLCDataset(HiLoModel model) {
final int size = model.getDataCount();
final OHLCDataItem[] items = new OHLCDataItem[size];
for(int j = 0; j < size; ++j) {
Date date = model.getDate(j);
Number open = model.getOpen(j);
Number high = model.getHigh(j);
Number low = model.getLow(j);
Number close = model.getClose(j);
Number volume = model.getVolume(j);
OHLCDataItem item = new OHLCDataItem(date,
doubleValue(open), doubleValue(high),
doubleValue(low), doubleValue(close),
doubleValue(volume));
items[j] = item;
}
Comparable series = model.getSeries();
if (series == null) {
series = DEFAULT_HI_LO_SERIES;
}
return new DefaultOHLCDataset(series, items);
}
private double doubleValue(Number n) {
return n == null ? 0.0 : n.doubleValue();
}
/**
* decode LegendItemEntity into key-value pair of Area's componentScope.
* @param area the Area where the final attribute is set
* @param info the LegendItemEntity to be decoded.
*/
private void decodeLegendInfo(Area area, LegendItemEntity info, Chart chart) {
if (info == null) {
return;
}
final ChartModel model = chart.getModel();
final int seq = ((Integer)chart.getAttribute("LEGEND_SEQ")).intValue();
if (model instanceof PieModel) {
Comparable category = ((PieModel)model).getCategory(seq);
area.setAttribute("category", category);
area.setAttribute("value", ((PieModel)model).getValue(category));
if (chart.isShowTooltiptext() && info.getToolTipText() == null) {
area.setTooltiptext(category.toString());
}
} else if (model instanceof CategoryModel) {
Comparable series = ((CategoryModel)model).getSeries(seq);
area.setAttribute("series", series);
if (chart.isShowTooltiptext() && info.getToolTipText() == null) {
area.setTooltiptext(series.toString());
}
} else if (model instanceof XYModel) {
Comparable series = ((XYModel)model).getSeries(seq);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -