【仅供内部供应商使用,不提供对外解答和培训】

Page tree

【仅供内部供应商使用,不提供对外解答和培训】

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

示例说明

demo地址:https://git.fanruan.com/fanruan/demo-chart-pie

该文档通过示例图表插件的代码,使开发者了解图表插件的开发过程,并通过例子了解图表的插件接口。

阅读时可以结合FineKit图表类型接口理解插件接口的作用。

示例使用Echarts的饼图,图表的数据结构很简单:

分类
......
.....

前台图表实例化var chart = echarts.init(dom),配置项chart.setOption(option)。

开发步骤

1.定义图表数据的属性集合,继承自类型BaseColumnFieldCollection,对于作为分组汇总条件的字段加上注解@KeyField

例子:

饼图中包含一个分类字段和一个值字段,其中分类字段需要添加注解。

DemoColumnFieldCollection
package com.fr.plugin.demo;

import com.fanruan.api.report.chart.field.BaseColumnFieldCollection;
import com.fr.chartx.data.annotations.KeyField;
import com.fr.chartx.data.field.ColumnField;

public class DemoColumnFieldCollection extends BaseColumnFieldCollection {

    @KeyField
    private ColumnField category = new ColumnField();

    private ColumnField value = new ColumnField();

    public ColumnField getCategory() {
        return category;
    }

    public void setCategory(ColumnField category) {
        this.category = category;
    }

    public ColumnField getValue() {
        return value;
    }

    public void setValue(ColumnField value) {
        this.value = value;
    }
}

2.定义图表属性类,继承BaseChartWithData,实现对应的方法。

并且这个类型要加上插件记录点功能注解@FunctionRecorder

DemoChart
package com.fr.plugin.demo;

import com.fanruan.api.cal.FormulaKit;
import com.fanruan.api.log.LogKit;
import com.fanruan.api.report.chart.BaseChartWithData;
import com.fanruan.api.script.FineCanvas;
import com.fanruan.api.util.AssistKit;
import com.fanruan.api.util.IOKit;
import com.fanruan.api.util.StringKit;
import com.fr.base.BaseFormula;
import com.fr.base.chart.cross.FormulaProcessor;
import com.fr.chart.ChartWebParaProvider;
import com.fr.extended.chart.HyperLinkPara;
import com.fr.json.JSON;
import com.fr.json.JSONArray;
import com.fr.json.JSONFactory;
import com.fr.json.JSONObject;
import com.fr.plugin.transform.FunctionRecorder;
import com.fr.stable.xml.XMLPrintWriter;
import com.fr.stable.xml.XMLableReader;

import java.util.List;
import java.awt.Image;
import java.awt.image.BufferedImage;

@FunctionRecorder
public class DemoChart extends BaseChartWithData {

    private static final String ID = "DEMO_CHART";

    private BaseFormula titleFormula = FormulaKit.newFormula(StringKit.EMPTY);

    private PieType pieType = PieType.PIE;

    private String legendPosition = "left";

    public PieType getPieType() {
        return pieType;
    }

    public void setPieType(PieType pieType) {
        this.pieType = pieType;
    }

    public BaseFormula getTitleFormula() {
        return titleFormula;
    }

    public void setTitleFormula(BaseFormula titleFormula) {
        this.titleFormula = titleFormula;
    }

    public String getLegendPosition() {
        return legendPosition;
    }

    public void setLegendPosition(String legendPosition) {
        this.legendPosition = legendPosition;
    }

	//通过判断图表的子类型在设计器中展示不同的图片。
    @Override
    protected Image designImage(int width, int height, int resolution, ChartWebParaProvider chartWebPara) {
        switch (pieType) {
            case PIE:
                return IOKit.readImageWithCache("com/fr/plugin/demo/pieType.png");
            default:
                return IOKit.readImageWithCache("com/fr/plugin/demo/ringType.png");
        }
    }

	//通过使用辅助类FineCanvas导出图表。
    @Override
    protected Image exportImage(int width, int height, int resolution, ChartWebParaProvider chartWebPara) {
        BufferedImage bufferedImage = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB);
        try {
            FineCanvas canvas = new FineCanvas("/com/fr/plugin/demo/echarts-adapter.js", "/com/fr/plugin/demo/echarts.js");
            canvas.loadText("canvas.height = " + height, "canvas.width = " + width);
            canvas.loadText("var myChart = echarts.init(canvas)");
            canvas.loadText("option = " + createAttributeConfig(chartWebPara).toString());
            canvas.loadText("myChart.setOption(option);");
            bufferedImage = canvas.paint();
        } catch (Exception ex) {
            LogKit.error(ex.getMessage(), ex);
        }
        return bufferedImage;
    }

    @Override
    public DemoChart clone() throws CloneNotSupportedException {
        DemoChart result = (DemoChart) super.clone();
        if (getTitleFormula() != null) {
            result.setTitleFormula(this.getTitleFormula().clone());
        }
        result.setPieType(this.getPieType());
        result.setLegendPosition(this.getLegendPosition());
        return result;
    }

    @Override
    public int hashCode() {
        return super.hashCode() + AssistKit.hashCode(this.getTitleFormula(), this.getPieType(), this.getLegendPosition());
    }

    @Override
    public boolean equals(Object ob) {
        return super.equals(ob)
                && ob instanceof DemoChart
                && AssistKit.equals(this.getTitleFormula(), ((DemoChart) ob).getTitleFormula())
                && AssistKit.equals(this.getPieType(), ((DemoChart) ob).getPieType())
                && AssistKit.equals(this.getLegendPosition(), ((DemoChart) ob).getLegendPosition());
    }

	//创建Echarts饼图需要的options,根据自身的属性来定义options中的配置项。
	//调用前,已经对FieldCollection中的字段做了对应的汇总计算,可以直接通过getFieldCollection获取到字段集合,从而获取其中的计算结果。
    @Override
    public JSONObject createAttributeConfig(ChartWebParaProvider chartWebPara) {
        JSONObject jsonObject = super.createAttributeConfig(chartWebPara);

        jsonObject.put("title", JSONFactory.createJSON(JSON.OBJECT).put("text", getTitleFormula().getResult()).put("x", "center"));
        jsonObject.put("tooltip", JSONFactory.createJSON(JSON.OBJECT).put("trigger", "item").put("formatter", "{b}: {c} ({d}%)"));

        DemoColumnFieldCollection columnFieldCollection = getFieldCollection(DemoColumnFieldCollection.class);
        List<Object> category = columnFieldCollection.getCategory().getValues();
        List<Object> values = columnFieldCollection.getValue().getValues();
        JSONArray legendData = JSONFactory.createJSON(JSON.ARRAY);
        JSONArray seriesData = JSONFactory.createJSON(JSON.ARRAY);
        for (int i = 0; i < category.size(); i++) {
            legendData.put(category.get(i));
            seriesData.put(JSONFactory.createJSON(JSON.OBJECT).put("name", category.get(i)).put("value", values.get(i)));
        }
        jsonObject.put("legend", JSONFactory.createJSON(JSON.OBJECT).put("orient", "vertical").put("left", getLegendPosition()).put("data", legendData));
        JSONObject series = JSONFactory.createJSON(JSON.OBJECT);
        series.put("type", "pie");
        switch (pieType) {
            case PIE:
                series.put("radius", "55%");
                break;
            default:
                series.put("radius", JSONFactory.createJSON(JSON.ARRAY).put("35%").put("55%"));
                break;
        }
        series.put("data", seriesData);
        jsonObject.put("series", series);
        return jsonObject;
    }

	//处理对象的公式类型的属性,这里处理了图表标题。
    @Override
    public void dealFormula(FormulaProcessor formulaProcessor) {
        if (titleFormula != null) {
            formulaProcessor.dealWith(titleFormula);
        }
        super.dealFormula(formulaProcessor);
    }

    @Override
    public String getID() {
        return ID;
    }

	//从xml中读取对象的属性。
    @Override
    public void readAttr(XMLableReader xmLableReader) {
        super.readAttr(xmLableReader);

        this.setPieType(PieType.parseInt(xmLableReader.getAttrAsInt("pieType", 0)));
        this.setTitleFormula(FormulaKit.newFormula(xmLableReader.getAttrAsString("title", "新建图表标题")));
        this.setLegendPosition(xmLableReader.getAttrAsString("legendPosition", StringKit.EMPTY));
    }

	//将对象的属性写入xml中。
    @Override
    public void writeAttr(XMLPrintWriter xmlPrintWriter) {
        super.writeAttr(xmlPrintWriter);
        xmlPrintWriter.attr("pieType", pieType.ordinal())
                .attr("title", titleFormula.toString())
                .attr("legendPosition", legendPosition);
    }

	//定义了超链参数下拉选择中的数据,选择的类型名称为分类,公式的内容为Category,点击传参为data下的name属性值。
    private static final HyperLinkPara Category = new HyperLinkPara() {
        @Override
        public String getName() {
            return "分类";
        }

        @Override
        public String getFormulaContent() {
            return "Category";
        }

        @Override
        public String[] getProps() {
            return new String[]{"data", "name"};
        }
    };

    @Override
    protected HyperLinkPara[] hyperLinkParas() {
        return new HyperLinkPara[]{
                Category,
        };
    }
}

