效果预览如下:
一、前端(superset\superset-frontend)
1、superset\superset-frontend\src\visualizations目录下,新增MixLineBar文件夹,主要文件如下图: 2、MixLineBar文件夹内容:
2.1、images文件夹:存放新增图表的预览图:thumbnail.png和thumbnailLager.png 下载地址:https://echarts.apache.org/examples/zh/editor.html?c=mix-line-bar 2.2、MixLineBar.js => 前端引入echarts进行新图表渲染
import echarts from 'echarts';
import d3 from 'd3';
import PropTypes from 'prop-types';
import { CategoricalColorNamespace } from '@superset-ui/core';
// 数据类型检查
const propTypes = {
data: PropTypes.object,
width: PropTypes.number,
height: PropTypes.number,
};
function MixLineBar(element, props) {
const { width, height, 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);
let colors = [];
if (colorFn && colorFn.colors) {
colors = colorFn.colors;
}
const colors_len = colors.length;
// y轴配置格式
const yAxis_1 = {
type: 'value',
name: 'Left_Y_Axis',
axisLabel: {
formatter: '{value}',
},
};
const 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++) {
const 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
const myChart = echarts.init(document.getElementById(sliceId), 'light');
// echarts 渲染图表的数据格式 在官网可以查看
const 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,
};
myChart.setOption(option);
}
MixLineBar.displayName = 'Mix Line Bar';
MixLineBar.propTypes = propTypes;
export default MixLineBar;
2.3、MixLineBarChartPlugin.js =>superset中渲染构造新图表
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
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,
useLegacyApi: true,
});
export default class MixLineBarChartPlugin extends ChartPlugin {
constructor() {
super({
metadata,
transformProps,
loadChart: () => import('./ReactMixLineBar.js'), // 前端渲染逻辑
});
}
}
2.4、ReactMixLineBar.js => 注册新图表
import reactify from '@superset-ui/core/esm/chart/components/reactify';
import Component from './MixLineBar';
export default reactify(Component);
2.5、transformProps.js =>前后端数据传递
export default function transformProps(chartProps) {
const { width, height, queriesData, formData } = chartProps;
// formData 前端页面的数据 queriesData 后端返回的数据
return {
data: queriesData[0].data,
width,
height,
formData,
legend: queriesData[0].data.legend,
x_data: queriesData[0].data.x_data,
series: queriesData[0].data.data,
};
}
3、superset\superset-frontend\src\visualizations\presets\MainPreset.js文件
// 开头导入
import MixLineBarChartPlugin from '../MixLineBar/MixLineBarChartPlugin';
// 末尾添加
new MixLineBarChartPlugin().configure({ key: 'mix_line_bar' }),
4、superset\superset-frontend\src\explore\components\controls\VizTypeControl\index.jsx
// 找到 DEFAULT_ORDER 变量 数组末尾添加新图表
'mix_line_bar',
5、superset\superset-frontend\src\explore\controlPanels\MixLineBar.js => superset中图表布局
/**
* https://echarts.apache.org/examples/zh/editor.html?c=mix-line-bar
* mix line bar
*/
import { t } from '@superset-ui/core';
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', ['row_limit'], ['limit']],
],
},
],
controlOverrides: { },
};
6、superset\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_label: {
type: 'TextControl',
label: t('Y Axis Label'),
renderTrigger: true,
default: '',
},
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'),
},
7、superset\superset-frontend\src\setup\setupPlugins.ts
// 开头引入
import MixLineBar from '../explore/controlPanels/MixLineBar';
// 末尾注册
.registerValue('mix_line_bar', MixLineBar)
8、superset\superset-frontend\src\explore\controlUtils文件夹下新增文件:expandControlConfig.js
import React from 'react';
import sharedControlComponents from '@superset-ui/chart-controls/lib/shared-controls/components';
import { controls as customizeControls } from '../controls';
export function expandControlType(controlType) {
if (
typeof controlType === 'string' &&
controlType in sharedControlComponents
) {
return sharedControlComponents[controlType];
}
return controlType;
}
export function expandControlConfig(control, controlOverrides = {}) {
// one of the named shared controls
if (typeof control === 'string' && control in customizeControls) {
const name = control;
return {
name,
config: { ...customizeControls[name], ...controlOverrides[name] },
};
} // JSX/React element or NULL
if (
!control ||
typeof control === 'string' ||
/* #__PURE__ */ React.isValidElement(control)
) {
return control;
} // already fully expanded control config, e.g.
if ('name' in control && 'config' in control) {
return {
...control,
config: {
...control.config,
type: expandControlType(control.config.type),
},
};
} // apply overrides with shared controls
if ('override' in control && control.name in customizeControls) {
const { name, override } = control;
return {
name,
config: { ...customizeControls[name], ...override },
};
}
return null;
}
同目录下getSectionsToRender.ts
// expandControlConfig 文件引入方式做调整
import {
ControlPanelConfig,
} from '@superset-ui/chart-controls';
import { expandControlConfig } from './expandControlConfig';
9、superset\superset-frontend目录下,安装指定版本的echarts 执行命令:npm install echarts@4.7.0 --save
二、后端(superset\superset)
1、superset\superset\viz.py
// 找到 METRIC_KEYS 变量 数组末尾添加2个字符串(自定义的组件)
"line_metrics", "bar_metrics",
2、后端注册mix-line-bar图表,注意:代码放在 def get_subclasses 之前
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
三、前后端启动联调即可
?????
|