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

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

« Previous Version 12 Current »

请注意,阅读此文档需要您拥有基础的JAVA和JS知识,您有必要亲自尝试使用FineReport的图表功能,如果您在阅读中出现疑问,可以比对零基础文档

 

图表插件介绍

图表插件是用于丰富帆软的图表类型,旨在帮助用户更好地做数据可视化展现。开发者需要通过帆软开放的接口,参考下面的demo和文档来制作插件。

安装FineReport和搭建插件开发环境

我们建议您在上手开发插件之前,先体验一下插件使用的具体情形,搭建环境的具体步骤在其他文档中写的很具体,这里不去赘述。

demo目录结构

plugins-calendarchart
  --src
    --com.fr.plugins.calendarchart
      --custompie
        --ChartConfigPane.java
        --DemoChartsPie.java
        --demoChartsPieUI.java
        --PieChart.java
      --images
        --pie256.png
      --web
        --echarts.bridge.js
        --echarts.loader.js
        --echarts.min.js
        --EChartsFileLoader.java
  --build.xml
  --plugin.xml
  --plugins-calendarchart.iml

1.custompie

此目录下存放第三方图表插件接口调用类,需要实现相应的方法。

2.images

此目录下存放插件的相关图片,供报表表单页面展示。

3.web

此目录下存放前台js,供图表预览时展示。

4.build.xml

该文件用于插件包构建,请参考文档

5.plugin.xml

该文件包含插件的接入点描述信息,请参考文档

6.plugins-calendarchart.iml

IEDA项目的配置。

接口介绍

接口详细介绍,请参阅文档

我的demo

1.demo开发概述

1) 开发过程:一共三个部分

数据抽取部分:设计器右侧的图表属性表-数据部分

前端展示样式:设计器右侧的图表属性表-样式部分

前端展示的js:图表预览展示的页面

2) 按照自己开发的类修改plugin.xml以及build.xml,请上面提到的文档。

2.下面详细讲一下开发过程吧

1) 默认数据集和图表属性+ js,除了js,我们需要继承以下三个类,并实现里面具体的方法:

Charts类:图表类

AbstractIndependentChartsProvider类:插件图表接口

AbstractJavaScriptFileHandler类:插件图表界面接口

第一步,继承Charts类,实现以下几个方法:

toJSON():通过getChartData(),这里有你需要的数据,并转成json,前端会拿到这个json

getChartID():返回的应该和plugin.xml配置中的相同

writeXML和readXML可以考虑不自定义实现,如需自定义实现下面会有介绍

public class YourCharts extends Charts {
    @Override

    public JSONObject toJSON(Repository repo) throws JSONException {

        //通过getChartData()得到你需要的数据,把这些数据转成自定义的json格式,前端会收到这个json
        return null;
    }

    @Override

    public String getChartID() {

        //这里返回值与plugin.xml配置中的plotID一致。
        return yourPlotID;
    }

    @Override

    public void writeXML(XMLPrintWriter xmlPrintWriter) {

        //图表属性配置信息会被存储在xml中,不考虑自定义图表样式,不用实现。
    }

    @Override

    public void readXML(XMLableReader xmLableReader) {

        //和writeXML一样,这里是读配置信息
	}
}

第二步,继承AbstractIndependentChartsProvider类,实现以下几个方法:

getChartName():返回插件的名字,供报表页面新增图表使用

getChartTypes():返回上一步创建的存储图表数据的类

getRequiredJS():你的图表交互的js

getWrapperName():JS对象名,该对象一般是一个函数,执行后会在给定的dom中绘制图表

getChartImagePath():图表的图标,文件存在images下面

currentAPILevel():当前接口的API等级用于判断是否要升级插件

public class YourChartsProvider extends AbstractIndependentChartsProvider {

    @Override

    public String getChartName() {

        return "你的图表插件名字";
    }

    @Override

    public Chart[] getChartTypes() {

        //返回上一步创建的存储图表数据的类,即YourCharts
        return new YourCharts[]{new YourCharts()};
    }

    @Override

    public String[] getRequiredJS() {

        //你的图表交互的js,所依赖的其他js文件,比如echarts.js在另外地方配置,后边会提到
        return new String[]{           
			"/com/fr/plugins/xxx/web/echarts.bridge.js"
        };
    }

    @Override

    public String getWrapperName() {

        //JS对象名,该对象一般是一个函数,执行后会在给定的dom中绘制图表 
        return "EChartsFactory";
    }

    @Override

