Commit 34e00408 authored by 陈帅's avatar 陈帅

Monitor finish

parent 61b2aaa6
......@@ -12,7 +12,6 @@
"dev": "umi dev"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"antd": "^3.10.9",
"dva": "^2.4.0",
"react": "^16.6.3",
......
......@@ -12,7 +12,6 @@
"dev": "umi dev"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"ant-design-pro": "^2.1.1",
"antd": "^3.10.9",
"dva": "^2.4.0",
......
......@@ -12,9 +12,7 @@
"dev": "umi dev"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"@antv/data-set": "^0.10.2",
"@types/numeral": "^0.0.25",
"antd": "^3.10.9",
"bizcharts": "^3.5.2",
"bizcharts-plugin-slider": "^2.1.1-beta.1",
......@@ -28,6 +26,7 @@
"devDependencies": {
"umi": "^2.6.9",
"umi-plugin-block-dev": "^1.1.0",
"umi-plugin-react": "^1.7.2"
"umi-plugin-react": "^1.7.2",
"@types/numeral": "^0.0.25"
}
}
\ No newline at end of file
......@@ -12,7 +12,6 @@
"dev": "umi dev"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"antd": "^3.10.9",
"dva": "^2.4.0",
"hash.js": "^1.1.5",
......
......@@ -11,7 +11,6 @@
"url": "https://github.com/umijs/umi-blocks/ant-design-pro/basicprofile"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"antd": "^3.10.9",
"dva": "^2.4.0",
"react": "^16.6.3",
......
......@@ -11,7 +11,6 @@
"url": "https://github.com/umijs/umi-blocks/ant-design-pro/cardlist"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"ant-design-pro": "^2.1.1",
"antd": "^3.10.9",
"dva": "^2.4.0",
......
......@@ -16,7 +16,6 @@
"react": "^16.6.3",
"umi-request": "^1.0.4",
"umi": "^2.6.8",
"@ant-design/pro-layout": "^4.0.5",
"umi-plugin-react": "^1.7.2"
},
"devDependencies": {
......
......@@ -11,13 +11,18 @@
"url": "https://github.com/umijs/umi-blocks/ant-design-pro/monitor"
},
"dependencies": {
"@antv/data-set": "^0.10.2",
"antd": "^3.10.9",
"bizcharts": "^3.5.2",
"bizcharts-plugin-slider": "^2.1.1-beta.1",
"dva": "^2.4.0",
"numeral": "^2.0.6",
"react": "^16.6.3",
"react": "^16.8.6",
"react-fittext": "^1.0.0",
"umi-request": "^1.0.0"
},
"devDependencies": {
"@types/numeral": "^0.0.25",
"mockjs": "^1.0.1-beta3",
"umi": "^2.6.9",
"umi-plugin-react": "^1.7.2",
......
import React, { Component } from 'react';
import { Charts, NumberInfo } from 'ant-design-pro';
import Charts from '../Charts';
import { Statistic } from 'antd';
import styles from './index.less';
const { MiniArea } = Charts;
function fixedZero(val) {
function fixedZero(val: number) {
return val * 1 < 10 ? `0${val}` : val;
}
......@@ -28,15 +28,18 @@ export default class ActiveChart extends Component {
componentDidMount() {
this.loopData();
}
timer: number | undefined;
requestRef: number | undefined;
componentWillUnmount() {
clearTimeout(this.timer);
cancelAnimationFrame(this.requestRef);
if (this.requestRef) {
cancelAnimationFrame(this.requestRef);
}
}
loopData = () => {
this.requestRef = requestAnimationFrame(() => {
this.timer = setTimeout(() => {
this.timer = window.setTimeout(() => {
this.setState(
{
activeData: getActiveData(),
......@@ -54,7 +57,7 @@ export default class ActiveChart extends Component {
return (
<div className={styles.activeChart}>
<NumberInfo subTitle="目标评估" total="有望达到预期" />
<Statistic title="目标评估" value="有望达到预期" />
<div style={{ marginTop: 32 }}>
<MiniArea
animate={false}
......@@ -67,10 +70,10 @@ export default class ActiveChart extends Component {
},
}}
yAxis={{
tickLine: false,
label: false,
title: false,
line: false,
tickLine: undefined,
label: undefined,
title: undefined,
line: undefined,
}}
data={activeData}
/>
......
import React from 'react';
import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
import autoHeight from '../autoHeight';
const { Arc, Html, Line } = Guide;
export interface IGaugeProps {
title: React.ReactNode;
color?: string;
height?: number;
bgColor?: number;
percent: number;
forceFit?: boolean;
style?: React.CSSProperties;
formatter?: (value: string) => string;
}
const defaultFormatter = (val: string): string => {
switch (val) {
case '2':
return '';
case '4':
return '';
case '6':
return '';
case '8':
return '';
default:
return '';
}
};
Shape.registerShape!('point', 'pointer', {
drawShape(cfg: any, group: any) {
let point = cfg.points[0];
point = (this as any).parsePoint(point);
const center = (this as any).parsePoint({
x: 0,
y: 0,
});
group.addShape('line', {
attrs: {
x1: center.x,
y1: center.y,
x2: point.x,
y2: point.y,
stroke: cfg.color,
lineWidth: 2,
lineCap: 'round',
},
});
return group.addShape('circle', {
attrs: {
x: center.x,
y: center.y,
r: 6,
stroke: cfg.color,
lineWidth: 3,
fill: '#fff',
},
});
},
});
class Gauge extends React.Component<IGaugeProps> {
render() {
const {
title,
height = 1,
percent,
forceFit = true,
formatter = defaultFormatter,
color = '#2F9CFF',
bgColor = '#F0F2F5',
} = this.props;
const cols = {
value: {
type: 'linear',
min: 0,
max: 10,
tickCount: 6,
nice: true,
},
};
const renderHtml = () => `
<div style="width: 300px;text-align: center;font-size: 12px!important;">
<p style="font-size: 14px; color: rgba(0,0,0,0.43);margin: 0;">${title}</p>
<p style="font-size: 24px;color: rgba(0,0,0,0.85);margin: 0;">
${(data[0].value * 10).toFixed(2)}%
</p>
</div>`;
const data = [{ value: percent / 10 }];
const textStyle: {
fontSize: number;
fill: string;
textAlign: 'center';
} = {
fontSize: 12,
fill: 'rgba(0, 0, 0, 0.65)',
textAlign: 'center',
};
return (
<Chart height={height} data={data} scale={cols} padding={[-16, 0, 16, 0]} forceFit={forceFit}>
<Coord type="polar" startAngle={-1.25 * Math.PI} endAngle={0.25 * Math.PI} radius={0.8} />
<Axis name="1" line={undefined} />
<Axis
line={undefined}
tickLine={undefined}
subTickLine={undefined}
name="value"
zIndex={2}
label={{
offset: -12,
formatter,
textStyle: textStyle,
}}
/>
<Guide>
<Line
start={[3, 0.905]}
end={[3, 0.85]}
lineStyle={{
stroke: color,
lineDash: undefined,
lineWidth: 2,
}}
/>
<Line
start={[5, 0.905]}
end={[5, 0.85]}
lineStyle={{
stroke: color,
lineDash: undefined,
lineWidth: 3,
}}
/>
<Line
start={[7, 0.905]}
end={[7, 0.85]}
lineStyle={{
stroke: color,
lineDash: undefined,
lineWidth: 3,
}}
/>
<Arc
start={[0, 0.965]}
end={[10, 0.965]}
style={{
stroke: bgColor,
lineWidth: 10,
}}
/>
<Arc
start={[0, 0.965]}
end={[data[0].value, 0.965]}
style={{
stroke: color,
lineWidth: 10,
}}
/>
<Html position={['50%', '95%']} html={renderHtml()} />
</Guide>
<Geom
line={false}
type="point"
position="value*1"
shape="pointer"
color={color}
active={false}
/>
</Chart>
);
}
}
export default autoHeight()(Gauge);
import React from 'react';
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
import autoHeight from '../autoHeight';
import styles from '../index.less';
export interface IAxis {
title: any;
line: any;
gridAlign: any;
labels: any;
tickLine: any;
grid: any;
label: any;
}
export interface IMiniAreaProps {
color?: string;
height?: number;
borderColor?: string;
line?: boolean;
animate?: boolean;
xAxis?: IAxis;
forceFit?: boolean;
scale?: { x?: any; y?: any };
yAxis?: Partial<IAxis>;
borderWidth?: number;
data: Array<{
x: number | string;
y: number;
}>;
}
class MiniArea extends React.Component<IMiniAreaProps> {
render() {
const {
height = 1,
data = [],
forceFit = true,
color = 'rgba(24, 144, 255, 0.2)',
borderColor = '#1089ff',
scale = { x: {}, y: {} },
borderWidth = 2,
line,
xAxis,
yAxis,
animate = true,
} = this.props;
const padding: [number, number, number, number] = [36, 5, 30, 5];
const scaleProps = {
x: {
type: 'cat',
range: [0, 1],
...scale!.x,
},
y: {
min: 0,
...scale!.y,
},
};
const tooltip: [string, (...args: any[]) => { name?: string; value: string }] = [
'x*y',
(x: string, y: string) => ({
name: x,
value: y,
}),
];
const chartHeight = height + 54;
return (
<div className={styles.miniChart} style={{ height }}>
<div className={styles.chartContent}>
{height > 0 && (
<Chart
animate={animate}
scale={scaleProps}
height={chartHeight}
forceFit={forceFit}
data={data}
padding={padding}
>
<Axis
key="axis-x"
name="x"
label={false}
line={false}
tickLine={false}
grid={false}
{...xAxis}
/>
<Axis
key="axis-y"
name="y"
label={false}
line={false}
tickLine={false}
grid={false}
{...yAxis}
/>
<Tooltip showTitle={false} crosshairs={false} />
<Geom
type="area"
position="x*y"
color={color}
tooltip={tooltip}
shape="smooth"
style={{
fillOpacity: 1,
}}
/>
{line ? (
<Geom
type="line"
position="x*y"
shape="smooth"
color={borderColor}
size={borderWidth}
tooltip={false}
/>
) : (
<span style={{ display: 'none' }} />
)}
</Chart>
)}
</div>
</div>
);
}
}
export default autoHeight()(MiniArea);
@import '~antd/lib/style/themes/default.less';
.pie {
position: relative;
.chart {
position: relative;
}
&.hasLegend .chart {
width: ~'calc(100% - 240px)';
}
.legend {
position: absolute;
top: 50%;
right: 0;
min-width: 200px;
margin: 0 20px;
padding: 0;
list-style: none;
transform: translateY(-50%);
li {
height: 22px;
margin-bottom: 16px;
line-height: 22px;
cursor: pointer;
&:last-child {
margin-bottom: 0;
}
}
}
.dot {
position: relative;
top: -1px;
display: inline-block;
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 8px;
}
.line {
display: inline-block;
width: 1px;
height: 16px;
margin-right: 8px;
background-color: @border-color-split;
}
.legendTitle {
color: @text-color;
}
.percent {
color: @text-color-secondary;
}
.value {
position: absolute;
right: 0;
}
.title {
margin-bottom: 8px;
}
.total {
position: absolute;
top: 50%;
left: 50%;
max-height: 62px;
text-align: center;
transform: translate(-50%, -50%);
& > h4 {
height: 22px;
margin-bottom: 8px;
color: @text-color-secondary;
font-weight: normal;
font-size: 14px;
line-height: 22px;
}
& > p {
display: block;
height: 32px;
color: @heading-color;
font-size: 1.2em;
line-height: 32px;
white-space: nowrap;
}
}
}
.legendBlock {
&.hasLegend .chart {
width: 100%;
margin: 0 0 32px 0;
}
.legend {
position: relative;
transform: none;
}
}
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';
export interface IPieProps {
animate?: boolean;
color?: string;
colors?: string[];
selected?: boolean;
height?: number;
margin?: [number, number, number, number];
hasLegend?: boolean;
padding?: [number, number, number, number];
percent?: number;
data?: Array<{
x: string | string;
y: number;
}>;
inner?: number;
lineWidth?: number;
forceFit?: boolean;
style?: React.CSSProperties;
className?: string;
total?: React.ReactNode | number | (() => React.ReactNode | number);
title?: React.ReactNode;
tooltip?: boolean;
valueFormat?: (value: string) => string | React.ReactNode;
subTitle?: React.ReactNode;
}
interface IPieState {
legendData: Array<{ checked: boolean; x: string; color: string; percent: number; y: string }>;
legendBlock: boolean;
}
class Pie extends Component<IPieProps, IPieState> {
state: IPieState = {
legendData: [],
legendBlock: false,
};
requestRef: number | undefined;
componentDidMount() {
window.addEventListener(
'resize',
() => {
this.requestRef = requestAnimationFrame(() => this.resize());
},
{ passive: true }
);
}
componentDidUpdate(preProps: IPieProps) {
const { data } = this.props;
if (data !== preProps.data) {
// because of charts data create when rendered
// so there is a trick for get rendered time
this.getLegendData();
}
}
componentWillUnmount() {
if (this.requestRef) {
window.cancelAnimationFrame(this.requestRef);
}
window.removeEventListener('resize', this.resize);
if (this.resize) {
(this.resize as any).cancel();
}
}
getG2Instance = (chart: G2.Chart) => {
this.chart = chart;
requestAnimationFrame(() => {
this.getLegendData();
this.resize();
});
};
// for custom lengend view
getLegendData = () => {
if (!this.chart) return;
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
if (!geom) return;
const items = geom.get('dataArray') || []; // 获取图形对应的
const legendData = items.map((item: { color: any; _origin: any }[]) => {
/* eslint no-underscore-dangle:0 */
const origin = item[0]._origin;
origin.color = item[0].color;
origin.checked = true;
return origin;
});
this.setState({
legendData,
});
};
handleRoot = (n: HTMLDivElement) => {
this.root = n;
};
handleLegendClick = (item: any, i: string | number) => {
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,
});
};
root!: HTMLDivElement;
chart: G2.Chart | undefined;
// for window resize auto responsive legend
@Bind()
@Debounce(300)
resize() {
const { hasLegend } = this.props;
const { legendBlock } = this.state;
if (!hasLegend || !this.root) {
window.removeEventListener('resize', this.resize);
return;
}
if (
this.root &&
this.root.parentNode &&
(this.root.parentNode as HTMLElement).clientWidth <= 380
) {
if (!legendBlock) {
this.setState({
legendBlock: true,
});
}
} else if (legendBlock) {
this.setState({
legendBlock: false,
});
}
}
render() {
const {
valueFormat,
subTitle,
total,
hasLegend = false,
className,
style,
height = 0,
forceFit = true,
percent,
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 {
data: propsData,
selected: propsSelected = true,
tooltip: propsTooltip = true,
} = this.props;
let data = propsData || [];
let selected = propsSelected;
let tooltip = propsTooltip;
const defaultColors = colors;
data = data || [];
selected = selected || true;
tooltip = tooltip || true;
let formatColor;
const scale = {
x: {
type: 'cat',
range: [0, 1],
},
y: {
min: 0,
},
};
if (percent || percent === 0) {
selected = false;
tooltip = false;
formatColor = (value: string) => {
if (value === '占比') {
return color || 'rgba(24, 144, 255, 0.85)';
}
return '#F0F2F5';
};
data = [
{
x: '占比',
y: parseFloat(percent + ''),
},
{
x: '反比',
y: 100 - parseFloat(percent + ''),
},
];
}
const tooltipFormat: [string, (...args: any[]) => { name?: string; value: string }] = [
'x*percent',
(x: string, p: number) => ({
name: x,
value: `${(p * 100).toFixed(2)}%`,
}),
];
const padding = [12, 0, 12, 0] as [number, number, number, number];
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 : undefined}
type="intervalStack"
position="percent"
color={['x', percent || percent === 0 ? formatColor : defaultColors] as any}
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}>
{`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
</span>
<span className={styles.value}>{valueFormat ? valueFormat(item.y) : item.y}</span>
</li>
))}
</ul>
)}
</div>
);
}
}
export default autoHeight()(Pie);
.tagCloud {
overflow: hidden;
canvas {
transform-origin: 0 0;
}
}
import React, { Component } from 'react';
import { Chart, Geom, Coord, Shape, Tooltip } from 'bizcharts';
import DataSet from '@antv/data-set';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
import classNames from 'classnames';
import autoHeight from '../autoHeight';
import styles from './index.less';
/* eslint no-underscore-dangle: 0 */
/* eslint no-param-reassign: 0 */
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
export interface ITagCloudProps {
data: Array<{
name: string;
value: string;
}>;
height?: number;
className?: string;
style?: React.CSSProperties;
}
interface ITagCloudState {
dv: any;
height?: number;
width: number;
}
class TagCloud extends Component<ITagCloudProps, ITagCloudState> {
state = {
dv: null,
height: 0,
width: 0,
};
componentDidMount() {
requestAnimationFrame(() => {
this.initTagCloud();
this.renderChart(this.props);
});
window.addEventListener('resize', this.resize, { passive: true });
}
componentDidUpdate(preProps?: ITagCloudProps) {
const { data } = this.props;
if (preProps && JSON.stringify(preProps.data) !== JSON.stringify(data)) {
this.renderChart(this.props);
}
}
isUnmount!: boolean;
componentWillUnmount() {
this.isUnmount = true;
window.cancelAnimationFrame(this.requestRef);
window.removeEventListener('resize', this.resize);
}
requestRef!: number;
resize = () => {
this.requestRef = requestAnimationFrame(() => {
this.renderChart(this.props);
});
};
root: HTMLDivElement | undefined;
saveRootRef = (node: HTMLDivElement) => {
this.root = node;
};
initTagCloud = () => {
function getTextAttrs(cfg: {
x?: any;
y?: any;
style?: any;
opacity?: any;
origin?: any;
color?: any;
}) {
return Object.assign({}, cfg.style, {
fillOpacity: cfg.opacity,
fontSize: cfg.origin._origin.size,
rotate: cfg.origin._origin.rotate,
text: cfg.origin._origin.text,
textAlign: 'center',
fontFamily: cfg.origin._origin.font,
fill: cfg.color,
textBaseline: 'Alphabetic',
});
}
(Shape as any).registerShape('point', 'cloud', {
drawShape(
cfg: { x: any; y: any },
container: { addShape: (arg0: string, arg1: { attrs: any }) => void }
) {
const attrs = getTextAttrs(cfg);
return container.addShape('text', {
attrs: Object.assign(attrs, {
x: cfg.x,
y: cfg.y,
}),
});
},
});
};
imageMask: HTMLImageElement | undefined;
@Bind()
@Debounce(500)
renderChart(nextProps: ITagCloudProps) {
// const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
const { data, height } = nextProps || this.props;
if (data.length < 1 || !this.root) {
return;
}
const h = height;
const w = this.root.offsetWidth;
const onload = () => {
const dv = new DataSet.View().source(data);
const range = dv.range('value');
const [min, max] = range;
dv.transform({
type: 'tag-cloud',
fields: ['name', 'value'],
imageMask: this.imageMask,
font: 'Verdana',
size: [w, h], // 宽高设置最好根据 imageMask 做调整
padding: 0,
timeInterval: 5000, // max execute time
rotate() {
return 0;
},
fontSize(d: { value: number }) {
// eslint-disable-next-line
return Math.pow((d.value - min) / (max - min), 2) * (17.5 - 5) + 5;
},
});
if (this.isUnmount) {
return;
}
this.setState({
dv,
width: w,
height: h,
});
};
if (!this.imageMask) {
this.imageMask = new Image();
this.imageMask.crossOrigin = '';
this.imageMask.src = imgUrl;
this.imageMask.onload = onload;
} else {
onload();
}
}
render() {
const { className, height } = this.props;
const { dv, width, height: stateHeight } = this.state;
return (
<div
className={classNames(styles.tagCloud, className)}
style={{ width: '100%', height }}
ref={this.saveRootRef}
>
{dv && (
<Chart
width={width}
height={stateHeight}
data={dv}
padding={0}
scale={{
x: { nice: false },
y: { nice: false },
}}
>
<Tooltip showTitle={false} />
<Coord reflect="y" />
<Geom
type="point"
position="x*y"
color="text"
shape="cloud"
tooltip={[
'text*value',
function trans(text, value) {
return { name: text, value };
},
]}
/>
</Chart>
)}
</div>
);
}
}
export default autoHeight()(TagCloud);
@import '~antd/lib/style/themes/default.less';
.waterWave {
position: relative;
display: inline-block;
transform-origin: left;
.text {
position: absolute;
top: 32px;
left: 0;
width: 100%;
text-align: center;
span {
color: @text-color-secondary;
font-size: 14px;
line-height: 22px;
}
h4 {
color: @heading-color;
font-size: 24px;
line-height: 32px;
}
}
.waterWaveCanvasWrapper {
transform: scale(0.5);
transform-origin: 0 0;
}
}
import React, { Component } from 'react';
import autoHeight from '../autoHeight';
import styles from './index.less';
/* eslint no-return-assign: 0 */
/* eslint no-mixed-operators: 0 */
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
export interface IWaterWaveProps {
title: React.ReactNode;
color?: string;
height?: number;
percent: number;
style?: React.CSSProperties;
}
class WaterWave extends Component<IWaterWaveProps> {
state = {
radio: 1,
};
componentDidMount() {
this.renderChart();
this.resize();
window.addEventListener(
'resize',
() => {
requestAnimationFrame(() => this.resize());
},
{ passive: true }
);
}
componentDidUpdate(props: IWaterWaveProps) {
const { percent } = this.props;
if (props.percent !== percent) {
// 不加这个会造成绘制缓慢
this.renderChart('update');
}
}
componentWillUnmount() {
cancelAnimationFrame(this.timer);
if (this.node) {
this.node.innerHTML = '';
}
window.removeEventListener('resize', this.resize);
}
resize = () => {
if (this.root) {
const { height = 1 } = this.props;
const { offsetWidth } = this.root.parentNode as HTMLElement;
this.setState({
radio: offsetWidth < height ? offsetWidth / height : 1,
});
}
};
timer: number = 0;
renderChart(type?: string) {
const { percent, color = '#1890FF' } = this.props;
const data = percent / 100;
const self = this;
cancelAnimationFrame(this.timer);
if (!this.node || (data !== 0 && !data)) {
return;
}
const canvas = this.node;
const ctx = canvas.getContext('2d');
if (!ctx) {
return;
}
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const radius = canvasWidth / 2;
const lineWidth = 2;
const cR = radius - lineWidth;
ctx.beginPath();
ctx.lineWidth = lineWidth * 2;
const axisLength = canvasWidth - lineWidth;
const unit = axisLength / 8;
const range = 0.2; // 振幅
let currRange = range;
const xOffset = lineWidth;
let sp = 0; // 周期偏移量
let currData = 0;
const waveupsp = 0.005; // 水波上涨速度
let arcStack: number[][] = [];
const bR = radius - lineWidth;
const circleOffset = -(Math.PI / 2);
let circleLock = true;
for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) {
arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
}
const cStartPoint = arcStack.shift() as number[];
ctx.strokeStyle = color;
ctx.moveTo(cStartPoint[0], cStartPoint[1]);
function drawSin() {
if (!ctx) {
return;
}
ctx.beginPath();
ctx.save();
const sinStack = [];
for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
const x = sp + (xOffset + i) / unit;
const y = Math.sin(x) * currRange;
const dx = i;
const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
ctx.lineTo(dx, dy);
sinStack.push([dx, dy]);
}
const startPoint = sinStack.shift() as number[];
ctx.lineTo(xOffset + axisLength, canvasHeight);
ctx.lineTo(xOffset, canvasHeight);
ctx.lineTo(startPoint[0], startPoint[1]);
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(1, color);
ctx.fillStyle = gradient;
ctx.fill();
ctx.restore();
}
function render() {
if (!ctx) {
return;
}
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
if (circleLock && type !== 'update') {
if (arcStack.length) {
const temp = arcStack.shift() as number[];
ctx.lineTo(temp[0], temp[1]);
ctx.stroke();
} else {
circleLock = false;
ctx.lineTo(cStartPoint[0], cStartPoint[1]);
ctx.stroke();
arcStack = [];
ctx.globalCompositeOperation = 'destination-over';
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.arc(radius, radius, bR, 0, 2 * Math.PI, true);
ctx.beginPath();
ctx.save();
ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, true);
ctx.restore();
ctx.clip();
ctx.fillStyle = color;
}
} else {
if (data >= 0.85) {
if (currRange > range / 4) {
const t = range * 0.01;
currRange -= t;
}
} else if (data <= 0.1) {
if (currRange < range * 1.5) {
const t = range * 0.01;
currRange += t;
}
} else {
if (currRange <= range) {
const t = range * 0.01;
currRange += t;
}
if (currRange >= range) {
const t = range * 0.01;
currRange -= t;
}
}
if (data - currData > 0) {
currData += waveupsp;
}
if (data - currData < 0) {
currData -= waveupsp;
}
sp += 0.07;
drawSin();
}
self.timer = requestAnimationFrame(render);
}
render();
}
root: HTMLDivElement | undefined | null;
node: HTMLCanvasElement | undefined | null;
render() {
const { radio } = this.state;
const { percent, title, height = 1 } = this.props;
return (
<div
className={styles.waterWave}
ref={n => (this.root = n)}
style={{ transform: `scale(${radio})` }}
>
<div style={{ width: height, height, overflow: 'hidden' }}>
<canvas
className={styles.waterWaveCanvasWrapper}
ref={n => (this.node = n)}
width={height * 2}
height={height * 2}
/>
</div>
<div className={styles.text} style={{ width: height }}>
{title && <span>{title}</span>}
<h4>{percent}%</h4>
</div>
</div>
);
}
}
export default autoHeight()(WaterWave);
import React from 'react';
export type IReactComponent<P = any> =
| React.StatelessComponent<P>
| React.ComponentClass<P>
| React.ClassicComponentClass<P>;
function computeHeight(node: HTMLDivElement) {
node.style.height = '100%';
const totalHeight = parseInt(getComputedStyle(node).height + '', 10);
const padding =
parseInt(getComputedStyle(node).paddingTop + '', 10) +
parseInt(getComputedStyle(node).paddingBottom + '', 10);
return totalHeight - padding;
}
function getAutoHeight(n: HTMLDivElement) {
if (!n) {
return 0;
}
let node = n;
let height = computeHeight(node);
const parentNode = node.parentNode as HTMLDivElement;
if (parentNode) {
height = computeHeight(parentNode);
}
return height;
}
interface IAutoHeightProps {
height?: number;
}
function autoHeight() {
return function<P extends IAutoHeightProps>(
WrappedComponent: React.ComponentClass<P> | React.SFC<P>
): React.ComponentClass<P> {
class AutoHeightComponent extends React.Component<P & IAutoHeightProps> {
state = {
computedHeight: 0,
};
root!: HTMLDivElement;
componentDidMount() {
const { height } = this.props;
if (!height) {
const h = getAutoHeight(this.root);
// eslint-disable-next-line
this.setState({ computedHeight: h });
if (h < 1) {
const h = getAutoHeight(this.root);
this.setState({ computedHeight: h });
}
}
}
handleRoot = (node: HTMLDivElement) => {
this.root = node;
};
render() {
const { height } = this.props;
const { computedHeight } = this.state;
const h = height || computedHeight;
return (
<div ref={this.handleRoot}>
{h > 0 && <WrappedComponent {...this.props} height={h} />}
</div>
);
}
}
return AutoHeightComponent;
};
}
export default autoHeight;
.miniChart {
position: relative;
width: 100%;
.chartContent {
position: absolute;
bottom: -28px;
width: 100%;
> div {
margin: 0 -5px;
overflow: hidden;
}
}
.chartLoading {
position: absolute;
top: 16px;
left: 50%;
margin-left: -7px;
}
}
import Pie from './Pie';
import Gauge from './Gauge';
import WaterWave from './WaterWave';
import TagCloud from './TagCloud';
import MiniArea from './MiniArea';
const Charts = {
Pie,
WaterWave,
Gauge,
MiniArea,
TagCloud,
};
export { Charts as default, Pie, WaterWave, Gauge, TagCloud, MiniArea };
export interface ITag {
name: string;
value: string;
type: string;
}
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale';
import { Row, Col, Card, Tooltip } from 'antd';
import { NumberInfo, Charts } from 'ant-design-pro';
import CountDown from 'ant-design-pro/lib/CountDown';
import numeral from 'numeral';
import ActiveChart from './components/ActiveChart';
import styles from './style.less';
const { Pie, WaterWave, Gauge, TagCloud } = Charts;
const targetTime = new Date().getTime() + 3900000;
@connect(({ BLOCK_NAME_CAMEL_CASE, loading }) => ({
BLOCK_NAME_CAMEL_CASE,
loading: loading.models.monitor,
}))
class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetchTags',
});
}
render() {
const { BLOCK_NAME_CAMEL_CASE, loading } = this.props;
const { tags } = BLOCK_NAME_CAMEL_CASE;
return (
<React.Fragment>
<Row gutter={24}>
<Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.trading-activity"
defaultMessage="Real-Time Trading Activity"
/>
}
bordered={false}
>
<Row>
<Col md={6} sm={12} xs={24}>
<NumberInfo
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.total-transactions"
defaultMessage="Total transactions today"
/>
}
suffix=""
total={numeral(124543233).format('0,0')}
/>
</Col>
<Col md={6} sm={12} xs={24}>
<NumberInfo
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.sales-target"
defaultMessage="Sales target completion rate"
/>
}
total="92%"
/>
</Col>
<Col md={6} sm={12} xs={24}>
<NumberInfo
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.remaining-time"
defaultMessage="Remaining time of activity"
/>
}
total={<CountDown target={targetTime} />}
/>
</Col>
<Col md={6} sm={12} xs={24}>
<NumberInfo
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.total-transactions-per-second"
defaultMessage="Total transactions per second"
/>
}
suffix=""
total={numeral(234).format('0,0')}
/>
</Col>
</Row>
<div className={styles.mapChart}>
<Tooltip
title={
<FormattedMessage
id="BLOCK_NAME.monitor.waiting-for-implementation"
defaultMessage="Waiting for implementation"
/>
}
>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png"
alt="map"
/>
</Tooltip>
</div>
</Card>
</Col>
<Col xl={6} lg={24} md={24} sm={24} xs={24}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.activity-forecast"
defaultMessage="Activity forecast"
/>
}
style={{ marginBottom: 24 }}
bordered={false}
>
<ActiveChart />
</Card>
<Card
title={
<FormattedMessage id="BLOCK_NAME.monitor.efficiency" defaultMessage="Efficiency" />
}
style={{ marginBottom: 24 }}
bodyStyle={{ textAlign: 'center' }}
bordered={false}
>
<Gauge
title={formatMessage({ id: 'BLOCK_NAME.monitor.ratio', defaultMessage: 'Ratio' })}
height={180}
percent={87}
/>
</Card>
</Col>
</Row>
<Row gutter={24}>
<Col xl={12} lg={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.proportion-per-category"
defaultMessage="Proportion Per Category"
/>
}
bordered={false}
className={styles.pieCard}
>
<Row style={{ padding: '16px 0' }}>
<Col span={8}>
<Pie
animate={false}
percent={28}
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.fast-food"
defaultMessage="Fast food"
/>
}
total="28%"
height={128}
lineWidth={2}
/>
</Col>
<Col span={8}>
<Pie
animate={false}
color="#5DDECF"
percent={22}
subTitle={
<FormattedMessage
id="BLOCK_NAME.monitor.western-food"
defaultMessage="Western food"
/>
}
total="22%"
height={128}
lineWidth={2}
/>
</Col>
<Col span={8}>
<Pie
animate={false}
color="#2FC25B"
percent={32}
subTitle={
<FormattedMessage id="BLOCK_NAME.monitor.hot-pot" defaultMessage="Hot pot" />
}
total="32%"
height={128}
lineWidth={2}
/>
</Col>
</Row>
</Card>
</Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.popular-searches"
defaultMessage="Popular Searches"
/>
}
loading={loading}
bordered={false}
bodyStyle={{ overflow: 'hidden' }}
>
<TagCloud data={tags} height={161} />
</Card>
</Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.resource-surplus"
defaultMessage="Resource Surplus"
/>
}
bodyStyle={{ textAlign: 'center', fontSize: 0 }}
bordered={false}
>
<WaterWave
height={161}
title={
<FormattedMessage
id="BLOCK_NAME.monitor.fund-surplus"
defaultMessage="Fund Surplus"
/>
}
percent={34}
/>
</Card>
</Col>
</Row>
</React.Fragment>
);
}
}
export default PAGE_NAME_UPPER_CAMEL_CASE;
import React, { Component } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale';
import { Row, Col, Card, Statistic, Tooltip } from 'antd';
import numeral from 'numeral';
import { Dispatch } from 'redux';
import { IStateType } from './model';
import ActiveChart from './components/ActiveChart';
import styles from './style.less';
import Charts from './components/Charts';
import { GridContent } from '@ant-design/pro-layout';
const { Countdown } = Statistic;
const { Pie, WaterWave, Gauge, TagCloud } = Charts;
const targetTime = new Date().getTime() + 3900000;
interface PAGE_NAME_UPPER_CAMEL_CASEProps {
BLOCK_NAME_CAMEL_CASE: IStateType;
dispatch: Dispatch;
loading: boolean;
}
@connect(
({
BLOCK_NAME_CAMEL_CASE,
loading,
}: {
BLOCK_NAME_CAMEL_CASE: IStateType;
loading: {
models: { [key: string]: boolean };
};
}) => ({
BLOCK_NAME_CAMEL_CASE,
loading: loading.models.monitor,
})
)
class PAGE_NAME_UPPER_CAMEL_CASE extends Component<PAGE_NAME_UPPER_CAMEL_CASEProps> {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetchTags',
});
}
render() {
const { BLOCK_NAME_CAMEL_CASE, loading } = this.props;
const { tags } = BLOCK_NAME_CAMEL_CASE;
return (
<GridContent>
<React.Fragment>
<Row gutter={24}>
<Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.trading-activity"
defaultMessage="Real-Time Trading Activity"
/>
}
bordered={false}
>
<Row>
<Col md={6} sm={12} xs={24}>
<Statistic
title={
<FormattedMessage
id="BLOCK_NAME.monitor.total-transactions"
defaultMessage="Total transactions today"
/>
}
suffix="元"
value={numeral(124543233).format('0,0')}
/>
</Col>
<Col md={6} sm={12} xs={24}>
<Statistic
title={
<FormattedMessage
id="BLOCK_NAME.monitor.sales-target"
defaultMessage="Sales target completion rate"
/>
}
value="92%"
/>
</Col>
<Col md={6} sm={12} xs={24}>
<Statistic
title={
<FormattedMessage
id="BLOCK_NAME.monitor.remaining-time"
defaultMessage="Remaining time of activity"
/>
}
>
<Countdown value={targetTime} />
</Statistic>
</Col>
<Col md={6} sm={12} xs={24}>
<Statistic
title={
<FormattedMessage
id="BLOCK_NAME.monitor.total-transactions-per-second"
defaultMessage="Total transactions per second"
/>
}
suffix="元"
value={numeral(234).format('0,0')}
/>
</Col>
</Row>
<div className={styles.mapChart}>
<Tooltip
title={
<FormattedMessage
id="BLOCK_NAME.monitor.waiting-for-implementation"
defaultMessage="Waiting for implementation"
/>
}
>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png"
alt="map"
/>
</Tooltip>
</div>
</Card>
</Col>
<Col xl={6} lg={24} md={24} sm={24} xs={24}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.activity-forecast"
defaultMessage="Activity forecast"
/>
}
style={{ marginBottom: 24 }}
bordered={false}
>
<ActiveChart />
</Card>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.efficiency"
defaultMessage="Efficiency"
/>
}
style={{ marginBottom: 24 }}
bodyStyle={{ textAlign: 'center' }}
bordered={false}
>
<Gauge
title={formatMessage({
id: 'BLOCK_NAME.monitor.ratio',
defaultMessage: 'Ratio',
})}
height={180}
percent={87}
/>
</Card>
</Col>
</Row>
<Row gutter={24}>
<Col xl={12} lg={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.proportion-per-category"
defaultMessage="Proportion Per Category"
/>
}
bordered={false}
className={styles.pieCard}
>
<Row style={{ padding: '16px 0' }}>
<Col span={8}>
<Pie
animate={false}
percent={28}
title={
<FormattedMessage
id="BLOCK_NAME.monitor.fast-food"
defaultMessage="Fast food"
/>
}
total="28%"
height={128}
lineWidth={2}
/>
</Col>
<Col span={8}>
<Pie
animate={false}
color="#5DDECF"
percent={22}
title={
<FormattedMessage
id="BLOCK_NAME.monitor.western-food"
defaultMessage="Western food"
/>
}
total="22%"
height={128}
lineWidth={2}
/>
</Col>
<Col span={8}>
<Pie
animate={false}
color="#2FC25B"
percent={32}
title={
<FormattedMessage
id="BLOCK_NAME.monitor.hot-pot"
defaultMessage="Hot pot"
/>
}
total="32%"
height={128}
lineWidth={2}
/>
</Col>
</Row>
</Card>
</Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.popular-searches"
defaultMessage="Popular Searches"
/>
}
loading={loading}
bordered={false}
bodyStyle={{ overflow: 'hidden' }}
>
<TagCloud data={tags || []} height={161} />
</Card>
</Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card
title={
<FormattedMessage
id="BLOCK_NAME.monitor.resource-surplus"
defaultMessage="Resource Surplus"
/>
}
bodyStyle={{ textAlign: 'center', fontSize: 0 }}
bordered={false}
>
<WaterWave
height={161}
title={
<FormattedMessage
id="BLOCK_NAME.monitor.fund-surplus"
defaultMessage="Fund Surplus"
/>
}
percent={34}
/>
</Card>
</Col>
</Row>
</React.Fragment>
</GridContent>
);
}
}
export default PAGE_NAME_UPPER_CAMEL_CASE;
import { queryTags } from './service';
import { ITag } from './data';
import { Reducer } from 'redux';
import { EffectsCommandMap } from 'dva';
import { AnyAction } from 'redux';
export default {
export interface IStateType {
tags: ITag[];
}
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: IStateType) => T) => T }
) => void;
export interface ModelType {
namespace: string;
state: IStateType;
effects: {
fetchTags: Effect;
};
reducers: {
saveTags: Reducer<IStateType>;
};
}
const Model: ModelType = {
namespace: 'BLOCK_NAME_CAMEL_CASE',
state: {
......@@ -26,3 +50,5 @@ export default {
},
},
};
export default Model;
......@@ -11,7 +11,6 @@
"url": "https://github.com/umijs/umi-blocks/ant-design-pro/resultfail"
},
"dependencies": {
"@ant-design/pro-layout": "^4.0.5",
"react": "^16.6.3",
"antd": "^3.10.9",
"ant-design-pro": "^2.1.1"
......
{
"private": true,
"scripts": {
"dev": "cross-env PAGES_PATH='CardList/src' umi dev",
"dev": "cross-env PAGES_PATH='Monitor/src' umi dev",
"lint:style": "stylelint \"src/**/*.less\" --syntax less",
"lint": "eslint --ext .js src mock tests && npm run lint:style",
"lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
......
......@@ -14,3 +14,4 @@ declare var APP_TYPE: string;
declare module 'react-fittext';
declare module '@antv/data-set';
declare module 'mockjs';
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment