-
Notifications
You must be signed in to change notification settings - Fork 439
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CE-337] Implement overview page in react theme
Implement overview for cluster & host in operator dashboard. Change-Id: Ic6f2409e97c15b11c851b22fbdd75ed72bdfeb0f Signed-off-by: Haitao Yue <hightall@me.com>
- Loading branch information
Showing
12 changed files
with
834 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
258 changes: 258 additions & 0 deletions
258
src/themes/react/static/dashboard/src/components/Charts/Pie/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
/* | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
import React, { Component } from 'react'; | ||
import { Chart, Tooltip, Geom, Coord } from 'bizcharts'; | ||
import { DataView } from '@antv/data-set'; | ||
import { Divider } from 'antd'; | ||
import classNames from 'classnames'; | ||
import ReactFitText from 'react-fittext'; | ||
import Debounce from 'lodash-decorators/debounce'; | ||
import Bind from 'lodash-decorators/bind'; | ||
import autoHeight from '../autoHeight'; | ||
|
||
import styles from './index.less'; | ||
|
||
/* eslint react/no-danger:0 */ | ||
@autoHeight() | ||
export default class Pie extends Component { | ||
state = { | ||
legendData: [], | ||
legendBlock: false, | ||
}; | ||
|
||
componentDidMount() { | ||
this.getLengendData(); | ||
this.resize(); | ||
window.addEventListener('resize', this.resize); | ||
} | ||
|
||
componentWillReceiveProps(nextProps) { | ||
if (this.props.data !== nextProps.data) { | ||
// because of charts data create when rendered | ||
// so there is a trick for get rendered time | ||
this.setState( | ||
{ | ||
legendData: [...this.state.legendData], | ||
}, | ||
() => { | ||
this.getLengendData(); | ||
} | ||
); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
window.removeEventListener('resize', this.resize); | ||
this.resize.cancel(); | ||
} | ||
|
||
getG2Instance = chart => { | ||
this.chart = chart; | ||
}; | ||
|
||
// for custom lengend view | ||
getLengendData = () => { | ||
if (!this.chart) return; | ||
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形 | ||
const items = geom.get('dataArray') || []; // 获取图形对应的 | ||
|
||
const legendData = items.map(item => { | ||
/* eslint no-underscore-dangle:0 */ | ||
const origin = item[0]._origin; | ||
origin.color = item[0].color; | ||
origin.checked = true; | ||
return origin; | ||
}); | ||
|
||
this.setState({ | ||
legendData, | ||
}); | ||
}; | ||
|
||
// for window resize auto responsive legend | ||
@Bind() | ||
@Debounce(300) | ||
resize() { | ||
const { hasLegend } = this.props; | ||
if (!hasLegend || !this.root) { | ||
window.removeEventListener('resize', this.resize); | ||
return; | ||
} | ||
if (this.root.parentNode.clientWidth <= 380) { | ||
if (!this.state.legendBlock) { | ||
this.setState({ | ||
legendBlock: true, | ||
}); | ||
} | ||
} else if (this.state.legendBlock) { | ||
this.setState({ | ||
legendBlock: false, | ||
}); | ||
} | ||
} | ||
|
||
handleRoot = n => { | ||
this.root = n; | ||
}; | ||
|
||
handleLegendClick = (item, i) => { | ||
const newItem = item; | ||
newItem.checked = !newItem.checked; | ||
|
||
const { legendData } = this.state; | ||
legendData[i] = newItem; | ||
|
||
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x); | ||
|
||
if (this.chart) { | ||
this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1); | ||
} | ||
|
||
this.setState({ | ||
legendData, | ||
}); | ||
}; | ||
|
||
render() { | ||
const { | ||
valueFormat, | ||
subTitle, | ||
total, | ||
hasLegend = false, | ||
className, | ||
style, | ||
height, | ||
forceFit = true, | ||
percent = 0, | ||
color, | ||
inner = 0.75, | ||
animate = true, | ||
colors, | ||
lineWidth = 1, | ||
} = this.props; | ||
|
||
const { legendData, legendBlock } = this.state; | ||
const pieClassName = classNames(styles.pie, className, { | ||
[styles.hasLegend]: !!hasLegend, | ||
[styles.legendBlock]: legendBlock, | ||
}); | ||
|
||
const defaultColors = colors; | ||
let data = this.props.data || []; | ||
let selected = this.props.selected || true; | ||
let tooltip = this.props.tooltip || true; | ||
let formatColor; | ||
|
||
const scale = { | ||
x: { | ||
type: 'cat', | ||
range: [0, 1], | ||
}, | ||
y: { | ||
min: 0, | ||
}, | ||
}; | ||
|
||
if (percent) { | ||
selected = false; | ||
tooltip = false; | ||
formatColor = value => { | ||
if (value === '占比') { | ||
return color || 'rgba(24, 144, 255, 0.85)'; | ||
} else { | ||
return '#F0F2F5'; | ||
} | ||
}; | ||
|
||
data = [ | ||
{ | ||
x: '占比', | ||
y: parseFloat(percent), | ||
}, | ||
{ | ||
x: '反比', | ||
y: 100 - parseFloat(percent), | ||
}, | ||
]; | ||
} | ||
|
||
const tooltipFormat = [ | ||
'x*percent', | ||
(x, p) => ({ | ||
name: x, | ||
value: `${(p * 100).toFixed(2)}%`, | ||
}), | ||
]; | ||
|
||
const padding = [12, 0, 12, 0]; | ||
|
||
const dv = new DataView(); | ||
dv.source(data).transform({ | ||
type: 'percent', | ||
field: 'y', | ||
dimension: 'x', | ||
as: 'percent', | ||
}); | ||
|
||
return ( | ||
<div ref={this.handleRoot} className={pieClassName} style={style}> | ||
<ReactFitText maxFontSize={25}> | ||
<div className={styles.chart}> | ||
<Chart | ||
scale={scale} | ||
height={height} | ||
forceFit={forceFit} | ||
data={dv} | ||
padding={padding} | ||
animate={animate} | ||
onGetG2Instance={this.getG2Instance} | ||
> | ||
{!!tooltip && <Tooltip showTitle={false} />} | ||
<Coord type="theta" innerRadius={inner} /> | ||
<Geom | ||
style={{ lineWidth, stroke: '#fff' }} | ||
tooltip={tooltip && tooltipFormat} | ||
type="intervalStack" | ||
position="percent" | ||
color={['x', percent ? formatColor : defaultColors]} | ||
selected={selected} | ||
/> | ||
</Chart> | ||
|
||
{(subTitle || total) && ( | ||
<div className={styles.total}> | ||
{subTitle && <h4 className="pie-sub-title">{subTitle}</h4>} | ||
{/* eslint-disable-next-line */} | ||
{total && ( | ||
<div className="pie-stat">{typeof total === 'function' ? total() : total}</div> | ||
)} | ||
</div> | ||
)} | ||
</div> | ||
</ReactFitText> | ||
|
||
{hasLegend && ( | ||
<ul className={styles.legend}> | ||
{legendData.map((item, i) => ( | ||
<li key={item.x} onClick={() => this.handleLegendClick(item, i)}> | ||
<span | ||
className={styles.dot} | ||
style={{ | ||
backgroundColor: !item.checked ? '#aaa' : item.color, | ||
}} | ||
/> | ||
<span className={styles.legendTitle}>{item.x}</span> | ||
<Divider type="vertical" /> | ||
<span className={styles.percent}> | ||
{`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`} | ||
</span> | ||
<span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span> | ||
</li> | ||
))} | ||
</ul> | ||
)} | ||
</div> | ||
); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
src/themes/react/static/dashboard/src/components/Charts/Pie/index.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
@import '~antd/lib/style/themes/default.less'; | ||
|
||
.pie { | ||
position: relative; | ||
.chart { | ||
position: relative; | ||
} | ||
&.hasLegend .chart { | ||
width: ~'calc(100% - 240px)'; | ||
} | ||
.legend { | ||
position: absolute; | ||
right: 0; | ||
min-width: 200px; | ||
top: 50%; | ||
transform: translateY(-50%); | ||
margin: 0 20px; | ||
list-style: none; | ||
padding: 0; | ||
li { | ||
cursor: pointer; | ||
margin-bottom: 16px; | ||
height: 22px; | ||
line-height: 22px; | ||
&:last-child { | ||
margin-bottom: 0; | ||
} | ||
} | ||
} | ||
.dot { | ||
border-radius: 8px; | ||
display: inline-block; | ||
margin-right: 8px; | ||
position: relative; | ||
top: -1px; | ||
height: 8px; | ||
width: 8px; | ||
} | ||
.line { | ||
background-color: @border-color-split; | ||
display: inline-block; | ||
margin-right: 8px; | ||
width: 1px; | ||
height: 16px; | ||
} | ||
.legendTitle { | ||
color: @text-color; | ||
} | ||
.percent { | ||
color: @text-color-secondary; | ||
} | ||
.value { | ||
position: absolute; | ||
right: 0; | ||
} | ||
.title { | ||
margin-bottom: 8px; | ||
} | ||
.total { | ||
position: absolute; | ||
left: 50%; | ||
top: 50%; | ||
text-align: center; | ||
height: 62px; | ||
transform: translate(-50%, -50%); | ||
& > h4 { | ||
color: @text-color-secondary; | ||
font-size: 14px; | ||
line-height: 22px; | ||
height: 22px; | ||
margin-bottom: 8px; | ||
font-weight: normal; | ||
} | ||
& > p { | ||
color: @heading-color; | ||
display: block; | ||
font-size: 1.2em; | ||
height: 32px; | ||
line-height: 32px; | ||
white-space: nowrap; | ||
} | ||
} | ||
} | ||
|
||
.legendBlock { | ||
&.hasLegend .chart { | ||
width: 100%; | ||
margin: 0 0 32px 0; | ||
} | ||
.legend { | ||
position: relative; | ||
transform: none; | ||
} | ||
} |
Oops, something went wrong.