Davinci数据可视化-新增图表类型-旭日图

Davinci 介绍

Davinci 是宜信出品的DVaaS(数据可视化及服务)的一款BI产品。
官网链接

概叙

由于网上并没有太多Davinci 教程文档,自己想新增一些其它的图表类型,对于我这个小白来说,花费了一些时间,避免一些朋友像我一样浪费时间。这里把代码贴出来,仅供参考。

最终效果图

在这里插入图片描述

新增图表类型-旭日图过程

旭日图有点特殊,所要求的数据需具备父子结构。
在这里插入图片描述
为了能够确保无限层级结构,这三个列是必不可少的。

  • id: 当前节点ID
  • pid: 当前节点父ID。0=顶级
  • isParent: 该节点下是否还有节点。该属性既是基线条件,也是递归条件。

所以要保证在已有的数据源中,用于旭日图展示的数据包含这样的结构。一般的层级、分类数据都包含类似的几个列。 不确定的是这个列名不一样。

以下一些代码,由于文件代码过多,只提供修改的片段,要自行对比位置修改加入。注意别搞错了哈。

新增可视化类型

修改 webapp/app/containers/View/constants.ts

export enum ViewModelVisualTypes {
  Number = 'number',
  String = 'string',
  Date = 'date',
  GeoCountry = 'geoCountry',
  GeoProvince = 'geoProvince',
  GeoCity = 'geoCity',

  //新加入
  TreeNodeId = 'treeNodeId', //节点ID
  TreeParentId = 'treeParentId', //父节点ID
  TreeIsParent = "treeIsParent" //是否拥有子节点
}

export const ViewModelVisualTypesLocale = {
  [ViewModelVisualTypes.Number]: '数字',
  [ViewModelVisualTypes.String]: '字符',
  [ViewModelVisualTypes.Date]: '日期',
  [ViewModelVisualTypes.GeoCountry]: '地理国家',
  [ViewModelVisualTypes.GeoProvince]: '地理省份',
  [ViewModelVisualTypes.GeoCity]: '地理城市',
  //新加入
  [ViewModelVisualTypes.TreeNodeId]:   '树-节点ID',
  [ViewModelVisualTypes.TreeParentId]: '树-父节点ID',
  [ViewModelVisualTypes.TreeIsParent]: '树-是否拥有子节点',
}

修改 webapp/app/containers/Widget/render/chart/util.ts (后面会用到)

//新加入
//确定列名、确定属性角色
export function treeKeyUnknownToknown(cols,model) {
  let idKey,pidKey,ispidKey,nameKey;
  cols.forEach((col) => {
    const { visualType } = model[col.name]
    if ( ViewModelVisualTypes.TreeParentId == visualType ){
      pidKey = col.name
    }
    else if ( ViewModelVisualTypes.TreeIsParent == visualType ){
      ispidKey = col.name
    }else if ( ViewModelVisualTypes.TreeNodeId == visualType ){
      idKey = col.name
    }else {
      nameKey = col.name
    }
  })
  return{
    idKey,pidKey,ispidKey,nameKey
  }
}

效果图
在这里插入图片描述
在新增view的时候,通过选择可视化类型来告诉程序这每个属性对应着的角,方便后面解析数据。

新增图表类型

修改 webapp/app/containers/Widget/config/chart/ChartTypes.ts

  //新加入
  /**
   * 旭日图
   */
  Sunburst = 20 ,

修改 webapp/app/containers/Widget/config/chart/index.tsx 这里参照该文件导入导出就行。

新增 webapp/app/containers/Widget/config/chart/sunburst.ts
图表类型的配置,包含一些默认样式、图表规范…

import ChartTypes from './ChartTypes'
import {IChartInfo} from "containers/Widget/components/Widget";
import {
  CHART_LABEL_POSITIONS,
  PIVOT_CHART_FONT_FAMILIES,
  PIVOT_DEFAULT_AXIS_LINE_COLOR,
  PIVOT_DEFAULT_FONT_COLOR,
  PIVOT_DEFAULT_HEADER_BACKGROUND_COLOR
} from "app/globalConstants";
import {monitoredSyncDataAction} from "containers/Dashboard/actions";

