superset0.36接入echarts 圖表:mix-line-bar

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'),
  },

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

結束語

本人不太會前端js,所以js文件 寫的有點醜,有大佬可以幫忙改進一下

這篇文章主要是寫了如何接入echarts 的柱狀折線圖,基本echarts 其他圖也類似

後續
1、36 版本 源碼搭建superset
2、superset 怎麼渲染圖表的 從後端到前端
3、superset 權限是怎麼控制的,以及如何把dashboard 提權
4、會寫原生table組件加載數據過多會比較慢,考慮用antd 的table 代替

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章