美文网首页
superset0.36 集成echarts

superset0.36 集成echarts

作者: Java全栈攻城狮 | 来源:发表于2020-06-16 17:21 被阅读0次

    superset和echarts版本
    superset : 0.36

    echarts : 4.7.0

    集成echarts柱状折线图 mix-line-bar
    前端目录 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['mix'] = 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['mix'] = 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 = '<div id='+ sliceId + ' style="height:' + height + 'px; width:' + width + 'px;"></div>';
        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、修改 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"
    

    后端 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 的柱状折线图,基本echarts 其他图也类似

    相关文章

      网友评论

          本文标题:superset0.36 集成echarts

          本文链接:https://www.haomeiwen.com/subject/hfprxktx.html