const sunburst:IChartInfo = {
  id: ChartTypes.Sunburst,
  name: 'sunburst',
  title: '旭日图',
  icon: 'icon-xuri',
  coordinate: 'cartesian',
  rules: [{ dimension: [3,4] , metric: [1,1] }],
  dimetionAxis: 'col',
  data: {
    cols: {
      title: '列',
      type: 'category'
    },
    rows: {
      title: '行',
      type: 'category'
    },
    metrics: {
      title: '指标',
      type: 'category'
    },
    filters: {
      title: '筛选',
      type: 'all'
    },
  },
  style: {
    sunburst: {
      themeColor: null,

      border: {
        color: '#000',
        width: 2,
        type: 'solid',
      },
      internalRadius: 0,
      dxternalRadius: 100,
    },
    spec: {
    },
    label: {
      showLabel: false,
      labelPosition: CHART_LABEL_POSITIONS[0].value,
      labelFontFamily: PIVOT_CHART_FONT_FAMILIES[0].value,
      labelFontSize: '12',
      labelColor: PIVOT_DEFAULT_FONT_COLOR
    },
  }
}
export default sunburst

新增 webapp/app/containers/Widget/components/Workbench/ConfigSections/SunburstSection.tsx
操作面板的样式栏

import React from 'react'
import debounce from 'lodash/debounce'
import { CheckboxChangeEvent } from 'antd/lib/checkbox'
import { Row, Col, Modal, Icon, InputNumber, Select, Checkbox,Slider  } from 'antd'
import {IDataParamProperty, IDataParams} from "containers/Widget/components/Workbench/OperatingPanel";
import {treeKeyUnknownToknown} from "containers/Widget/render/chart/util";
import {IViewModel} from "containers/View/types";
import ColorPicker from 'components/ColorPicker'
import {PIVOT_CHART_LINE_STYLES} from "app/globalConstants";
const defaultTheme = require('assets/json/echartsThemes/default.project.json')

const styles = require('../Workbench.less')
const defaultThemeColors = defaultTheme.theme.color

export interface color {
  key: String
  value: String
}

export interface SunburstConfig {
  themeColor: color[],
  border: {
    color: string
    width: number
    type: 'solid' | 'dashed' | 'dotted'
  },
  internalRadius: Number,
  dxternalRadius: Number,
}

interface SunburstStates {
  tThemeColor: color[] //主题色
  colorItems: any[]    //主题色彩色器
}

interface SunburstSectionProps {
  config: SunburstConfig
  onChange: (
    prop: string,
    value: number | string | boolean |  color[] ,
    propPath: string[] | null
  ) => void
  dataSource: object[]
  dataParams: IDataParams
  model: IViewModel,

}

export class SunburstSection extends React.PureComponent<SunburstSectionProps ,SunburstStates,{}> {

  private debounceInputChange = null

  constructor (props: SunburstSectionProps) {
    super(props)
    this.state = {
      tThemeColor: new Array<color>(),
      colorItems: []
    }
    const { dataSource , dataParams , model ,config} = this.props
    let { themeColor } = config
    const { cols } = dataParams
      let {idKey,pidKey,ispidKey,nameKey} = treeKeyUnknownToknown( cols.items , model );
      let pid;
      let nameIndex = 0 ;
      dataSource.map( ( g , index )=>{
        pid = g[pidKey];
        if ( pid == 0 ){
          if ( themeColor == null ){
            themeColor = [];
          }
          themeColor[nameIndex] = {
            key:  g[nameKey] ,
            value: themeColor[nameIndex] ?  themeColor[nameIndex].value : defaultThemeColors[nameIndex]
          };
          nameIndex += 1
        }
      })
    this.state = {
      tThemeColor: themeColor,
      colorItems: this.buildColours( themeColor )
    }
    this.props.onChange("themeColor",themeColor,null);
    this.debounceInputChange = debounce(props.onChange, 1500)
  }

