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