    public String getChartImagePath() {

        //你的图表插件的图标
        return "com/fr/plugins/xxx/images/xxx.png";
    }

    @Override 

    public int currentAPILevel() {

        //当前接口的API等级用于判断是否要升级插件
        return CURRENT_API_LEVEL;
    }
}

第三步,继承AbstractJavaScriptFileHandler类,实现以下几个方法:

pathsForFiles():如果你用的是Echarts的话,返回echarts的js,例如:

return new String[]{
	"/com/fr/plugin/parallelchart/web/echarts.loader.js",
	"/com/fr/plugin/parallelchart/web/echarts.min.js"
};

如果你用的是g2的话,返回g2的js,例如:

return new String[]{
	"/com/fr/plugin/parallelchart/web/g2.js"
};

encode():编码格式

public class EchartFileLoader extends AbstractJavaScriptFileHandler {

    @Override


    public String[] pathsForFiles() {
		
		//加载js
        return new String[]{
            "/com/fr/plugin/parallelchart/web/echarts.loader.js",
			"/com/fr/plugin/parallelchart/web/echarts.min.js"
        };
    }

    @Override

    public String encode() {

        return EncodeConstants.ENCODING_UTF_8;
    }

2) 自定义图表属性+默认数据集 +js 

在上边的例子基础上,加上一个自定义属性的配置,以配置图表的标题为例。

第一步,继承ChartsConfigPane的YourChartsConfigPane,通过这个类的populate和update方法与Charts类做交互,实现以下几个方法:

YourChartsConfigPane():构建图表属性面板,以自定义标题为例

Populate():把YourCharts中图表配置的数据在属性面板上展示

Update():把面板上的数据存到YourCharts中去

public class YourChartsConfigPane extends ChartsConfigPane<YourCharts> {


	private ChartCollection chartCollection;
    private UITextField value;

    public YourChartsConfigPane() {

		//这里构建了一个图表属性面板,创建了一个文本框,用来让用户输入将展示的图表的标题
		this.setLayout(new BorderLayout());
		northJpane.setLayout(new GridLayout(4, 1, 10,10));
		northJpane.add(nameUILabel);
		northJpane.add(value);
		northJpane.add(demoUILabel);
		northJpane.add(customTextArea);
		this.add(northJpane, BorderLayout.NORTH);
		this.add(centerJpane, BorderLayout.CENTER);
		this.setSize(200, 200);
		this.setVisible(true);

		//初始化所有组件后必须调用此方法为他们添加监听。注意:此方法必须实现,不实现无法监听更新保存!!
		initAllListeners();
    }

	//这个类是点击确认按钮时的监听
    class ColorEventListener implements ActionListener {


        @Override

        public void actionPerformed(ActionEvent e) {
          
			update(chartCollection);
        }
    }

    @Override

    //返回的类是类定义中的泛型类
    public Class<?extends Charts> accptType() {

        return YourCharts.class;
    }

    @Override

    public void populate(ChartCollection collection, YourCharts selectedChart) {

        chartCollection = collection;

        //把YourCharts中图表配置的数据这边的面板上
    	value.setText(selectedChart.getCustomData());
    }

    @Override


    public void update(ChartCollection collection, YourCharts selectedChart) {

        //把面板上的数据存到YourCharts中去
        selectedChart.setCustomData(value.getText());
    }
}
 

第二步,继承Charts的YourCharts,默认图表属性时,writeXML和readXML不需要实现,自定义图表属性需要实现这两个方法。

writeXML():把数据存到XML中,具体过程参考demo或者查看文档

readXML():从xml中取数据,具体过程参考demo或者查看文档

public class YourCharts extends Charts {

	//这个类之前只需要修改writeXML和readXML两个方法 
    @Override

    public void writeXML(XMLPrintWriter xmlPrintWriter) {

    	//这里将数据存在XML中
       	xmlPrintWriter.startTAG(TAG_NAME)
			.attr("custom", getCustomData())
            .end();
        writeDefinition(xmlPrintWriter);
    }

    @Override

    public void readXML(XMLableReader xmLableReader) {

        //这里将XML中的数据读出来
        if (xmLableReader.isChildNode()) {


            String tagName = xmLableReader.getTagName();
            if (tagName.equals("customChartDemo")) {
            	setCustomData(xmLableReader.getAttrAsString("custom","111"));
            }
        }
    }
}

3) 自定义图表属性+自定义数据集+js

在上边基础上,新加3个类,分别是:定义数据集的面板类,数据集的定义和查询类,查询结果存储类。