  /**
   * 构建采色器
   * @param tThemeColor
   */
  private buildColours( tThemeColor ){
    let ci = [];
    tThemeColor.map( ( g , index )=>{
      ci.push(<Row key={index} gutter={8} type="flex" align="middle" className={styles.blockRow}>
        <Col span={8}>{g.key}</Col>
        <Col span={4}>
          <ColorPicker
            value={g.value}
            onChange={this.colorChange( index )}
          />
        </Col>
      </Row>)
    })
    return ci;
  }

  private sliderChange = (prop) => (value) => {
    this.props.onChange( prop , value , null );
  }

  private propChange = (propName: string, propPath?: string) => (
    e: number | string | CheckboxChangeEvent
  ) => {
    const value = (e as CheckboxChangeEvent).target
      ? (e as CheckboxChangeEvent).target.checked
      : (e as string | number)
    this.props.onChange(propName, value, propPath ? [propPath] : [])
  }

  private  colorChange = (index) => (color) => {
    const tThemeColor = this.state.tThemeColor;
    tThemeColor[index].value = color
    this.setState({
      tThemeColor: tThemeColor.map((item, _index) => _index == index ? {...item, value: color} : item),
      colorItems: this.buildColours( tThemeColor )
  })
    this.props.onChange("themeColor",tThemeColor,null);
  }


  private lineStyles = PIVOT_CHART_LINE_STYLES.map((l) => (
    <Option key={l.value} value={l.value}>
      {l.name}
    </Option>
  ))

  public render () {
    const { model ,config} = this.props
    const { color, width, type  } = config.border
    const { internalRadius , dxternalRadius } = config
    const { colorItems } = this.state

    function formatter(value) {
      return `${value}%`;
    }

    return (
      <div>

        <div className={styles.paneBlock}>
          <h4>主题颜色</h4>
          <div className={styles.blockBody}>
            {(colorItems)}
          </div>
        </div>

        <div className={styles.paneBlock}>
            <h4>边框</h4>
              <Row
                gutter={8}
                type="flex"
                align="middle"
                className={styles.blockRow}
              >
              <Col span={10}>
                  <Select
                    placeholder="样式"
                    className={styles.blockElm}
                    value={type}
                    onChange={this.propChange('type', 'border')}
                  >
                    {this.lineStyles}
                  </Select>
              </Col>
              <Col span={10}>
                <InputNumber
                  placeholder="粗细"
                  className={styles.blockElm}
                  value={width}
                  min={0}
                  onChange={this.propChange('width', 'border')}
                />
              </Col>
              <Col span={4}>
                <ColorPicker
                  value={color}
                  onChange={this.propChange('color', 'border')}
                />
              </Col>
            </Row>
        </div>

        <div className={styles.paneBlock}>
          <h4>半径</h4>
            <Row
              gutter={8}
              type="flex"
              align="middle"
              className={styles.blockRow}
            >
              <Col key="cLabel" span={8}>
                内部半径
              </Col>
              <Col span={14}>
                <Slider value={internalRadius}  onChange={this.sliderChange("internalRadius")}  />
              </Col>
          </Row>
            <Row
              gutter={8}
              type="flex"
              align="middle"
              className={ styles.blockRow}
            >
             <Col key="dLabel" span={8}>
               外部半径
             </Col>
             <Col span={14}>
               <Slider value={dxternalRadius} onChange={this.sliderChange("dxternalRadius")} />
             </Col>
          </Row>
        </div>

      </div>
    )
  }
}

export default SunburstSection

修改 webapp/app/containers/Widget/components/Widget

//加入
import {SunburstConfig} from "containers/Widget/components/Workbench/ConfigSections/SunburstSection";

export interface IChartStyles {
  //....省略
  //加入
  sunburst?: SunburstConfig
}

修改 webapp/app/containers/Widget/components/Workbench/OperatingPanel.tsx

//分别导入
import SunburstSection from './ConfigSections/SunburstSection'
import sunburst from "containers/Widget/config/chart/sunburst";


	const {
      spec, xAxis, yAxis, axis, splitLine, pivot: pivotConfig, label, legend,
      visualMap, toolbox, areaSelect, scorecard, gauge, iframe, table, bar, radar, doubleYAxis,ds,river,pictorialBar ,
      sunburst //加入
      } = styleParams
	
	//新加入
      {sunburst && <SunburstSection
              dataSource={dataSource}
              dataParams={dataParams}
              model={selectedView.model}
              config={sunburst}
              onChange={this.styleChange('sunburst')}
      />}