3.定义数据配置界面,单元格数据源配置界面继承BaseCellDataFieldsPane,数据集数据源配置界面继承BaseDataSetFieldsPane。需要添加DemoColumnFieldCollection作为泛型使用。

(1)单元格数据源配置面板。

使用公式输入框UIFormulaTextField类作为单元格数据源的数据配置框,添加分类和值两个输入框。

DemoCellDataFieldsPane
package com.fr.plugin.demo;

import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.chart.field.BaseCellDataFieldsPane;
import com.fanruan.api.design.ui.component.formula.UIFormulaTextField;

import java.awt.Component;

public class DemoCellDataFieldsPane extends BaseCellDataFieldsPane<DemoColumnFieldCollection> {

    private UIFormulaTextField categoryPane;
    private UIFormulaTextField valuePane;

    public void initComponents(){
        categoryPane = new UIFormulaTextField();
        valuePane = new UIFormulaTextField();
        super.initComponents();
    }

	//定义的选择框的名称,这里使用国际化的写法。
    @Override
    protected String[] fieldLabels() {
        return new String[]{
                DesignKit.i18nText("Fine-Plugin_Demo_Category"),
                DesignKit.i18nText("Fine-Plugin_Demo_Value")
        };
    }

	//定义所有的组件,包含分类和值两个输入框。
    @Override
    protected Component[] fieldComponents() {
        return new Component[]{
                categoryPane,
                valuePane
        };
    }

	//定义公式组件,包含分类和值两个输入框。
    @Override
    protected UIFormulaTextField[] formulaPanes() {
        return new UIFormulaTextField[]{
                categoryPane,
                valuePane
        };
    }

	//通过DemoColumnFieldCollection对象的属性,对面板进行了还原,这里使用了父类定义的populateField方法。
    @Override
    public void populateBean(DemoColumnFieldCollection dataConf) {
        populateField(categoryPane, dataConf.getCategory());
        populateField(valuePane, dataConf.getValue());
    }

	//通过面板的值,生成DemoColumnFieldCollection对象,这里使用了父类定义的updateField方法。
    @Override
    public DemoColumnFieldCollection updateBean() {
        DemoColumnFieldCollection dataConfig = new DemoColumnFieldCollection();

        updateField(categoryPane, dataConfig.getCategory());
        updateField(valuePane, dataConfig.getValue());

        return dataConfig;
    }
}

(2)数据集数据源配置面板

使用下拉选择框UIComboBox类作为数据集数据源的数据配置框,可以选择数据集中对应的字段,添加分类和值两个输入框,同时添加汇总方法的选择框CalculateComboBox。

DemoDataSetFieldsPane
package com.fr.plugin.demo;

import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.chart.field.BaseDataSetFieldsPane;
import com.fanruan.api.design.ui.component.UIComboBox;
import com.fanruan.api.design.ui.component.chart.CalculateComboBox;

import java.awt.Component;

public class DemoDataSetFieldsPane extends BaseDataSetFieldsPane<DemoColumnFieldCollection> {

    private UIComboBox categoryPane;
    private UIComboBox valuePane;
    private CalculateComboBox calculateComboBox;

    public void initComponents() {
        categoryPane = new UIComboBox();
        valuePane = new UIComboBox();
        calculateComboBox = new CalculateComboBox();
        super.initComponents();
    }

