superset集成Echarts的地图并实现省市区下钻

如有需要,我可以给大家发代码包,评论区留下你的邮箱地址

第一步:左侧控制栏开发

查询、过滤等等条件配置配置在此文件夹中
在这里插入图片描述

/*
 * @FilePath: /biSuperset/superset-frontend/src/explore/controlPanels/EchartsMap.js
 */
import {
  t,
  // validateNonEmpty
} from '@superset-ui/translation';
import { sections } from '@superset-ui/chart-controls';

export default {
  controlPanelSections: [
    sections.legacyRegularTime,
    {
      label: t('Query'),
      expanded: true,
      controlSetRows: [
        ['groupby'],
        ['metrics'],
        ['adhoc_filters'],
        ['row_limit'],
      ],
    },
  ],
  controlOverrides: {
    groupby: {
      label: '省字段(必须与表字段省字段相匹配)',
      description: '',
      // TODO 此处需要放开
      // validators: [validateNonEmpty],
    },
    metric: {
      label: t('Metric'),
      description: '',
      // TODO 此处需要放开
      // validators: [validateNonEmpty],
    },
    time_grain_sqla: {
      default: null,
    },
  },
};

第二步:左侧栏注册

位置:explore/components/controls/VizTypeControl/VizTypeGallery
参照案例即可

'echarts_map',

第三步:右侧渲染层开发

EchartsMap.js (主要渲染文件)

/* eslint-disable array-callback-return */
import echarts from 'echarts';
// eslint-disable-next-line import/no-extraneous-dependencies
import d3 from 'd3';
import PropTypes from 'prop-types';
import 'echarts/map/js/china';
import { cityJson, provinceJson } from './map';

