Superset 地图(下钻)开发
1, 左侧控制栏开发
新增 assets/src/explore/controlPanels/EchartsMap.js
import { t } from '@superset-ui/translation';
export default {
controlPanelSections: [
{
label: t('GROUP BY'),
expanded: true,
controlSetRows: [
['groupby'],
['metrics'],
['percent_metrics'],
['include_time'],
['timeseries_limit_metric', 'order_desc'],
],
},
{
label: t('NOT GROUPED BY'),
description: t('Use this section if you want to query atomic rows'),
controlSetRows: [
['all_columns'],
['order_by_cols'],
],
},
{
label: t('Options'),
controlSetRows: [
['table_timestamp_format'],
['row_limit', 'page_length'],
['include_search', 'table_filter'],
],
}
],
controlOverrides: {
metrics: {
validators: [],
},
time_grain_sqla: {
default: null,
},
}
};
2, 左侧栏注册
assets/src/explore/controlPanels/index.js
参照案例即可
3, 右侧渲染层开发
EchartsMap.js (主要渲染文件)
import echarts from 'echarts';
import d3 from 'd3';
import PropTypes from 'prop-types';
import china from 'echarts/map/js/china'
import {city_json, province_json} from './map'
// 数据类型检查
const propTypes = {
width: PropTypes.number,
height: PropTypes.number,
};
function EchartsMap(element, props) {
const {
width,
height,
data,
formData
} = props; // transformProps.js 返回的数据
const div = d3.select(element);
const sliceId = 'echarts-map-' + 10;
const html = '<div id="' + sliceId + '" style="height:' + height + 'px; width:' + width + 'px;border:1px"></div>';
div.html(html);
let myChart = echarts.init(document.getElementById(sliceId), 'light');
document.oncontextmenu = function () {
return false;
}; // 取消浏览器的邮件点击事件
let groupbys = formData['groupby'];
let metrics = formData['metrics'];
let lab = 1;
let data_value = data[groupbys[0]];
let data_name = new Set();
let option = {
title: {
subtext: '点击进入下一级,右键返回中国地图',
x: 'center',
bottom: '5%'
},
tooltip: {
trigger: 'item',
formatter: function (params) {
let res = params.name + '<br/>';
let myseries = option.series;
for (let i = 0; i < myseries.length; i++) {
for (let j = 0; j < myseries[i].data.length; j++) {
if (myseries[i].data[j].name == params.name) {
res += myseries[i].name + ' : ' + myseries[i].data[j].value + '</br>';
}
}
}
return res;
}
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: {readOnly: false},
restore: {},
saveAsImage: {}
}
},
visualMap: {
//type: 'continuous',
min: 0,
max: data['max'],
text: ['高', '低'],
realtime: false,
calculable: true,
//right:'-15%',
inRange: {
color: ['#d0f4fc',
'#a9dbf6',
'#9cd3f4',
'#93cdf3',
'#83c2f0',
'#6eb5ed',
'yellow']
}
},
series: getSeries('china')
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
// 异步加载其余地图
province_json.map(item => {
$.getJSON('http://' + window.document.location.host + '/static/assets/spec/echarts/province/' + item['value'] + '.json', function (res) {
echarts.registerMap(item['name'], res);
})
});
//用点击事件来切换地图实现下钻功能,该省份有值时才可以下钻
myChart.on('click', function (chinaParam) {
if (data_name.has(chinaParam.name) && lab === 1 && groupbys.length > 1) {
lab = 2;
data_value = data[groupbys[1]];
let result_value = [];
data_value.map(item => {
let midValue = [];
item['data'].map(opt => {
if (opt['key'] === chinaParam.name) {
midValue.push({'name': opt['name'], 'value': opt['value']})
}
});
result_value.push({'name': item['name'], 'data': midValue})
});
data_value = result_value;
option = myChart.getOption();
option.series = getSeries(chinaParam.name);
myChart.setOption(option);
city_json.map(item => {
if (item['dep'] === chinaParam.name) {
$.getJSON('http://' + window.document.location.host + '/static/assets/spec/echarts/citys/' + item['value'] + '.json', function (res) {
echarts.registerMap(item['name'], res);
})
}
})
}
if (data_name.has(chinaParam.name) && lab === 2 && groupbys.length > 2) {
lab = 3;
data_value = data[groupbys[2]];
let result_value = [];
data_value.map(item => {
let midValue = [];
item['data'].map(opt => {
if (opt['key'] === chinaParam.name) {
midValue.push({'name': opt['name'], 'value': opt['value']})
}
});
result_value.push({'name': item['name'], 'data': midValue})
});
data_value = result_value;
option = myChart.getOption();
option.series = getSeries(chinaParam.name);
myChart.setOption(option);
}
});
//用双击事件来返回最上层的中国地图,当不在中国地图时生效
myChart.on('contextmenu', function (chinaParam) {
if (myChart.getOption().series[0].map !== 'china') {
lab = 1;
data_value = data[groupbys[0]];
option = myChart.getOption();
option.series = getSeries('china');
myChart.setOption(option);
}
});
function getSeries(type) {
let result = [];
data_name.clear();
metrics.map(opt => {
data_value.map(datax => {
if (datax['name'] === opt) {
datax['data'].map(item => {
data_name.add(item['name']);
});
let midx = {
name: datax['name'],
type: 'map',
map: type,
selectedMode: 'single',
roam: 'scale',
data: datax['data'],
label: {
normal: {
show: true,
textStyle: {color: "#b6a38a"}
},
emphasis: {
show: true,
textStyle: {color: "#ff6347"}
}
},
itemStyle: {
emphasis: {
areaColor: "#2e4783",
borderWidth: 0
}
}
};
result.push(midx);
}
})
});
return result;
}
}
EchartsMap.displayName = 'Echarts Map';
EchartsMap.propTypes = propTypes;
export default EchartsMap;
**EchartsMapChartPlugin.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('Echarts Map'),
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('./ReactEchartsMap.js'), // 前端渲染逻辑
});
}
}
map.js 地图文件
const province_json = [
{"name": '北京', value: 110000},
{"name": '天津', value: 120000},
.......
{"name": '香港', value: 810000},
{"name": '澳门', value: 820000},
];
const city_json = [
{'name': '石家庄市', 'value': 130100, 'dep': '河北'},
{'name': '唐山市', 'value': 130200, 'dep': '河北'},
{'name': '秦皇岛市', 'value': 130300, 'dep': '河北'},
.....
{'name': '可克达拉市', 'value': 659008, 'dep': '新疆'},
{'name': '昆玉市', 'value': 659009, 'dep': '新疆'}
];
export {city_json, province_json}
ReactEchartsMap.js
import reactify from '@superset-ui/chart/esm/components/reactify';
import Component from './EchartsMap';
export default reactify(Component);
transformProps.js 数据传递的js
export default function transformProps(chartProps) {
const {width, height, payload, formData} = chartProps;
// formData 前端页面的数据
// queryData 后端返回的数据
return {
data: payload.data,
width,
height,
formData,
};
}
4, 右侧模块注册
assets/src/visualizations/presets/MainPreset.js 参照其它案例即可完成
5, 修改package.json
"echarts": "^4.7.0",
6, 后端开发
viz.py 添加如下内容
class EchartsMap(NVD3Viz):
""" echarts map viz """
viz_type = 'echarts_map' # 对应前端名字
verbose_name = _('echarts_map')
credits = 'a <a href="https://github.com/airbnb/superset">Superset</a> original'
is_timeseries = False
def run_extra_queries(self):
qry = super().query_obj()
metrics = qry['metrics']
groupbys = qry['groupby']
by = []
self.dataframes = {}
for groupby in groupbys:
by.append(groupby)
for metric in metrics:
qry.update({'metrics': [metric],'groupby':by})
df = self.get_df_payload(query_obj=qry).get("df")
self.dataframes[groupby+ '_' +metric] = df
def get_data(self, df):
metrics = self.form_data.get("metrics") or []
groupbys = self.form_data.get("groupby") or []
d = {}
max_num = 0
index = 0
for groupby in groupbys:
index = index + 1
midlist = []
for metric in metrics:
df = self.dataframes.get(groupby+ '_' +metric)
list = df.itertuples(index=False)
if index == 1:
data = [
{'name': row[0], 'value': row[1]} for row in list
]
mid_max = max([data[i].get('value') for i in range(len(data))])
if mid_max > max_num:
max_num = mid_max
else:
data = [
{'name': row[index-1], 'value': row[index],'key':row[index-2]} for row in list
]
midlist.append({'name': metric, 'data': data})
d.update({groupby: midlist})
d.update({'max': max_num})
return d
7, Q&A
实现echarts三级下钻,里面使用了动态加载地图数据,文件中没有提供,后续提供到下载
数据是一次加载,会有些问题,可以使用ajax异步,后面开发表格下钻时,已经实现过ajax加载数据,可以查看