	//定义的选择框的名称,这里使用国际化的写法。
    @Override
    protected String[] fieldLabels() {
        return new String[]{
                DesignKit.i18nText("Fine-Plugin_Demo_Category"),
                DesignKit.i18nText("Fine-Plugin_Demo_Value"),
                DesignKit.i18nText("Fine-Plugin_Demo_Summary_Method")
        };
    }

	//定义所有的组件,包含分类和值两个组件。
    @Override
    protected Component[] fieldComponents() {
        return new Component[]{
                categoryPane,
                valuePane,
                calculateComboBox
        };
    }

    //定义公式下拉框组件,包含分类和值两个下拉框。
    @Override
    protected UIComboBox[] filedComboBoxes() {
        return new UIComboBox[]{
                categoryPane,
                valuePane
        };
    }

	//通过DemoColumnFieldCollection对象的属性,对面板进行了还原,普通下拉框使用了父类定义的populateField方法,带有汇总方式的下拉框使用父类定义的populateFunctionField方法。
    @Override
    public void populateBean(DemoColumnFieldCollection dataConf) {
        populateField(categoryPane, dataConf.getCategory());
        populateFunctionField(valuePane, calculateComboBox, dataConf.getValue());
    }

	//通过面板的值,生成DemoColumnFieldCollection对象,普通下拉框使用了父类定义的updateField方法,带有汇总方式的下拉框使用父类定义的updateFunctionField方法。
    @Override
    public DemoColumnFieldCollection updateBean() {
        DemoColumnFieldCollection dataConfig = new DemoColumnFieldCollection();

        updateField(categoryPane, dataConfig.getCategory());
        updateFunctionField(valuePane, calculateComboBox, dataConfig.getValue());

        return dataConfig;
    }
}

4.定义图表类型,继承自BaseChartType

DemoType
package com.fr.plugin.demo;

import com.fanruan.api.report.chart.BaseChartType;
import com.fanruan.api.report.chart.BaseChartWithData;


public class DemoType extends BaseChartType {

    //定义的所以图表子类型的图表属性对象,这里包含饼图和圆环图,主要区别就在于DemoChart的pieType属性。
    public BaseChartWithData[] getChartTypes() {
        return new BaseChartWithData[]{
                createDemoChart(PieType.PIE),
                createDemoChart(PieType.RING)
        };
    }

    
    //图表在web端展现时需要的JS文件,依赖echarts.js和demoWrapper.js
    public String[] getRequiredJS() {
        return new String[]{
                "com/fr/plugin/demo/demoWrapper.js",
                "com/fr/plugin/demo/echarts.js"
        };
    }

    //图表在web端展现时需要的CSS文件,这里依赖一个demo.css文件
    public String[] getRequiredCss() {
        return new String[]{
                "com/fr/plugin/demo/demo.css"
        };
    }

    //JS对象名,这里是定义在demoWrapper.js中的对象。
    public String getWrapperName() {
        return "demoWrapper";
    }

    private DemoChart createDemoChart(PieType pieType) {
        DemoChart demoChart = new DemoChart();
        demoChart.setPieType(pieType);
        return demoChart;
    }
}

5.定义图表类型配置界面,继承自DefaultTypePane类,该类已经默认实现了配置界面图表子类型的选择方法。

DemoTypePane
package com.fr.plugin.demo;

import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.chart.DefaultTypePane;
import com.fanruan.api.design.ui.component.UIButtonGroup;
import com.fanruan.api.util.StringKit;

import javax.swing.JPanel;
import java.awt.Component;

public class DemoTypePane extends DefaultTypePane<DemoChart> {

    private UIButtonGroup buttonGroup = new UIButtonGroup(new String[]{DesignKit.i18nText("Fine-Plugin_Legend_Right"), DesignKit.i18nText("Fine-Plugin_Legend_Left")});

    //配置界面类型选择时子类型的图片路径,默认使用插入图表时的子类型图片,这里重新使用了尺寸不同的图片
    @Override
    protected String[] getTypeIconPath() {
        return new String[]{
                "com/fr/plugin/demo/pieType.png",
                "com/fr/plugin/demo/ringType.png"
        };
    }