第一步,继承AbstractTableDataContentPane,定义数据集的面板类,就是你的插件用户在这个面板上配置他们想要的数据,比如要用哪个表的哪一列数据,里边需要实现的接口作用,如下:

populateBean:同步配置到面板。
updateBean:将面板配置更新
checkBoxUse:检查某些box是否可用
clearAllBoxList:清空设置box的设置
refreshBoxListWithSelectTableData:刷新box

public class TableDataContentPane extends AbstractTableDataContentPane {

    private UIComboBox dateComboBox;

    private UIComboBox valueComboBox;

    public TableDataContentPane(ChartDataPane parent) {

        dateComboBox = new UIComboBox();
        valueComboBox = new UIComboBox();
		dateComboBox.setPreferredSize(new Dimension(100, 20));
  		valueComboBox.setPreferredSize(new Dimension(100, 20));

        Component[][] components = new Component[][]{


            new Component[]{new UILabel("名称", SwingConstants.RIGHT), dateComboBox},

            new Component[]{new UILabel("值", SwingConstants.RIGHT), valueComboBox}};

        	double p = TableLayout.PREFERRED;
	        double[] columnSize = {p, p};
	        double[] rowSize = {p, p, p};


        JPanel panel = TableLayoutHelper.createTableLayoutPane(components, rowSize, columnSize);

        setLayout(new BorderLayout());

        add(panel, BorderLayout.CENTER);
    }

    @Override

    public void populateBean(ChartCollection collection) {
      
		DefaultTableDataDefinition configuration = (DefaultTableDataDefinition)
		collection.getSelectedChart().getFilterDefinition();

        if (configuration == null) return;

        combineCustomEditValue(dateComboBox, configuration.getDate());
        combineCustomEditValue(valueComboBox, configuration.getValue());
    }

    @Override

    public void updateBean(ChartCollection ob) {

    	DefaultTableDataDefinition myConfiguration = new DefaultTableDataDefinition();
        Object wname = dateComboBox.getSelectedItem();
        Object wvalue = valueComboBox.getSelectedItem();

        if (wname != null) {
        	myConfiguration.setDate(wname.toString());
        }

        if (wvalue != null) {
        	myConfiguration.setValue(wvalue.toString());
        }

        ob.getSelectedChart().setFilterDefinition(myConfiguration);
    }

    @Override

    public void clearAllBoxList() {
    
		dateComboBox.removeAll();
        valueComboBox.removeAll();
    }

    @Override

    protected void refreshBoxListWithSelectTableData(List columnNameList) {
       
		refreshBoxItems(dateComboBox, columnNameList);
		refreshBoxItems(valueComboBox, columnNameList)
    }
}

第二步,继承TableDataDefinition,定义数据集的定义和查询类,这个类的功能主要是把用户定义的哪个表哪个列这样的信息,转换成真正的数据(比如这一列的所有值),然后将这些查询结果放在3中提到的查询结果存储类。

public class DefaultTableDataDefinition extends TableDataDefinition {


    public static final String XML_TAG = "DefaultTableDataDefinition";
    private String date;
    private String value;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getValue() {
        return value;
    }

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

    @Override

    public ChartData createChartData(DataModel resultSet, DataProcessor dataProcessor) {

        Map<String, String> data = new HashMap<>();

        try {


            int wordNameCol = DataCoreUtils.getColumnIndexByName(resultSet, getDate());
            int wordValueCol = DataCoreUtils.getColumnIndexByName(resultSet, getValue());

            Map<Object, List<Object>> map = new HashMap<>();

            for (int rowIndex = 0; rowIndex < resultSet.getRowCount(); rowIndex++) {

                Object wordName = resultSet.getValueAt(rowIndex, wordNameCol);
                Object wordValue = resultSet.getValueAt(rowIndex, wordValueCol);

                if (wordName != null && wordValue != null) {

                    if (!map.containsKey(wordName)) {
                    	map.put(wordName, new ArrayList<>());
                    }
                  
					map.get(wordName).add(wordValue);
                }

            }

            for (Map.Entry<Object, List<Object>> entry : map.entrySet()){

            	data.put(entry.getKey().toString(),
                	entry.getValue().get(0).toString());
            }

        } catch (Exception e) {
           
			FRLogger.getLogger().error(e.getMessage(), e);
        }

        return new TableDataContent(data);
    }

    public void writeXML(XMLPrintWriter writer) {
      
		writer.startTAG(XML_TAG)
        	.attr("date", getDate())               
			.attr("value", getValue());
        super.writeXML(writer);
        writer.end();
    }

