superset集成echarts

文章目录

  • 服务地址
  • 前言
  • 集成echarts
    • 集成echarts柱状折线图 mix-line-bar
      • 流程
        • 1、 superset-frontend/src/visualizations/ 目录下
        • 2、 修改 superset-frontend/src/visualizations/presets/MainPreset.js
        • 3、 修改 superset-frontend/src/explore/components/controls/VizTypeControl.jsx
        • 4、新增 superset-frontend/src/explore/controlPanels/MixLineBar.js
        • 5、修改 superset-frontend/src/explore/controls.jsx 新增的一些自定义组件
        • 6. 修改 setupPlugins.ts
        • 7、修改package.json
        • 8. 后端 py 修改 superset/viz.py
        • 汉化与问题记录

服务地址

服务可以登录 http://h42a4q.natappfree.cc/ 体验,账号:user,密码:user。
git 地址:https://github.com/Sherhang/incubator-superset/tree/zh/
superset集成echarts_第1张图片
superset集成echarts_第2张图片

前言

目前superset官方项目处于快速迭代中,版本的差异很大,目前发布的稳定版为0.36,所以改造主要基于0.36版本。另外,目前的master分支对元数据库结构已经做了改动,不能向前兼容。

目前superset的前端用d3和nvd3来做,很多图表不符合中国人的使用习惯,d3的部分图表性能很差,所以必须进行定制化。

集成echarts

echarts官方link,

superset使用最新版本0.36,echarts4.7.0。主要参考link。这份教程给出的柱状折线图还不能达到实用的需求,比如格式调整,样式调整,字段分组等功能都没有做,后面我会把能实用的教程补齐。

集成echarts柱状折线图 mix-line-bar

这个柱状折线图完成的功能不全,仅仅帮助大家理解superset图表从后端到前端的渲染流程

流程

集成echarts柱状折线图 mix-line-bar
superset和echarts版本
前端目录 superset-frontend
首先0.36版本是比较新的版本,代码结构相比 0.30以前的改动还是比较大的,主要是前端的代码结构变化比较大, superset 把前端的插件单独放在一个superset-ui的项目中;superset中的前端代码主要放在superset-frontend的目录中

主要修改的地方

1、 superset-frontend/src/visualizations/ 目录下

1-1 新增文件夹MixLineBar,
主要新加的文件目录

1-2 新建文件夹 images 放入新增图表的图片
图片在echarts 上可以下载
https://www.echartsjs.com/examples/zh/index.html

选择你要接入的图表,然后右键另存为 就可以下载下来了
然后放在 images 文件下,可以转化为png

1-3 新增文件 MixLineBarChartPlugin.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('Mix Line Bar'),
  description: '',
  credits: ['https://www.echartsjs.com/examples/en/editor.html?c=mix-line-bar'],
  thumbnail,
});

export default class MixLineBarChartPlugin extends ChartPlugin {
  constructor() {
    super({
      metadata,
      transformProps,
      loadChart: () => import('./ReactMixLineBar.js'), // 前端渲染逻辑
    });
  }
}


1-4 新增文件 ReactMixLineBar.js 注册

import reactify from '@superset-ui/chart/esm/components/reactify';
import Component from './MixLineBar';

export default reactify(Component);

1-5 新增文件 transformProps.js 前端后端数据转换

export default function transformProps(chartProps) {

    const {width, height, queryData, formData} = chartProps;
    // formData 前端页面的数据
    // queryData  后端返回的数据
    return {
        data: queryData.data,
        width,
        height,
        formData,
        legend: queryData.data.legend,
        x_data: queryData.data.x_data,
        series: queryData.data.data,
    };

}

1-6 新增文件 MixLineBar.js 前端渲染图表主要逻辑

import echarts from 'echarts';
import d3 from 'd3';
import PropTypes from 'prop-types';
import { CategoricalColorNamespace } from '@superset-ui/color';

// 数据类型检查
const propTypes = {
    data: PropTypes.object,
    width: PropTypes.number,
    height: PropTypes.number,
};