	//根据chart的属性确定选择的是哪一个子类型,这里是根据chart的pieType判断是饼图还是圆环图,如果只有一种子类型,不需要实现。
    @Override
    protected int getSelectIndexInChart(DemoChart chart) {
        return chart.getPieType().ordinal();
    }

	//根据选择的子类型的索引,来还原chart对象的属性。如果只有一种子类型,不需要实现。
    @Override
    protected void setSelectIndexInChart(DemoChart chart, int index) {
        chart.setPieType(PieType.parseInt(index));
    }

	//定义类型选择界面的组件集合,typePane代表子类型选择的组件,这里又定义了一个图例位置选择的组件。顺序决定了组件的上下位置。
    @Override
    protected Component[][] getPaneComponents(JPanel typePane) {
        return new Component[][]{
                new Component[]{typePane},
                new Component[]{buttonGroup}
        };
    }

	//根据对象属性还原界面的配置选项,先调用父类的方法实现子类型的还原,再完成本类中图例选项的还原。
    @Override
    public void populateBean(DemoChart ob) {
        super.populateBean(ob);
        buttonGroup.setSelectedIndex(StringKit.equals("left", ob.getLegendPosition()) ? 0 : 1);
    }

	//根绝界面的配置选项设置对象的属性,先调用父类的方法设置了图表子类型的属性,再完成本类中图例属性的设置。
    @Override
    public void updateBean(DemoChart ob) {
        super.updateBean(ob);
        ob.setLegendPosition(buttonGroup.getSelectedIndex() == 0 ? "left" : "right");
    }
}

6.定义其他的面板:

如果要实现一个完全自定义的面板,则继承BaseOtherPane类,添加面板组件,并实现createContentPane方法来创建面板。

DemoTitlePane
package com.fr.plugin.demo;

import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.chart.BaseOtherPane;
import com.fanruan.api.design.ui.component.formula.UIFormulaTextField;

import javax.swing.JPanel;
import java.awt.BorderLayout;

public class DemoTitlePane extends BaseOtherPane<DemoChart> {

	//公式输入框
    private UIFormulaTextField title;

	//从对象属性中获取标题并更新到面板中
    @Override
    public void populate(DemoChart ob) {
        title.populateBean(ob.getTitleFormula().toString());
    }

	//将面板中配置的标题还原到对象属性
    @Override
    public void update(DemoChart ob) {
        ob.getTitleFormula().setContent(title.updateBean());
    }

	//该面板仅包含一个标题公式输入框
    @Override
    protected JPanel createContentPane() {
        JPanel panel = new JPanel(new BorderLayout(0, 6));
        title = new UIFormulaTextField();
        panel.add(title, BorderLayout.CENTER);
        return panel;
    }

	//面板名称为标题
    @Override
    public String title4PopupWindow() {
        return DesignKit.i18nText("Fine-Plugin_Demo_Title");
    }
}

除此之外,FineKit提供了一个自带超链和自动刷新的面板DefaultOtherPane类,该类可以直接使用或者继承后覆写部分方法。

(1)如果想使用自动刷新和超链的功能,则直接使用该类即可。

(2)如果只想使用其中一种功能,或者还需要再面板中添加其他组件,则可以继承该类后覆写createContentPane方法,重新定义面板。可以使用createRefreshPane方法生成自动刷新面板,使用createHyperlinkPane方法来生成超链面板。

7.定义图表的所有界面,图表名称、图表子名称、图表示例图片路径,图表Icon路径等,继承BaseChartTypeUI。

数据配置界面需要利用SingleDataPane类来组合单元格数据源和数据集数据源,创建BaseDataPane对象,实现createSingleDataPane匿名方法即可。

DemoUI
package com.fr.plugin.demo;

import com.fanruan.api.design.DesignKit;
import com.fanruan.api.design.chart.BaseChartTypeUI;
import com.fanruan.api.design.chart.BaseDataPane;
import com.fanruan.api.design.chart.BaseOtherPane;
import com.fanruan.api.design.chart.DefaultOtherPane;
import com.fanruan.api.design.chart.DefaultTypePane;
import com.fanruan.api.design.chart.SingleDataPane;
import com.fr.design.gui.frpane.AttributeChangeListener;

public class DemoUI extends BaseChartTypeUI {