新增 webapp/app/containers/Widget/render/chart/sunburst.ts
用于构建旭日图

import { IChartProps } from '../../components/Chart'
import { decodeMetricName } from '../../components/util'
import {getLegendOption, getLabelOption, treeKeyUnknownToknown} from './util'
import { EChartOption } from 'echarts'
import defaultTheme from 'assets/json/echartsThemes/default.project.json'
const defaultThemeColors = defaultTheme.theme.color

/**
 * 最基础的旭日图组装需要父子结构的数据,支持无限递归,直到没有子节点。
 * 所以数据得包含三个属性,才可保证。
 * id 当前节点ID
 * pid 当前节点父ID。0=顶级
 * isP 该节点下是否还有节点。该属性既是基线条件,也是递归条件。
 * @param chartProps
 * @param drillOptions
 */
export default function (chartProps: IChartProps, drillOptions?: any) {
  const {
    cols,
    data,
    metrics,
    chartStyles,
    model,
  } = chartProps
  const {label , sunburst } = chartStyles
  const { themeColor,border, internalRadius, dxternalRadius } = sunburst
  const {
    color: borderColor,
    width: borderWidth,
    type: borderType,
  } = border

  let id,pid,ispid,name;
  const labelOption = {
    label: getLabelOption('sunburst', label, metrics)
  }
  /**
   * 确认每个属性的key
   */
  let {idKey,pidKey,ispidKey,nameKey} = treeKeyUnknownToknown( cols,model );

  let dataObj = new Array();
  //遍历指标
  metrics.forEach((m, i) => {
    const decodedMetricName = decodeMetricName(m.name)
    data.map((g, index) => {
      let value = g[`${m.agg}(${decodedMetricName})`];
      pid = g[pidKey];
      name = g[nameKey];
      id = g[idKey];
      if ( pid == 0 ){
        let children = new Array();
        findNode( data , id , children)
        let node = {
          name: name,
          value: value,
          children: children
        }
        dataObj.push( node );
      }
    })
  })
  var colors = [];
  if ( themeColor == null ){
    colors = defaultThemeColors;
  }else{
    themeColor.map( (item , index) => {
      colors[index] = item.value
    } );
  }

  let seriesObj = {
    color: colors,
    series: {
      type: "sunburst",
      data: dataObj,
      ...labelOption,
      itemStyle: {
        borderColor,
        borderType,
        borderWidth
      },
      radius: [`${internalRadius}%`, `${dxternalRadius}%`]
    },
  }

  function findNode( data , id , children ) {
    metrics.forEach((m, i) => {
      const decodedMetricName = decodeMetricName(m.name)
      data.map((g, index) => {
        pid = g[pidKey];
        if (pid === id) {
          ispid = g[ispidKey];
          let childrenNode = new Array();
          if (ispid === 0) {
            findNode(data, g[idKey], childrenNode)
          }
          let node = {
            name: g[nameKey],
            value:  g[`${m.agg}(${decodedMetricName})`],
            children: childrenNode
          }
          children.push(node);
        }
      })
    })
  }

  return {
   ...seriesObj
  }
}

修改 webapp/app/containers/Widget/render/chart/index.ts

//加入
import sunburst from './sunburst'

//加入
case 'sunburst': return sunburst(chartProps, drillOptions)

到这就结束辣,需要修改的地方大致就是这几个。有问题的地方欢迎大家提出、指正。
图表、图表样式的构建,还有要优化、新增的功能,大家可以自己修改。以上仅供参考。‘

漏了几处

Widget\components\Workbench\index.tsx 文件中

<OperatingPanel
				//...
				//新加入
                dataSource={data} >




Widget\components\Workbench\OperatingPanel.tsx 文件中

interface IOperatingPanelProps {
  //...
  //新加入
  dataSource: object[]
}

 public render () {
    const {
      //...
      //新加入
      dataSource
    } = this.props
	//...
/>
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值