com.igalia.java.zk.components.JFreeChartEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.igalia.java.zk.components.JFreeChartEngine.java

Source

/*
* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.igalia.java.zk.components;

import java.awt.Color;
import java.awt.Font;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.encoders.EncoderUtil;
import org.jfree.chart.encoders.ImageFormat;
import org.jfree.chart.entity.CategoryItemEntity;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.JFreeChartEntity;
import org.jfree.chart.entity.LegendItemEntity;
import org.jfree.chart.entity.PieSectionEntity;
import org.jfree.chart.entity.PlotEntity;
import org.jfree.chart.entity.TickLabelEntity;
import org.jfree.chart.entity.TitleEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.gantt.GanttCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.data.xy.XYZDataset;
import org.zkoss.lang.Objects;
import org.zkoss.util.TimeZones;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Area;
import org.zkoss.zul.CategoryModel;
import org.zkoss.zul.Chart;
import org.zkoss.zul.ChartModel;
import org.zkoss.zul.PieModel;
import org.zkoss.zul.XYModel;
import org.zkoss.zul.impl.ChartEngine;

public class JFreeChartEngine implements ChartEngine {

    private String _type;
    private ChartImpl _chartImpl;
    private transient boolean _threeD;
    private static final SimpleDateFormat _dateFormat = new SimpleDateFormat();
    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);
    }

    private ChartImpl getChartImpl(Chart chart) {
        if (Objects.equals(chart.getType(), _type) && _threeD == chart.isThreeD()) {
            return _chartImpl;
        }

        if (Chart.TIME_SERIES.equals(chart.getType())) {
            _chartImpl = new TimeSeriesChart();
        } else if (Chart.BAR.equals(chart.getType())) {
            _chartImpl = chart.isThreeD() ? new Bar3dChart() : new BarChart();
        } else if (Chart.PIE.equals(chart.getType())) {
            _chartImpl = chart.isThreeD() ? new Pie3dChart() : new PieChart();
        } else {
            throw new RuntimeException("Unsupported chart type: " + chart.getType());
        }

        _threeD = chart.isThreeD();
        _type = chart.getType();
        return _chartImpl;
    }

    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()));
        }

        //since 3.6.3, JFreeChart 1.0.13 change default fonts which does not support Chinese, allow
        //developer to set font.

        //title font
        final Font tfont = chart.getTitleFont();
        if (tfont != null) {
            jfchart.getTitle().setFont(tfont);
        }

        //legend font
        final Font lfont = chart.getLegendFont();
        if (lfont != null) {
            jfchart.getLegend().setItemFont(lfont);
        }

        if (plot instanceof CategoryPlot) {
            final CategoryPlot cplot = (CategoryPlot) plot;
            cplot.setRangeGridlinePaint(new Color(0xc0, 0xc0, 0xc0));

            //Domain axis(x axis)
            final Font xlbfont = chart.getXAxisFont();
            final Font xtkfont = chart.getXAxisTickFont();
            if (xlbfont != null) {
                cplot.getDomainAxis().setLabelFont(xlbfont);
            }
            if (xtkfont != null) {
                cplot.getDomainAxis().setTickLabelFont(xtkfont);
            }

            Color[] colorMappings = (Color[]) chart.getAttribute("series-color-mappings");
            if (colorMappings != null) {
                for (int ii = 0; ii < colorMappings.length; ii++) {
                    cplot.getRenderer().setSeriesPaint(ii, colorMappings[ii]);
                }
            }

            Double lowerBound = (Double) chart.getAttribute("range-axis-lower-bound");
            if (lowerBound != null) {
                cplot.getRangeAxis().setAutoRange(false);
                cplot.getRangeAxis().setLowerBound(lowerBound);
            }

            Double upperBound = (Double) chart.getAttribute("range-axis-upper-bound");
            if (upperBound != null) {
                cplot.getRangeAxis().setAutoRange(false);
                cplot.getRangeAxis().setUpperBound(upperBound);
            }

            //Range axis(y axis)
            final Font ylbfont = chart.getYAxisFont();
            final Font ytkfont = chart.getYAxisTickFont();
            if (ylbfont != null) {
                cplot.getRangeAxis().setLabelFont(ylbfont);
            }
            if (ytkfont != null) {
                cplot.getRangeAxis().setTickLabelFont(ytkfont);
            }
        } else if (plot instanceof XYPlot) {
            final XYPlot xyplot = (XYPlot) plot;
            xyplot.setRangeGridlinePaint(Color.LIGHT_GRAY);
            xyplot.setDomainGridlinePaint(Color.LIGHT_GRAY);

            //Domain axis(x axis)
            final Font xlbfont = chart.getXAxisFont();
            final Font xtkfont = chart.getXAxisTickFont();
            if (xlbfont != null) {
                xyplot.getDomainAxis().setLabelFont(xlbfont);
            }
            if (xtkfont != null) {
                xyplot.getDomainAxis().setTickLabelFont(xtkfont);
            }

            //Range axis(y axis)
            final Font ylbfont = chart.getYAxisFont();
            final Font ytkfont = chart.getYAxisTickFont();
            if (ylbfont != null) {
                xyplot.getRangeAxis().setLabelFont(ylbfont);
            }
            if (ytkfont != null) {
                xyplot.getRangeAxis().setTickLabelFont(ytkfont);
            }
        } else if (plot instanceof PiePlot) {
            plot.setOutlineStroke(null);
        }

        //callbacks for each area
        ChartRenderingInfo jfinfo = new ChartRenderingInfo();
        BufferedImage bi = jfchart.createBufferedImage(chart.getIntWidth(), chart.getIntHeight(),
                Transparency.TRANSLUCENT, jfinfo);

        //remove old areas
        if (chart.getChildren().size() > 20)
            chart.invalidate(); //improve performance if too many chart
        chart.getChildren().clear();

        if (Events.isListened(chart, Events.ON_CLICK, false) || chart.isShowTooltiptext()) {
            int j = 0;
            String preUrl = null;
            for (Iterator it = jfinfo.getEntityCollection().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;
                    }
                }

                //1. JFreeChartEntity area cover the whole chart, will "mask" other areas
                //2. LegendTitle area cover the whole legend, will "mask" each legend
                //3. PlotEntity cover the whole chart plotting araa, will "mask" each bar/line/area
                if (!(ce instanceof JFreeChartEntity)
                        && !(ce instanceof TitleEntity && ((TitleEntity) ce).getTitle() instanceof LegendTitle)
                        && !(ce instanceof PlotEntity)) {
                    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);
        }
    }

    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 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);
            area.setAttribute("series", series);
            if (chart.isShowTooltiptext() && info.getToolTipText() == null) {
                area.setTooltiptext(series.toString());
            }
        }
    }

    /**
     * decode XYItemEntity into key-value pair of Area's componentScope.
     */
    private void decodeXYInfo(Area area, XYItemEntity info, Chart chart) {
        if (info == null) {
            return;
        }
        TimeZone tz = chart.getTimeZone();
        if (tz == null)
            tz = TimeZones.getCurrent();

        XYDataset dataset = info.getDataset();
        int si = info.getSeriesIndex();
        int ii = info.getItem();

        area.setAttribute("series", dataset.getSeriesKey(si));

        if (dataset instanceof XYZDataset) {
            XYZDataset ds = (XYZDataset) dataset;
            area.setAttribute("x", ds.getX(si, ii));
            area.setAttribute("y", ds.getY(si, ii));
            area.setAttribute("z", ds.getZ(si, ii));
        } else {
            area.setAttribute("x", dataset.getX(si, ii));
            area.setAttribute("y", dataset.getY(si, ii));
        }

    }

    /**
     * decode CategoryItemEntity into key-value pair of Area's componentScope.
     */
    private void decodeCategoryInfo(Area area, CategoryItemEntity info) {
        if (info == null) {
            return;
        }

        CategoryDataset dataset = info.getDataset();
        Comparable category = info.getColumnKey();
        Comparable series = info.getRowKey();

        area.setAttribute("series", series);
        area.setAttribute("category", category);

        if (dataset instanceof GanttCategoryDataset) {
            final GanttCategoryDataset gd = (GanttCategoryDataset) dataset;
            area.setAttribute("start", gd.getStartValue(series, category));
            area.setAttribute("end", gd.getEndValue(series, category));
            area.setAttribute("percent", gd.getPercentComplete(series, category));
        } else {
            area.setAttribute("value", dataset.getValue(series, category));
        }
    }

    /**
     * decode TickLabelEntity into key-value pair of Area's componentScope.
     */
    private void decodeTickLabelInfo(Area area, TickLabelEntity info, Chart chart) {
        if (info == null) {
            return;
        }
        final ChartModel model = chart.getModel();
        final int seq = ((Integer) chart.getAttribute("TICK_SEQ")).intValue();

        if (model instanceof CategoryModel) {
            Comparable category = ((CategoryModel) model).getCategory(seq);
            area.setAttribute("category", category);
            if (chart.isShowTooltiptext() && info.getToolTipText() == null) {
                area.setTooltiptext(category.toString());
            }
        }
    }

    /**
     * decode PieSectionEntity into key-value pair of Area's componentScope.
     */
    private void decodePieSectionInfo(Area area, PieSectionEntity info) {
        PieDataset dataset = info.getDataset();
        Comparable category = info.getSectionKey();
        area.setAttribute("value", dataset.getValue(category));
        area.setAttribute("category", category);
    }

    /**
     * transfer a CategoryModel into JFreeChart CategoryDataset.
     */
    private CategoryDataset CategoryModelToCategoryDataset(CategoryModel model) {
        final 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 PieModel into JFreeChart PieDataset.
     */
    private PieDataset PieModelToPieDataset(PieModel model) {
        final DefaultPieDataset dataset = new DefaultPieDataset();
        for (final Iterator it = model.getCategories().iterator(); it.hasNext();) {
            final Comparable category = (Comparable) it.next();
            Number value = (Number) model.getValue(category);
            dataset.setValue(category, value);
        }
        return dataset;
    }

    /*
    * transfer a XYModel into JFreeChart XYSeriesCollection.
    */
    private XYDataset XYModelToXYDataset(XYModel model) {
        final 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 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);
        }
        final 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(series);
            //new org.jfree.data.time.TimeSeries(series, pclass); //deprecated since JFreeChart 10.0.13
            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 PlotOrientation getOrientation(String orient) {
        return "horizontal".equals(orient) ? PlotOrientation.HORIZONTAL : PlotOrientation.VERTICAL;
    }

    //-- Chart specific implementation --//
    /** base chart */
    abstract private class ChartImpl {
        abstract void render(Chart chart, Area area, ChartEntity info);

        abstract JFreeChart createChart(Chart chart);
    }

    private class TimeSeriesChart extends ChartImpl {

        @Override
        public void render(Chart chart, Area area, ChartEntity info) {
            if (info instanceof LegendItemEntity) {
                area.setAttribute("entity", "LEGEND");
                Integer seq = (Integer) chart.getAttribute("LEGEND_SEQ");
                seq = seq == null ? new Integer(0) : new Integer(seq.intValue() + 1);
                chart.setAttribute("LEGEND_SEQ", seq);
                decodeLegendInfo(area, (LegendItemEntity) info, chart);
            } else if (info instanceof XYItemEntity) {
                area.setAttribute("entity", "DATA");
                decodeXYInfo(area, (XYItemEntity) info, chart);
            } else {
                area.setAttribute("entity", "TITLE");
                if (chart.isShowTooltiptext()) {
                    area.setTooltiptext(chart.getTitle());
                }
            }
        }

        @Override
        public JFreeChart createChart(Chart chart) {
            ChartModel model = (ChartModel) chart.getModel();
            if (!(model instanceof XYModel)) {
                throw new UiException("model must be a org.zkoss.zul.XYModel");
            }
            final JFreeChart jchart = ChartFactory.createTimeSeriesChart(chart.getTitle(), chart.getXAxis(),
                    chart.getYAxis(), XYModelToTimeDataset((XYModel) model, chart), chart.isShowLegend(),
                    chart.isShowTooltiptext(), true);
            setupDateAxis(jchart, chart);
            return jchart;
        }
    }

    private void setupDateAxis(JFreeChart jchart, Chart chart) {
        final Plot plot = jchart.getPlot();
        final DateAxis axisX = (DateAxis) ((XYPlot) plot).getDomainAxis();
        final TimeZone zone = chart.getTimeZone();
        if (zone != null) {
            axisX.setTimeZone(zone);
        }
        if (chart.getDateFormat() != null) {
            axisX.setDateFormatOverride(_dateFormat);
        }
    }

    private class BarChart extends ChartImpl {
        public void render(Chart chart, Area area, ChartEntity info) {
            if (info instanceof LegendItemEntity) {
                area.setAttribute("entity", "LEGEND");
                Integer seq = (Integer) chart.getAttribute("LEGEND_SEQ");
                seq = seq == null ? new Integer(0) : new Integer(seq.intValue() + 1);
                chart.setAttribute("LEGEND_SEQ", seq);
                decodeLegendInfo(area, (LegendItemEntity) info, chart);
            } else if (info instanceof CategoryItemEntity) {
                area.setAttribute("entity", "DATA");
                decodeCategoryInfo(area, (CategoryItemEntity) info);
            } else if (info instanceof XYItemEntity) {
                area.setAttribute("entity", "DATA");
                decodeXYInfo(area, (XYItemEntity) info, chart);
            } else if (info instanceof TickLabelEntity) {
                area.setAttribute("entity", "CATEGORY");
                Integer seq = (Integer) chart.getAttribute("TICK_SEQ");
                seq = seq == null ? new Integer(0) : new Integer(seq.intValue() + 1);
                chart.setAttribute("TICK_SEQ", seq);
                decodeTickLabelInfo(area, (TickLabelEntity) info, chart);
            } else {
                area.setAttribute("entity", "TITLE");
                if (chart.isShowTooltiptext()) {
                    area.setTooltiptext(chart.getTitle());
                }
            }
        }

        public JFreeChart createChart(Chart chart) {
            ChartModel model = (ChartModel) chart.getModel();
            if (model instanceof CategoryModel) {
                return ChartFactory.createBarChart(chart.getTitle(), chart.getXAxis(), chart.getYAxis(),
                        CategoryModelToCategoryDataset((CategoryModel) model), getOrientation(chart.getOrient()),
                        chart.isShowLegend(), chart.isShowTooltiptext(), true);
            } else if (model instanceof XYModel) {
                return ChartFactory.createXYBarChart(chart.getTitle(), chart.getXAxis(), false, chart.getYAxis(),
                        (IntervalXYDataset) XYModelToXYDataset((XYModel) model), getOrientation(chart.getOrient()),
                        chart.isShowLegend(), chart.isShowTooltiptext(), true);
            } else {
                throw new UiException("The only supported model is org.zkoss.zul.CategoryModel");
            }
        }
    }

    /** bar3d chart */
    private class Bar3dChart extends BarChart {
        public JFreeChart createChart(Chart chart) {
            ChartModel model = (ChartModel) chart.getModel();
            if (!(model instanceof CategoryModel)) {
                throw new UiException("model must be a org.zkoss.zul.CategoryModel");
            }
            return ChartFactory.createBarChart3D(chart.getTitle(), chart.getXAxis(), chart.getYAxis(),
                    CategoryModelToCategoryDataset((CategoryModel) model), getOrientation(chart.getOrient()),
                    chart.isShowLegend(), chart.isShowTooltiptext(), true);
        }
    }

    /** pie chart */
    private class PieChart extends ChartImpl {
        @Override
        void render(Chart chart, Area area, ChartEntity info) {
            if (info instanceof LegendItemEntity) {
                area.setAttribute("entity", "LEGEND");
                Integer seq = (Integer) chart.getAttribute("LEGEND_SEQ");
                seq = seq == null ? new Integer(0) : new Integer(seq.intValue() + 1);
                chart.setAttribute("LEGEND_SEQ", seq);
                decodeLegendInfo(area, (LegendItemEntity) info, chart);
            } else if (info instanceof PieSectionEntity) {
                area.setAttribute("entity", "DATA");
                decodePieSectionInfo(area, (PieSectionEntity) info);
            } else {
                area.setAttribute("entity", "TITLE");
                if (chart.isShowTooltiptext()) {
                    area.setTooltiptext(chart.getTitle());
                }
            }
        }

        @Override
        public JFreeChart createChart(Chart chart) {
            PieModel model = (PieModel) chart.getModel();
            if (!(model instanceof PieModel)) {
                throw new UiException("model must be a org.zkoss.zul.PieModel");
            }
            return ChartFactory.createPieChart(chart.getTitle(), PieModelToPieDataset(model), chart.isShowLegend(),
                    chart.isShowTooltiptext(), false);
        }

    }

    /** pie3d chart */
    private class Pie3dChart extends PieChart {
        @Override
        public JFreeChart createChart(Chart chart) {
            PieModel model = (PieModel) chart.getModel();
            if (!(model instanceof PieModel)) {
                throw new UiException("model must be a org.zkoss.zul.PieModel");
            }
            return ChartFactory.createPieChart3D(chart.getTitle(), PieModelToPieDataset(model),
                    chart.isShowLegend(), chart.isShowTooltiptext(), false);
        }
    }

}