【仅供内部供应商使用,不提供对外解答和培训】
Note |
---|
请注意,阅读此文档需要您拥有基础的JAVA和JS知识,您有必要亲自尝试使用FineReport的图表功能,如果您在阅读中出现疑问,可以比对零基础文档。 |
...
请注意,阅读此文档需要您拥有基础的JAVA和JS知识,您有必要亲自尝试使用FineReport的图表功能,如果您在阅读中出现疑问,可以比对零基础文档。 |
图表插件是用于丰富帆软的图表类型,旨在帮助用户更好地做数据可视化展现。开发者需要通过帆软开放的接口,参考下面的demo和文档来制作插件。
我们建议您在上手开发插件之前,先体验一下插件使用的具体情形,搭建环境的具体步骤在其他文档中写的很具体,这里不去赘述。
Code Block |
---|
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
|
此目录下存放第三方图表插件接口调用类,需要实现相应的方法。
此目录下存放插件的相关图片,供报表表单页面展示。
此目录下存放前台js,供图表预览时展示。
该文件用于插件包构建,请参考文档。
该文件包含插件的接入点描述信息,请参考文档。
IEDA项目的配置。
接口详细介绍,请参阅文档。
1) 开发过程:一共三个部分
数据抽取部分:设计器右侧的图表属性表-数据部分
前端展示样式:设计器右侧的图表属性表-样式部分
前端展示的js:图表预览展示的页面
2) 按照自己开发的类修改plugin.xml以及build.xml,请上面提到的文档。
1) 默认数据集和图表属性+ js,除了js,我们需要继承以下三个类,并实现里面具体的方法:
Charts类:图表类
AbstractIndependentChartsProvider类:插件图表接口
AbstractJavaScriptFileHandler类:插件图表界面接口
第一步,继承Charts类,实现以下几个方法:
toJSON():通过getChartData(),这里有你需要的数据,并转成json,前端会拿到这个json
getChartID():返回的应该和plugin.xml配置中的相同
writeXML和readXML可以考虑不自定义实现,如需自定义实现下面会有介绍
Code Block | ||||
---|---|---|---|---|
| ||||
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等级用于判断是否要升级插件
Code Block | ||
---|---|---|
| ||
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,例如:
Code Block | ||
---|---|---|
| ||
return new String[]{
"/com/fr/plugin/parallelchart/web/echarts.loader.js",
"/com/fr/plugin/parallelchart/web/echarts.min.js"
};
|
如果你用的是g2的话,返回g2的js,例如:
Code Block | ||
---|---|---|
| ||
return new String[]{
"/com/fr/plugin/parallelchart/web/g2.js"
}; |
encode():编码格式
Code Block | ||
---|---|---|
| ||
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中去
Code Block | ||
---|---|---|
| ||
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或者查看文档
Code Block | ||
---|---|---|
| ||
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
Code Block | ||
---|---|---|
| ||
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中提到的查询结果存储类。
Code Block | ||
---|---|---|
| ||
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中提到的查询结果的存储类。
Code Block | ||
---|---|---|
| ||
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这个方法即可。
Code Block | ||
---|---|---|
| ||
@Override
public AbstractTableDataContentPane getTableDataSourcePane(Plot plot, ChartDataPane parent) {
//这里改成你的类名即可
return new YourDataContentPane(parent);
}
|
4)开发JS
echarts.bridge.js文件中定义预览时调用的函数EChartsFactory,主要分为以下几部分:
部分一,function:定义一些参数取值,不需要变。
Code Block | ||
---|---|---|
| ||
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即可)
Code Block | ||
---|---|---|
| ||
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){
}
};
|
为了减少代码中的中文和硬编码,我们可以对其进行国际化。
Code Block | ||
---|---|---|
| ||
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,如下
Code Block | ||
---|---|---|
| ||
<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图