function MixLineBar(element, props) {

    const {
        width,
        height,
        data,
        formData,
        x_data,
        series,
        legend,
    } = props; // transformProps.js 返回的数据

    const fd = formData
    // 配置y轴显示信息
    const left_y_min = fd.leftYMIn
    const left_y_max = fd.leftYMax
    const left_y_interval = fd.leftYInterval
    const right_y_min = fd.rightYMin
    const right_y_max = fd.rightYMax
    const right_y_interval = fd.rightYInterval
    // y轴别名
    const y_axis_label = fd.yAxisLabel
    const y_axis_2_label = fd.yAxis2Label

    // 右边y轴 对应的 指标列
    const right_y_column = fd.rightYColumn
    // 为了适配颜色
    const colorFn = CategoricalColorNamespace.getScale(fd.colorScheme);
    var colors = []
    if (colorFn && colorFn.colors) {
        colors = colorFn.colors
    }
    const colors_len = colors.length

    // y轴配置格式
    var yAxis_1 = {
        type: 'value',
        name: 'Left_Y_Axis',
        axisLabel: {
            formatter: '{value}'
        }
    }

    var yAxis_2 = {
        type: 'value',
        name: 'Right_Y_Axis',
        axisLabel: {
            formatter: '{value}'
        }
    }

    if (left_y_min !== undefined) {
        yAxis_1['min'] = left_y_min
    }
    if (left_y_max != undefined) {
        yAxis_1['max'] = left_y_max
    }
    if (left_y_interval != undefined) {
        yAxis_1['interval'] = left_y_interval
    }
    if (right_y_min != undefined) {
        yAxis_2['min'] = right_y_min
    }
    if (right_y_max != undefined) {
        yAxis_2['max'] = right_y_max
    }
    if (right_y_interval != undefined) {
        yAxis_2['interval'] = right_y_interval
    }
    if (y_axis_label != undefined){
        yAxis_1['name'] = y_axis_label
    }
    if (y_axis_2_label != undefined){
        yAxis_2['name'] = y_axis_2_label
    }

    // 处理series 显示的数据 [{'name':xx, 'type':xx, 'data':xx, 'yAxisIndex':xx}]
    // 重新请求时, 默认展示左y,
    for (let i = 0; i < series.length; i++) {
        var serie = series[i]
        serie['yAxisIndex'] = 0
        if (right_y_column != undefined && right_y_column.indexOf(serie.name) >= 0) {
            serie['yAxisIndex'] = 1
        }
        if(colors_len>0){
            serie['itemStyle'] = {
                'color': colors[i%colors_len]
            }
        }
    }


    const div = d3.select(element);
    const sliceId = 'mix-bar-line-' + fd.sliceId;
    const html = '
+ height + 'px; width:' + width + 'px;">
'
; div.html(html); // init echarts,light 为制定主题,可以查看官方api var myChart = echarts.init(document.getElementById(sliceId), 'light'); // echarts 渲染图表的数据格式 在官网可以查看 var option = { tooltip: { trigger: 'axis', axisPointer: { type: 'cross', crossStyle: { color: '#999' } } }, legend: { data: legend, //[] x轴的数据 }, xAxis: [ { type: 'category', data: x_data, axisPointer: { type: 'shadow' } } ], yAxis: [ yAxis_1, yAxis_2, ], series: series, }; myChart.setOption(option); } MixLineBar.displayName = 'Mix Line Bar'; MixLineBar.propTypes = propTypes; export default MixLineBar;

2、 修改 superset-frontend/src/visualizations/presets/MainPreset.js

// 开头导入
import MixLineBarChartPlugin from '../MixLineBar/MixLineBarChartPlugin'

// 末尾添加
new MixLineBarChartPlugin().configure({ key: 'mix_line_bar' }),

3、 修改 superset-frontend/src/explore/components/controls/VizTypeControl.jsx

 //找到 DEFAULT_ORDER 这个变量 数组末尾 添加 新图表
 
 'mix_line_bar',

4、新增 superset-frontend/src/explore/controlPanels/MixLineBar.js

前端页面布局

/**
 *   https://www.echartsjs.com/examples/zh/editor.html?c=mix-line-bar
 *   mix line bar
 */
import { t } from '@superset-ui/translation';


export default {
    requiresTime: true,
    controlPanelSections: [
        {
            label: t('Chart Options'),
            expanded: true,
            controlSetRows: [
                ['color_scheme', 'label_colors'],
            ],
        },

        {
            label: t('X Axis'),
            expanded: true,
            controlSetRows: [
                ['groupby'],
            ],
        },
        {
            label: t('Line Type'),
            expanded: true,
            controlSetRows: [
                ['line_metrics'],
            ],
        },
        {
            label: t('Bar Type'),
            expanded: true,
            controlSetRows: [
                ['bar_metrics'],
            ],
        },
        {
            label: t('Real Y Axis 2 Display Columns'),
            expanded: true,
            controlSetRows: [
                ['right_y_column'],
            ],
        },

        {
            label: t('Y Axis 1 Scale Value Setting'),
            expanded: true,
            controlSetRows: [
                ['left_y_min', 'left_y_max', 'left_y_interval'],
                ['y_axis_label']
            ],
        },
        {
            label: t('Y Axis 2 Scale Value Setting'),
            expanded: true,
            controlSetRows: [
                ['right_y_min', 'right_y_max', 'right_y_interval'],
                ['y_axis_2_label']
            ],
        },
        {
            label: t('Query'),
            expanded: true,
            controlSetRows: [
                ['adhoc_filters'],
            ],
        },

    ],
    controlOverrides: {

    },
};


5、修改 superset-frontend/src/explore/controls.jsx 新增的一些自定义组件

// 后面的是注释 如有影响请删掉

  line_metrics: {
    ...metrics, // 继承
    multi: true, // 多选
    clearable: true, // 是否可调用, true当作sql
    validators: [], // 是否可以为空
    label: t('Line Type Metrics'),
    description: t('Metrics for which line type are to be displayed'),
  },

  bar_metrics: {
    ...metrics,
    multi: true,
    clearable: true,
    validators: [],
    label: t('Bar Type Metrics'),
    description: t('Metrics for which bar type are to be displayed'),
  },

  y_metrics_2: {
    ...metrics, 
    multi: true,
    validators: [],
    default:null,
    label: t('Y Axis 2 Columns'),
    description: t('Select the numeric columns to display in Right-Y-Axis'),
  },
    left_y_min: {
    type: 'TextControl', //文本输入
    label: t('Left Y Min'),
    renderTrigger: true,
    isInt: true,
    description: t('Left Y Min'),
  },
  left_y_max: {
    type: 'TextControl',
    label: t('Left Y Max'),
    renderTrigger: true,
    isInt: true,
    description: t('Left Y Max'),
  },
  left_y_interval: {
    type: 'TextControl',
    label: t('Left Y Interval'),
    renderTrigger: true,
    isInt: true,
    description: t('Left Y Interval'),
  },
  right_y_min: {
    type: 'TextControl',
    label: t('Right Y Min'),
    renderTrigger: true,
    isInt: true,
    description: t('Right Y Min'),
  },
  right_y_max: {
    type: 'TextControl',
    label: t('Right Y Max'),
    renderTrigger: true,
    isInt: true,
    description: t('Right Y Max'),
  },
  right_y_interval: {
    type: 'TextControl',
    label: t('Right Y Interval'),
    renderTrigger: true,
    isInt: true,
    description: t('Right Y Interval'),
  },
  y_axis_2_label: {
    type: 'TextControl',
    label: t('Y Axis 2 Label'),
    renderTrigger: true,
    default: '',
  },
  
 right_y_column: {
    type: 'SelectControl',
    freeForm: true,
    renderTrigger: true,
    multi: true,
    label: t('Y Axis 2 Column'),
    description: t('Choose or add metrics (label) to display in right y axis'),
  },


6. 修改 setupPlugins.ts

superset-frontend/src/setup/setupPlugins.ts

// 开头引入
import MixLineBar from '../explore/controlPanels/MixLineBar';

// 末尾注册
.registerValue('mix_line_bar', MixLineBar)


7、修改package.json

// 新增引入 echarts 版本
"echarts": "^4.7.0"

8. 后端 py 修改 superset/viz.py

图表处理的逻辑都在这个文件中

1、修改地方 找到 METRIC_KEYS 数组后 添加2个字符串(自定义的组件)

"line_metrics", "bar_metrics",

2、修改地方,新增新图表后端逻辑

class MixLineBarViz(NVD3Viz):
    """ mix line bar"""
    viz_type = "mix_line_bar"
    verbose_name = _("Mix Line Bar")
    # 是否排序
    sort_series = False
    # 是否对time 做处理 _timestamp
    is_timeseries = False

    def query_obj(self):
        # check bar column, line column 是否重复
        bar_metrics = self.form_data.get('bar_metrics')
        line_metrics = self.form_data.get('line_metrics')
        if not bar_metrics and not line_metrics:
            raise Exception(_("Please choose metrics on line or bar type"))
        bar_metrics = [] if not bar_metrics else bar_metrics
        line_metrics = [] if not line_metrics else line_metrics
        intersection = [m for m in bar_metrics if m in line_metrics]
        if intersection:
            raise Exception(_("Please choose different metrics on line and bar type"))
        d = super().query_obj()
        return d

    def to_series(self, df, classed=""):
        """
         拼接 前端渲染需要的数据
        :param df:
        :param classed:
        :return: {'legend':[], 'bar':[], 'line':[]}
        """
        cols = []
        for col in df.columns:
            if col == "":
                cols.append("N/A")
            elif col is None:
                cols.append("NULL")
            else:
                cols.append(col)
        df.columns = cols
        series = df.to_dict("series")
        # [{}]
        bar_metrics = self.form_data.get('bar_metrics', [])
        bar_metrics = [] if not bar_metrics else bar_metrics
        line_metrics = self.form_data.get('line_metrics', [])
        line_metrics = [] if not line_metrics else line_metrics

        metrics = self.all_metrics
        legend, data = [], []
        for mt in metrics:
            m_label = utils.get_metric_name(mt)
            ys = series[m_label]
            if df[m_label].dtype.kind not in "biufc":
                continue
            legend.append(m_label)
            info = {
                "name": m_label,
                "data": [
                    ys.get(ds, None) for ds in df.index
                ],
                "type": ''
            }
            if mt in bar_metrics:
                info['type'] = 'bar'
            elif mt in line_metrics:
                info['type'] = 'line'
            else:
                continue
            data.append(info)
        chart_data = {
            'legend': legend,
            'data': data,
            'x_data': [str(ds) if not isinstance(ds, tuple) else ','.join(map(str, ds)) for ds in df.index]
        }

        return chart_data

    def get_data(self, df: pd.DataFrame):
        # 后端返回的数据
        df = df.pivot_table(index=self.groupby, values=self.metric_labels)
        chart_data = self.to_series(df)
        return chart_data


echarts mix-line-bar 图表字段理解
官方 构建新图表option 例子
https://www.echartsjs.com/examples/zh/editor.html?c=mix-line-bar

echarts 配置手册 options 参数
https://www.echartsjs.com/zh/option.html#series-bar

echarts api文档
https://www.echartsjs.com/zh/api.html#echarts
——————————————————————————————————————————

严格按照教程执行,最后添加echarts可以直接用npm 安装一下这一个包就可以,npm install [email protected]

最后

superset init 
superset run 
npm run buuild
npm run dev

汉化与问题记录

  • 问题记录

    1. expore报错:xpected string or bytes-like object
    2. Mix Line Bar:返回 ‘mix_line_bar’
    3. 点击An error occurred while loading the SQL,报错An error occurred while loading the SQL

    1和3问题解决:数据源里面不能有全是NULL的列。

    问题3:我把class MixLineBarViz(NVD3Viz):加错位置了,不能加在末尾,因为末尾有个注册,要加在incubator-superset\superset\viz.py 和其它类同级的地方,约2890行。

  • 问题记录
    选择图表类型发现新增的图表比例不一样,用win自带的画图3D编辑,把画布改成512*512即可。

    新图表名称修改:superset/viz.py + 2984: verbose_name = _(“Mix Line Bar”)
    我们仍然用国际化的方案。在translations/zh/文件夹下找到message.*三个文件

    message.json末尾(这个文件其实也可以用 po2json messages.po messages.json -f jed1.x -p 来生成。

             "Line Width": [
                "线宽"
             ],
             "Mix Line Bar":[
                "柱状折线图"
             ]
    

    message.po末尾添加,我这里顺便把源码没有完成的汉化注释取消了,以后需要手动在代码里添加了。

    源码在 5176行开始都是没有完成的。

    #: src\visualizations\MixLineBar\MixLineBarChartPlugin.js:8
    msgid "Mix Line Bar"
    msgstr "柱状折线图"
    
    #: src\explore\controls.jsx:1969  src\explore\controlPanels\MixLineBar.js:27
    msgid "Line Type"
    msgstr "折线图"
    
    #: src\explore\controls.jsx:1969
    msgid "Line Type Metrics"
    msgstr "折线图字段"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controlPanels\MixLineBar.js:34
    msgid "Bar Type"
    msgstr "柱状图"
    
    #: src\explore\controls.jsx:1978
    msgid "Bar Type Metrics"
    msgstr "柱状图字段"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controlPanels\MixLineBar.js:41
    msgid "Real Y Axis 2 Display Columns"
    msgstr "Y2轴显示的列"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:1987,2044
    msgid "Y Axis 2 Column"
    msgstr "选择Y2轴的列"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controlPanels\MixLineBar.js:49
    msgid "Y Axis 1 Scale Value Setting"
    msgstr "Y轴标度值设置"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:1992,1995
    msgid "Left Y Min"
    msgstr "Y轴下边界"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:1999,2002
    msgid "Left Y Max"
    msgstr "Y轴上边界"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:2006,2009
    msgid "Left Y Interval"
    msgstr "Y轴比例尺间隔"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controlPanels\MixLineBar.js:57
    msgid "Y Axis 2 Scale Value Setting"
    msgstr "Y2轴标度值设置"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:2013,2016
    msgid "Right Y Min"
    msgstr "Y2轴下边界"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:2020,2023
    msgid "Right Y Max"
    msgstr "Y2轴上边界"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:2027,2030
    msgid "Right Y Interval"
    msgstr "Y2轴比例尺间隔"
    
    #:incubator-superset-0.36\superset-frontend\src\explore\controls.jsx:2034
    msgid "Y Axis 2 Label"
    msgstr "Y2轴标签"
    

    回到superset文件夹,在env0.36虚拟环境下运行

    pybabel compile -d translations  # 汉化
    cd translations/zh/LC_MESSAGES
    po2json messages.po messages.json -f jed1.x -p   # -f -p 参数可以参考官网
    

    然后 superset init,superset run, npm run build, npm run dev即可。

    优化:incubator-superset-0.36\superset-frontend\src\visualizations\MixLineBar\MixLineBar.js添加保存等动作toolbox。

     // echarts 渲染图表的数据格式 在官网可以查看
        
        var option = {
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'cross',
                    crossStyle: {
                        color: '#999'
                    }
                }
            },
            toolbox: {
                feature: {
                    dataView: {show: true, readOnly: false},
                    magicType: {show: true, type: ['line', 'bar']},
                    restore: {show: true},
                    saveAsImage: {show: true}
                }
            },
    

    问题记录:图例不支持自定义,且改了中文标签依然显示的实例英文的SQL表达式 。
    解决:点击simple上面的编辑按钮,这里填入你想显示的标签。

    问题记录:Y2轴无法选择要显示的列。
    解决方案:并非bug,这里需要你把需要使用的列手动输入一个SQL表达式:比如在选择了折线图有个字段SUM(a),想把它对齐右侧Y轴,那就在自定义那里填上去。

你可能感兴趣的:(superset,python)