有问题请联系管理员stephenking@fanruan.com

Page tree
Skip to end of metadata
Go to start of metadata
请注意,阅读此文档需要您拥有基础的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

14 Comments

  1. Anonymous

    希望能是一个循序渐进,每步都可以做出一个小插件,比如,完成第一部分,就是一个不用自定义面板的图表插件,完成第二步就是可以自定义属性设置的插件。。。。。。。。。你这样直接是一个大的,万一出错,排查也不好排查的

  2. Anonymous

    只能讲看的一头雾水啊。。。这个真的是开发文档么?感觉不连贯啊,不熟悉这个插件开发的估计看懂真不容易吧

    1. 对于初学者,确实一头雾水。。。准备再看一遍 - - |||

      1. Anonymous

        中间的是三种方式,而不是三个步骤,应该是使用其中一个就可以

  3. Anonymous

    这不是一个循序渐进的文档吗?

    1>默认数据集+图表属性+js

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

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

    4>开发js

    1. Anonymous

      可能对于熟悉的人来说,你说的这个循序渐进一眼就懂。对于刚接触的人来说,确实一头雾水,完全摸不清。建议像零基础文档部分那样详细展开介绍一下。后面的这几个文档真的是惜字如金的感觉,头疼了。。。。

    2. Anonymous

      你跟着这个文档写起来测试项目了没?大神教教我

  4. 看到群里的流程图,结合这篇文章,外加自己DEBUG看数据走向,明白了许多

  5. Anonymous

    有谁按照这些步骤成功完成这些不同定义的?

     

  6. Anonymous

    可以的话还是自己跟着代码跑一边,然后再来看这个文档就觉得很简单了

  7. Anonymous

     写得真心不好,,完全不知道写啥。。。这教程跟spring那些文档相比,差得有点远。。

  8. Anonymous

    写这个文章的人,估计也是随便写的。。

    1. 写的真心不好,丢人

  9. 4>开发js 中的事件怎么开放出来,比如单击 双击,使其他的控件能够接受相关的参数。

Write a comment…