    public void readXML(XMLableReader reader) {
       
		super.readXML(reader);

        if (reader.isAttr()) {

            String tmpVal;

            if ((tmpVal = reader.getAttrAsString("date", null)) != null){
            	this.setDate(tmpVal);
            }

            if ((tmpVal = reader.getAttrAsString("value", null)) != null){          
				this.setValue(tmpVal);
            }
        }
    }

    public boolean equals(Object ob) {

        return ob instanceof DefaultTableDataDefinition
&& ComparatorUtils.equals(((DefaultTableDataDefinition) ob).getDate(), this.getDate())
&& ComparatorUtils.equals(((DefaultTableDataDefinition) ob).getValue(), this.getValue())
&& super.equals(ob);
    }

    public Object clone() throws CloneNotSupportedException {
       
		DefaultTableDataDefinition cloned = (DefaultTableDataDefinition) super.clone();
        cloned.setDate(getDate());
        cloned.setValue(getValue());
        return cloned;
    }
}

第三步,继承NormalChartData,定义存储2中提到的查询结果的存储类。

public class TableDataContent extends NormalChartData {

    private final Map<String, String> data;

	TableDataContent(Map<String, String> data) {
		this.data = data;
    }

    public Map<String, String> getData() {
        return data;
    }
}

第四步,将自定义的数据集面板,覆盖原先默认的。方法是之前有一个继承了AbstractIndependentChartsUI的类,在里边Override这个方法即可。

@Override

public AbstractTableDataContentPane getTableDataSourcePane(Plot plot, ChartDataPane parent) {

	//这里改成你的类名即可
	return new YourDataContentPane(parent);
}

4)开发JS

echarts.bridge.js文件中定义预览时调用的函数EChartsFactory,主要分为以下几部分:

部分一,function:定义一些参数取值,不需要变。

EChartsFactory = function(options, $dom) {

    this.options = options;
    this.$dom = $dom;
    this.chartID = options.chartID;
    this.autoRefreshTime = options.autoRefreshTime || 0;
    this.width = options.width || $dom.width();// 补充从dom获取.
    this.height = options.height || $dom.height();
    this.sheetIndex = options.sheetIndex || 0;
    this.ecName = options.ecName || '';
   
	FR.Chart.WebUtils._installChart(this, this.chartID);
};

部分二,prototype:页面展示(如果开发者也是用的Echarts的话,只要自定义prototype即可)

EChartsFactory.prototype = {


    constructor : EChartsFactory,

    inits : function() {

        //后台传过来的数据或者样式都在 this.options.chartAttr中
        var ct = this.options.chartAttr;

        //新建一个Echarts图表myChart
		var myChart = echarts.init(this.$dom[0]);

        //获取后台传过来的data,并解析
        var data = ct.data;
        var max = 0;

        for (var i = 0; i < data.length; i += 1) {
            if (parseInt(max) < parseInt(data[i][1])) {
                max = data[i][1];
            }
        }

        var year = echarts.number.parseDate(data[0][0]).getFullYear();
        max = max / 20;

        //获取后台传过来的title
        var title = ct.title;

        //设置图表的参数title、tooltip、legend等
        option = {

        }
       
		this.newCharts.setOption(ct);
    },

    resize : function() {
    
		this.newCharts.resize();
    },

    refresh:function() {

    },

    refreshData:function(options){

    },

    //数据监控的刷新方式
    setData:function(options, aimation){

    }
};

国际化

为了减少代码中的中文和硬编码,我们可以对其进行国际化。

继承接口AbstractLocaleFinder,实现find()方法,返回国际化文件目录

import com.fr.stable.fun.impl.AbstractLocaleFinder;

public class youclassname extends AbstractLocaleFinder {

    @Override

    public int currentAPILevel() {
        return CURRENT_LEVEL;
    }

    @Override

    public String find() {
        return "com/fr/plugin-XXX/locale/XXX";
    }
}

添加国际化映射文件

在com/fr/plugin-XXX/locale/目录下,添加XXX.properties文件。

Plugin-XXX=XX图

Plugin-XXX_title=标题

….

注册方式

在plugin.xml中添加youclassname,如下

<extra-core>

   <LocaleFinder class="com.fr.plugin.xxx.youclassname"/>

</extra-core>

使用方式

1)JAVA中用Inter.getLocText("Plugin-XXX")替换XX图

2)JS中用FR.i18nText("Plugin-XXX ")替换XX图 

 

  • No labels