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
//...
/>