	//图表配置界面类型选择面板
    @Override
    public DefaultTypePane getPlotTypePane() {
        return new DemoTypePane();
    }

	//图表配置界面数据配置面板,组合了我们定义的单元格数据源配置面板和数据集数据源配置面板。
    @Override
    public BaseDataPane getChartDataPane(AttributeChangeListener listener) {
        return new BaseDataPane(listener) {
            @Override
            protected SingleDataPane createSingleDataPane() {
                return new SingleDataPane(new DemoDataSetFieldsPane(), new DemoCellDataFieldsPane());
            }
        };
    }

	//图表配置界面其他面板的集合,这里定义了一个标题配置的面板和一个包含自动刷新和超链的面板。
    @Override
    public BaseOtherPane[] getAttrPaneArray(AttributeChangeListener listener) {
        return new BaseOtherPane[]{new DemoTitlePane(), new DefaultOtherPane()};
    }

	//图表Icon的路径。
    @Override
    public String getIconPath() {
        return "com/fr/plugin/demo/icon.png";
    }

	//选择插入图表时,图表的名称。
    @Override
    public String getName() {
        return DesignKit.i18nText("Fine-Plugin_Demo_Chart");
    }

	//选择插入图表时,图表子类型的名称。
    @Override
    public String[] getSubName() {
        return new String[]{
                DesignKit.i18nText("Fine-Plugin_Pie"),
                DesignKit.i18nText("Fine-Plugin_Ring")
        };
    }

	//选择插入图表时,图表子类型的图片路径。
    @Override
    public String[] getDemoImagePath() {
        return new String[]{
                "com/fr/plugin/demo/pie.png",
                "com/fr/plugin/demo/ring.png"
        };
    }
}

8.图表JS文件

其中Van.FRChartBridge.demoWrapper对象需要和DemoType.getWrapperName()定义的对象名称一致。

demoWrapper.js
!(function () {
    Van.FRChartBridge.demoWrapper = Van.FRChartBridge.AbstractChart.extend({
		//初始化方法,这里使用echarts的初始化方法,并且在echarts图表的点击事件中绑定了超链方法
        _init: function (dom, option) {
            var chart = echarts.init(dom);
            //绑定点击触发超链函数
            chart.on('click', this.getLinkFun());
            chart.setOption(option);
            return chart;
        },

	 	//自动刷新方法。开启自动刷新后会根据时间间隔调用此方法
        _refresh: function (chart, option) {
            chart.setOption(option);
        },

		//定义空数据的条件,这里是当options.series.data.length长度为0时,代表空数据,主要原因是判断空数据时,显示默认的空数据背景
        _emptyData: function (options) {
            return options.series.data.length === 0;
        }
    })
})();

9.配置plugin.xml文件

其中id和版本和jar包名称对应,示例的jar包名称为plugin-com.fr.plugin.demoChart-1.0.0.jar;

function-recorder下对应的插件功能点记录,我们这里给也加了DemoChart@FunctionRecorder注解;

ChartTypeProvider和ChartTypeUIProvider分别对应了图表类型和图表界面的类型,即实现了BaseChartType和BaseChartTypeUI的类,其中的chartID需要和DemoChart中的getID返回值一致;

LocaleFinder是国际化,参考8.插件接口实战——国际化接口

plugin.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?><plugin>
 <id>com.fr.plugin.demoChart</id>
 <name><![CDATA[DEMO图表]]></name>
 <active>yes</active>
 <version>1.0.0</version>
 <env-version>10.0</env-version>
 <jartime>2019-10-09</jartime>
 <vendor>Bjorn</vendor>
 <description><![CDATA[给开发者参考的图表插件demo]]></description>
 <change-notes><![CDATA[]]></change-notes>

 <function-recorder class="com.fr.plugin.demo.DemoChart"/>

 <extra-chart>
 <ChartTypeProvider class="com.fr.plugin.demo.DemoType" chartID="DEMO_CHART"/>
 </extra-chart>

 <extra-core>
 <LocaleFinder class="com.fr.plugin.demo.DemoLocaleFinder"/>
 </extra-core>

 <extra-chart-designer>
 <ChartTypeUIProvider class="com.fr.plugin.demo.DemoUI" chartID="DEMO_CHART"/>
 </extra-chart-designer>
</plugin>
  • No labels