superset安装和环境配置网上都有现成的例子,此处就不赘述了。不过还是顺便说下我是git clone的源码安装的。发布的版本是0.28.1 。运行系统为linux,Windows的话有一些坑,github或者别的博客都有说解决方法,我这里也不赘述了。
在二次开发部分,网上并没有太多新的内容。最简单来说,添加新的图。网上给的例子,在当前版本的superset中都已经不支持了,连项目目录结构都变了,根本不能用,除非退回到之前的版本。但是我想大家都不会想用老版本的。就连github上给的例子都是旧的。我在github上提出过希望他们发一个新的教程出来,不过开发者说他们正在开发这样的插件,反正也没更新例子。
废话到此为止,开始。
为superset配置echarts。
在superset\superset\assets\packages.json中的“dependencies”中添加"echarts": "^4.2.0-rc.2"。版本号就用自己的echarts版本号。
然后打开命令行终端,在这个assets下执行
$ npm install -d
$ npm run dev
这里我直接给出添加一个图例需要哪些文件。后面会解释如何修改这些文件。
supserset/viz.py #修改
superset/assets/src/visualizations #添加
superset/static/assets/src/visualizations/presets/LegacyChartPreset.js #修改
\superset\assets\src\explore\controlPanels #添加
superset/assets/src/explore/controlPanels/index.js #修改
\superset\assets\src\explore\controls.jsx #可选项
这里举个非常简单易懂的栗子:添加一个echarts的散点图。
我们先来看一下最终效果页面:
viz.py 这个文件起到一个类似于视图的功能,从前端接受请求后进行一系列处理。我们可以看到,一个图例就是一个类。经过分析已有的这些类,发现这些类大部分是继承了BaseViz这个基类,并且主要重写了query_obj和get_data这两个方法。其中query_obj,顾名思义,是用来构造查询条件的。而查询条件来源于页面左侧的组件,这些组件的信息存储在form_data的一个字典中,query_obj方法中将会处理form_data,构造除自己需要的查询条件并返回,在BaseViz这个基类中,会根据这个查询条件来查询出数据。然后是get_data方法,get_data接收一个参数df,也就是query_obj之后查询出来的数据,也可以结合你自己构造的form_data中的一些条件对df进行处理。处理之后的数据以dict格式返回,这个dict会交给js进行下一步处理,也就是展示了。这一系列流程,可以通过debug的方式走一遍,对加深理解非常有帮助。
除了两个比较重要的方法之外,我们自定义的类还应该有两个重要的类属性viz_type和verbose_name。其中viz_type是标识当前类,也就是我们自定义图例的名称,注意这个名称应该与后面的js也结合起来。verbose_name则是相当于别名,展示在前端的。一些别的可能用到的属性,多参考前面已有的类,应该会得到解释。
这里给我们的类起一个名字,就叫TimeSeriesScatterViz,因为我期望这是一个时间序列的散点图。
class TimeSeriesScatterViz(BaseViz):
viz_type = 'time_series_scatter'
verbose_name = "Time Series Scatter"
sort_series = False
is_timeseries = True
def query_obj(self):
d = super(TimeSeriesScatterViz, self).query_obj()
fd = self.form_data #form_data中包含界面左侧组件内容
if not fd.get('all_columns'): #这个字段对应×××组件,不为空
raise Exception('Choose Columns')
if fd.get('all_columns'):
d['columns'] = fd.get('all_columns') # all_columns是左侧组件名,后面会提到
# 这里的例子非常简单,其实可以做很多事
return d
def get_data(self, df):
# df是pandas的DataFrame类型
# 这里的例子非常简单,其实可以做很多事
data = np.array(df).tolist() #假设数据很简单,不需要做别的处理
# 如果除了绘图用的数据还有别的信息,可以构造一个字典来返回
# data = {'plot_data':plot_data,'other_info':other_info}
return data
按照已有的方式,我们将会为自己的图例增加一个文件夹,名字跟前面的类名一样就叫TimeSeriesScatter。文件夹之下还应该有一个文件夹,叫images,这个文件夹下面存放两个图片文件,分别是thumbnail.png和thumbnailLager.png。展示在选图例的界面中,区别在尺寸大小不一样。
然后还应该有transformProps.js、TimeSeriesScatterChartPlugin.js、TimeSeriesScatter.js、RectTimeSeriesScatter.js这四个文件
这里一定注意文件名不能错,要跟文件内部对象名对应,否则superset会找不到对应文件。比如你的类名叫ClassName。那么你应该有ClassName.js、ClassNameChartPlugin.js、RectClassName.js、transformProps.js这四个文件。文件内容如下
// TimeSeriesScatter.js
import echarts from 'echarts';
import d3 from 'd3';
import PropTypes from 'prop-types';
const propTypes = {
data: PropTypes.array,
width: PropTypes.number,
height: PropTypes.number,
}; //检查类型,其中data包含viz.py中返回的数据,width和height为图表宽高
function TimeSeriesScatter(element, props){
const {
width,
height,
data,
} = props;
// console.log(data) 可以检查一下data内容
const div = d3.select(element, props);
var html = '';
div.html(html); //给echarts添加div
var myChart = echarts.init(document.getElementById('time_series_scatter')); //初始化echarts,接下来的就是echarts内容了
var option = {
tooltip: {
formatter: '{b}: {c}'
},
xAxis: {
type: 'time',
scale: true
},
yAxis: {
type: 'value',
scale: true
},
series: [{
type: 'scatter',
symbolSize: 20,
}, {
type: 'scatter',
data: data,
}]
};
myChart.setOption(option);
}
TimeSeriesScatter.displayName = 'Time Series Scatter';
TimeSeriesScatter.propTypes = propTypes;
export default TimeSeriesScatter;
其它几个文件,主要是参考superset已有的图例,或许有别的trick,我所举例的仅仅是能够正常展示的
// RectTimeSeriesScatter.js
import reactify from '../../utils/reactify';
import Component from './TimeSeriesScatter';
export default reactify(Component);
// TimeSeriesScatterChartPlugin.js
import { t } from '@superset-ui/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
const metadata = new ChartMetadata({
name: t('Time Series Scatter'),
description: '',
credits: ['http://echarts.baidu.com/examples/editor.html?c=scatter-effect'],
thumbnail,
});
export default class TimeSeriesScatterChartPlugin extends ChartPlugin {
constructor() {
super({
metadata,
transformProps,
loadChart: () => import('./ReactTimeSeriesScatter.js'),
});
}
}
// transformProps.js
export default function transformProps(chartProps) {
const { width, height, payload } = chartProps;
//console.log(chartProps); 可以用来验证数据是否正确
return {
data: payload.data,
width,
height,
};
}
做完以上工作还不算完,还需要修改\superset\assets\src\visualizations\presets\LegacyChartPreset.js文件
import TimeSeriesScatter from '../TimeSeriesScatter/TimeSeriesScatterChartPlugin';
// 仿照已有的就行
new TimeSeriesScatter().configure({key: 'time_series_scatter'})
这样\superset\assets\src\visualizations下的文件算是改完了(应该没有漏掉的吧……应该)
这个文件夹下放的文件主要是来配置左侧的一些组件。对于我的新图例来说,需要添加一个新的文件来配置图例所需组件。
//superset\assets\src\explore\controlPanels\TimeSeriesScatter.js
import { t } from '@superset-ui/translation';
export default {
controlPanelSections: [
{
label: t('NOT GROUPED BY'), //控制块标题,可以有多个控制块,一块包含多个组件
description: t('Use this section if you want to query atomic rows'), //描述
expanded: true,
controlSetRows: [
['all_columns'], //使用的组件名
['row_limit', null],
],
},
],
};
其中\superset\assets\src\explore\controls.jsx文件(注意路径)定义了组件的形式与功能,如果需要自定义组件,可以在这个文件下开发,具体可以参考已有的部分。选取对自己的图例合适的组件也需要多看这个文件。比如在我的图例中使用的Columns组件:
// all_columns为组件名,调用时用此名
all_columns: {
type: 'SelectControl', // 组件类型
multi: true, //不知道啥意思
label: t('Columns'), //组件标题,t为翻译函数
default: [], //默认值
description: t('Columns to display'), //组件描述
// 组件功能实现
optionRenderer: c => ,
valueRenderer: c => ,
valueKey: 'column_name',
mapStateToProps: state => ({
options: (state.datasource) ? state.datasource.columns : [],
}),
},
做完以上工作,需要使配置组件的js生效,还要修改superset\assets\src\explore\controlPanels\index.js
import TimeSeriesScatter from './TimeSeriesScatter'
...
//仿照已有的添加即可
export const controlPanelConfigs = {
...
time_series_scatter: TimeSeriesScatter,
}
需要注意的一点是,每次修改完js文件,要执行npm run dev来重新编译以更新。也可以在package.json文件中的“dev”参数设置中添加--hot命令,只需要运行一次(别关掉啊,让它跑着,相当于一个监听服务),后面一旦js文件有改动,会自动编译变更部分,速度也会比从头开始编译要快。
好,那么至此应该大功告成了……吧?由于距离写这篇博文已经过了一段时间,可能有疏漏的地方,还望指正。