// TODO 待优化
// 数据类型检查
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);

  const myChart = echarts.init(document.getElementById(sliceId));
  document.oncontextmenu = function () {
    return false;
  }; // 取消浏览器的邮件点击事件

  const groupbys = formData.groupby;
  const { metrics } = formData;
  let lab = 1;
  let dataValue = data[groupbys[0]];
  const dataName = new Set();

  function getSeries(type) {
    const result = [];
    dataName.clear();
    metrics.map(opt => {
      dataValue.map(datax => {
        if (
          datax.name === opt ||
          JSON.stringify(datax.name) === JSON.stringify(opt)
        ) {
          datax.data.map(item => {
            dataName.add(item.name);
          });
          const 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;
  }

  let option = {
    title: {
      subtext: '点击进入下一级,右键返回中国地图',
      x: 'center',
      bottom: '5%',
    },
    tooltip: {
      trigger: 'item',
      formatter(params) {
        let res = `${params.name}<br/>`;
        const myseries = option.series;
        for (let i = 0; i < myseries.length; i += 1) {
          for (let j = 0; j < myseries[i].data.length; j += 1) {
            if (myseries[i].data[j].name === params.name) {
              let field = myseries[i].name;
              if (typeof field === 'object') {
                field = field.column.column_name;
              }
              res += `${field} : ${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);

  // 异步加载其余地图
  provinceJson.map(item => {
    $.getJSON(
      `http://${window.document.location.host}/static/assets/echarts/province/${item.label}.json`,
      function (res) {
        echarts.registerMap(item.name, res);
      },
    );
  });

  // 用点击事件来切换地图实现下钻功能,该省份有值时才可以下钻
  myChart.on('click', function (chinaParam) {
    if (
      chinaParam.name === '香港' ||
      chinaParam.name === '澳门' ||
      chinaParam.name === '台湾' ||
      chinaParam.name === '南海诸岛'
    ) {
      return;
    }
    // TODO XXXXX
    // if (dataName.has(chinaParam.name) && lab === 1 && groupbys.length > 1) {
    if (lab === 1) {
      lab = 2;
      // dataValue = data[groupbys[1]];
      dataValue = data[groupbys[0]];
      // ! TODO 为了简易的显示图表而注释,此处可以安接口方式,走返回不同的层级的数据结构

      // let resultValue = [];
      // dataValue.map(item => {
      //     let midValue = [];
      //     item['data'].map(opt => {
      //         if (opt['key'] === chinaParam.name) {
      //             midValue.push({ 'name': opt['name'], 'value': opt['value'] })
      //         }
      //     });
      //     resultValue.push({ 'name': item['name'], 'data': midValue })
      // });

      // dataValue = resultValue;
      option = myChart.getOption();
      option.series = getSeries(chinaParam.name);
      myChart.setOption(option);
      // TODO 三级目录暂未开发,考虑到公司不需要地级县
      cityJson.map(item => {
        if (item.dep === chinaParam.name) {
          $.getJSON(
            `http://${window.document.location.host}/static/assets/echarts/citys/${item.value}.json`,
            function (res) {
              echarts.registerMap(item.name, res);
            },
          );
        }
      });
    }

    if (dataName.has(chinaParam.name) && lab === 2 && groupbys.length > 2) {
      lab = 3;
      dataValue = data[groupbys[2]];
      const resultValue = [];
      dataValue.map(item => {
        const midValue = [];
        item.data.map(opt => {
          if (opt.key === chinaParam.name) {
            midValue.push({ name: opt.name, value: opt.value });
          }
        });
        resultValue.push({ name: item.name, data: midValue });
      });

      dataValue = resultValue;
      option = myChart.getOption();
      option.series = getSeries(chinaParam.name);
      myChart.setOption(option);
    }
  });

  // 用双击事件来返回最上层的中国地图,当不在中国地图时生效
  myChart.on('contextmenu', function () {
    if (myChart.getOption().series[0].map !== 'china') {
      lab = 1;
      dataValue = data[groupbys[0]];
      option = myChart.getOption();
      option.series = getSeries('china');
      myChart.setOption(option);
    }
  });
}

EchartsMap.displayName = 'Echarts Map';
EchartsMap.propTypes = propTypes;

export default EchartsMap;

**EchartsMapChartPlugin.js ** 插件文件

/*
 * @Author: bo.lin
 * @Date: 2021-10-14 14:11:25
 * @LastEditors: lijuan.sun
 * @LastEditTime: 2021-10-21 10:35:45
 * @PageTitle: XXX页面
 * @Description: XXX
 * @FilePath: /biSuperset/superset-frontend/src/visualizations/EchartsMap/EchartsMapChartPlugin.js
 */

import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
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,
  useLegacyApi: true,
});

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

map.js 地图文件

/*
 * @Author: lijuan.sun
 * @Date: 2021-10-20 16:46:43
 * @LastEditors: lijuan.sun
 * @LastEditTime: 2021-10-26 12:54:09
 * @PageTitle: 页面...
 * @Description: 描述...
 * @FilePath: /biSuperset/superset-frontend/src/visualizations/EchartsMap/map.js
 */
const provinceJson = [
  { name: '安徽', value: 110000, label: 'anhui' },
  { name: '澳门', value: 820000, label: 'aomen' },
  { name: '北京', value: 110000, label: 'beijing' },
  { name: '重庆', value: 110000, label: 'chongqing' },
  { name: '福建', value: 110000, label: 'fujian' },
  { name: '甘肃', value: 110000, label: 'gansu' },
  { name: '广东', value: 110000, label: 'guangdong' },
  { name: '广西', value: 810000, label: 'guangxi' },
  { name: '贵州', value: 810000, label: 'guizhou' },
  { name: '海南', value: 810000, label: 'hainan' },
  { name: '湖北', value: 120000, label: 'hebei' },
  { name: '黑龙江', value: 810000, label: 'heilongjiang' },
  { name: '河南', value: 810000, label: 'henan' },
  { name: '湖北', value: 810000, label: 'hubei' },
  { name: '湖南', value: 120000, label: 'hunan' },
  { name: '江苏', value: 810000, label: 'jiangsu' },
  { name: '江西', value: 810000, label: 'jiangxi' },
  { name: '吉林', value: 810000, label: 'jilin' },
  { name: '辽宁', value: 120000, label: 'liaoning' },
  { name: '内蒙古', value: 810000, label: 'neimenggu' },
  { name: '宁夏', value: 810000, label: 'ningxia' },
  { name: '青海', value: 810000, label: 'qinghai' },
  { name: '山东', value: 120000, label: 'shandong' },
  { name: '上海', value: 810000, label: 'shanghai' },
  { name: '陕西', value: 810000, label: 'shanxi1' },
  { name: '山西', value: 810000, label: 'shanxi' },
  { name: '四川', value: 810000, label: 'sichuan' },
  { name: '台湾', value: 120000, label: 'taiwan' },
  { name: '天津', value: 810000, label: 'tianjin' },
  { name: '香港', value: 810000, label: 'xianggang' },
  { name: '新疆', value: 810000, label: 'xinjiang' },
  { name: '西藏', value: 810000, label: 'xizang' },
  { name: '云南', value: 120000, label: 'yunnan' },
  { name: '浙江', value: 810000, label: 'zhejiang' },
];
// TODO 此处暂时未使用到,如果需要三级,需要在此处新增以及寻找相关城市的json
const cityJson = [
  { name: '石家庄市', value: 130100, dep: '河北' },
  { name: '唐山市', value: 130200, dep: '河北' },
  { name: '秦皇岛市', value: 130300, dep: '河北' },
  { name: '可克达拉市', value: 659008, dep: '新疆' },
  { name: '昆玉市', value: 659009, dep: '新疆' },
];

export { cityJson, provinceJson };

ReactEchartsMap.js

/*
 * @Author: bo.lin
 * @Date: 2021-10-14 14:14:10
 * @LastEditors: lijuan.sun
 * @LastEditTime: 2021-10-21 10:36:57
 * @PageTitle: XXX页面
 * @Description: XXX
 * @FilePath: /biSuperset/superset-frontend/src/visualizations/EchartsMap/ReactEchartsMap.js
 */

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

export default reactify(Component);

transformProps.js

/*
 * @Author: lijuan.sun
 * @Date: 2021-10-19 11:44:59
 * @LastEditors: lijuan.sun
 * @LastEditTime: 2021-10-21 11:03:33
 * @PageTitle: 页面...
 * @Description: 描述...
 * @FilePath: /biSuperset/superset-frontend/src/visualizations/EchartsMap/transformProps.js
 */
export default function transformProps(chartProps) {
  // TODO XXXXX
  const { width, height, queriesData, formData } = chartProps;
  // formData 前端页面的数据
  // queryData  后端返回的数据
  return {
    data: queriesData[0].data,
    width,
    height,
    formData,
  };
}

第四步:右侧模块注册

assets/src/visualizations/presets/MainPreset.js 参照其它案例即可完成

import EchartsMapChartPlugin from '../EchartsMap/EchartsMapChartPlugin';
new EchartsMapChartPlugin().configure({ key: 'echarts_map' }),

第五步:修改package.json(必须)

"echarts": "^4.7.0",

第六步: 后端开发

superset/viz.py

class EchartsMapViz(NVD3Viz):
    """ echarts map"""
    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']
        # 模仿country
        if not metrics:
            raise QueryObjectValidationError("Must specify a metric")
        if not groupbys:
            raise QueryObjectValidationError("Must provide ISO codes")
        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+ '_' +str(metric)] = df

    def get_data(self, df):
        tmp_df = 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:
                tmp_df = self.dataframes.get(groupby+ '_' +str(metric))
                list = tmp_df.itertuples(index=False)
                if index == 1:
                    data = [
                        {'name': row[0], 'value': row[1]} for row in list
                    ]
                    mid_max = 0
                    # 新增data是否为空的判断
                    if len(data):
                        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

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 39
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值