Unverified Commit c148b641 authored by niko's avatar niko Committed by GitHub

Upgrade BizCharts (#370)

* update bizcharts

* fixed lint

* add charts autoHeight decorator

* 升级 TimelineChart

* 升级 TagCloud

* upgrade bizcharts-plugin-slider

* fix(chart): tag cloud overlapping (#461)

The key point is: `chart.coord().reflect()`.
and you can adjust gaps between words by setting padding
for tag-cloud transform

* upgrade react@16 & bizcharts@3.1.0

* update bizcharts to 3.1.0-beta2

* fix bizcharts repaint

* upgrade to bizcharts@3.1.0-beta.4

* fix TimelineCharts detail style
parent cc28e069
...@@ -14,3 +14,4 @@ _roadhog-api-doc ...@@ -14,3 +14,4 @@ _roadhog-api-doc
npm-debug.log* npm-debug.log*
/coverage /coverage
.idea
...@@ -13,11 +13,6 @@ ...@@ -13,11 +13,6 @@
] ]
} }
}, },
"externals": {
"g2": "G2",
"g-cloud": "Cloud",
"g2-plugin-slider": "G2.Plugin.slider"
},
"ignoreMomentLocale": true, "ignoreMomentLocale": true,
"theme": "./src/theme.js", "theme": "./src/theme.js",
"hash": true "hash": true
......
...@@ -19,17 +19,17 @@ ...@@ -19,17 +19,17 @@
"test:all": "node ./tests/run-tests.js" "test:all": "node ./tests/run-tests.js"
}, },
"dependencies": { "dependencies": {
"@antv/data-set": "^0.8.0",
"antd": "^3.0.0", "antd": "^3.0.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-runtime": "^6.9.2", "babel-runtime": "^6.9.2",
"bizcharts": "^3.1.0-beta.4",
"bizcharts-plugin-slider": "^2.0.1",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"core-js": "^2.5.1", "core-js": "^2.5.1",
"dva": "^2.1.0", "dva": "^2.1.0",
"enquire-js": "^0.1.1", "enquire-js": "^0.1.1",
"fastclick": "^1.0.6", "fastclick": "^1.0.6",
"g-cloud": "^1.0.2-beta",
"g2": "^2.3.13",
"g2-plugin-slider": "^1.2.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"lodash-decorators": "^4.4.1", "lodash-decorators": "^4.4.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
......
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import { MiniArea } from '../Charts'; import { MiniArea } from '../Charts';
import NumberInfo from '../NumberInfo'; import NumberInfo from '../NumberInfo';
...@@ -14,16 +14,16 @@ function getActiveData() { ...@@ -14,16 +14,16 @@ function getActiveData() {
for (let i = 0; i < 24; i += 1) { for (let i = 0; i < 24; i += 1) {
activeData.push({ activeData.push({
x: `${fixedZero(i)}:00`, x: `${fixedZero(i)}:00`,
y: (i * 50) + (Math.floor(Math.random() * 200)), y: Math.floor(Math.random() * 200) + (i * 50),
}); });
} }
return activeData; return activeData;
} }
export default class ActiveChart extends PureComponent { export default class ActiveChart extends Component {
state = { state = {
activeData: getActiveData(), activeData: getActiveData(),
} };
componentDidMount() { componentDidMount() {
this.timer = setInterval(() => { this.timer = setInterval(() => {
...@@ -42,43 +42,40 @@ export default class ActiveChart extends PureComponent { ...@@ -42,43 +42,40 @@ export default class ActiveChart extends PureComponent {
return ( return (
<div className={styles.activeChart}> <div className={styles.activeChart}>
<NumberInfo <NumberInfo subTitle="目标评估" total="有望达到预期" />
subTitle="目标评估"
total="有望达到预期"
/>
<div style={{ marginTop: 32 }}> <div style={{ marginTop: 32 }}>
<MiniArea <MiniArea
animate={false} animate={false}
line line
borderWidth={2} borderWidth={2}
height={84} height={84}
scale={{
y: {
tickCount: 3,
},
}}
yAxis={{ yAxis={{
tickCount: 3,
tickLine: false, tickLine: false,
labels: false, label: false,
title: false, title: false,
line: false, line: false,
}} }}
data={activeData} data={activeData}
/> />
</div> </div>
{ {activeData && (
activeData && ( <div className={styles.activeChartGrid}>
<div className={styles.activeChartGrid}> <p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p> <p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p> </div>
</div> )}
) {activeData && (
} <div className={styles.activeChartLegend}>
{ <span>00:00</span>
activeData && ( <span>{activeData[Math.floor(activeData.length / 2)].x}</span>
<div className={styles.activeChartLegend}> <span>{activeData[activeData.length - 1].x}</span>
<span>00:00</span> </div>
<span>{activeData[Math.floor(activeData.length / 2)].x}</span> )}
<span>{activeData[activeData.length - 1].x}</span>
</div>
)
}
</div> </div>
); );
} }
......
...@@ -2,7 +2,7 @@ import * as React from "react"; ...@@ -2,7 +2,7 @@ import * as React from "react";
export interface BarProps { export interface BarProps {
title: React.ReactNode; title: React.ReactNode;
color?: string; color?: string;
margin?: [number, number, number, number]; padding?: [number, number, number, number];
height: number; height: number;
data: Array<{ data: Array<{
x: string; x: string;
......
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import G2 from 'g2'; import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
import Debounce from 'lodash-decorators/debounce'; import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind'; import Bind from 'lodash-decorators/bind';
import equal from '../equal'; import autoHeight from '../autoHeight';
import styles from '../index.less'; import styles from '../index.less';
class Bar extends PureComponent { @autoHeight()
class Bar extends Component {
state = { state = {
autoHideXLabels: false, autoHideXLabels: false,
} };
componentDidMount() { componentDidMount() {
this.renderChart(this.props.data);
window.addEventListener('resize', this.resize); window.addEventListener('resize', this.resize);
} }
componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) {
this.renderChart(nextProps.data);
}
}
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.resize); window.removeEventListener('resize', this.resize);
if (this.chart) {
this.chart.destroy();
}
this.resize.cancel();
} }
@Bind() @Bind()
@Debounce(200) @Debounce(400)
resize() { resize() {
if (!this.node) { if (!this.node) {
return; return;
...@@ -49,99 +38,60 @@ class Bar extends PureComponent { ...@@ -49,99 +38,60 @@ class Bar extends PureComponent {
this.setState({ this.setState({
autoHideXLabels: true, autoHideXLabels: true,
}); });
this.renderChart(data);
} }
} else if (autoHideXLabels) { } else if (autoHideXLabels) {
this.setState({ this.setState({
autoHideXLabels: false, autoHideXLabels: false,
}); });
this.renderChart(data);
} }
} }
handleRoot = (n) => {
this.root = n;
};
handleRef = (n) => { handleRef = (n) => {
this.node = n; this.node = n;
} };
renderChart(data) {
const { autoHideXLabels } = this.state;
const {
height = 0,
fit = true,
color = 'rgba(24, 144, 255, 0.85)',
margin = [32, 0, (autoHideXLabels ? 8 : 32), 40],
} = this.props;
if (!data || (data && data.length < 1)) {
return;
}
// clean render() {
this.node.innerHTML = ''; const { height, title, forceFit = true, data, color = 'rgba(24, 144, 255, 0.85)', padding } = this.props;
const { Frame } = G2;
const frame = new Frame(data);
const chart = new G2.Chart({
container: this.node,
forceFit: fit,
height: height - 22,
legend: null,
plotCfg: {
margin,
},
});
if (autoHideXLabels) { const { autoHideXLabels } = this.state;
chart.axis('x', {
title: false,
tickLine: false,
labels: false,
});
} else {
chart.axis('x', {
title: false,
});
}
chart.axis('y', {
title: false,
line: false,
tickLine: false,
});
chart.source(frame, { const scale = {
x: { x: {
type: 'cat', type: 'cat',
}, },
y: { y: {
min: 0, min: 0,
}, },
}); };
chart.tooltip({
title: null,
crosshairs: false,
map: {
name: 'x',
},
});
chart.interval().position('x*y').color(color).style({
fillOpacity: 1,
});
chart.render();
this.chart = chart; const tooltip = [
} 'x*y',
(x, y) => ({
render() { name: x,
const { height, title } = this.props; value: y,
}),
];
return ( return (
<div className={styles.chart} style={{ height }}> <div className={styles.chart} style={{ height }} ref={this.handleRoot}>
<div> <div ref={this.handleRef}>
{ title && <h4>{title}</h4>} {title && <h4>{title}</h4>}
<div ref={this.handleRef} /> <Chart
scale={scale}
height={height}
forceFit={forceFit}
data={data}
padding={padding || 'auto'}
>
<Axis name="x" title={false} label={!autoHideXLabels} tickLine={!autoHideXLabels} />
<Axis name="y" title={false} line={false} tickLine={false} min={0} />
<Tooltip showTitle={false} crosshairs={false} />
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
</Chart>
</div> </div>
</div> </div>
); );
......
import React, { PureComponent } from 'react'; import React from 'react';
import G2 from 'g2'; import { Chart, Geom, Axis, Coord, Guide, Shape } from 'bizcharts';
import equal from '../equal'; import autoHeight from '../autoHeight';
const { Shape } = G2; const { Arc, Html, Line } = Guide;
const primaryColor = '#2F9CFF'; const defaultFormatter = (val) => {
const backgroundColor = '#F0F2F5'; switch (val) {
case '2':
/* eslint no-underscore-dangle: 0 */ return '';
class Gauge extends PureComponent { case '4':
componentDidMount() { return '';
setTimeout(() => { case '6':
this.renderChart(); return '';
}, 10); case '8':
return '';
default:
return '';
} }
};
componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) { Shape.registerShape('point', 'pointer', {
setTimeout(() => { drawShape(cfg, group) {
this.renderChart(nextProps); let point = cfg.points[0];
}, 10); point = this.parsePoint(point);
} const center = this.parsePoint({
} x: 0,
y: 0,
componentWillUnmount() {
if (this.chart) {
this.chart.destroy();
}
}
handleRef = (n) => {
this.node = n;
}
initChart(nextProps) {
const { title, color = primaryColor } = nextProps || this.props;
Shape.registShape('point', 'dashBoard', {
drawShape(cfg, group) {
const originPoint = cfg.points[0];
const point = this.parsePoint({ x: originPoint.x, y: 0.4 });
const center = this.parsePoint({
x: 0,
y: 0,
});
const shape = group.addShape('polygon', {
attrs: {
points: [
[center.x, center.y],
[point.x + 8, point.y],
[point.x + 8, point.y - 2],
[center.x, center.y - 2],
],
radius: 2,
lineWidth: 2,
arrow: false,
fill: color,
},
});
group.addShape('Marker', {
attrs: {
symbol: 'circle',
lineWidth: 2,
fill: color,
radius: 8,
x: center.x,
y: center.y,
},
});
group.addShape('Marker', {
attrs: {
symbol: 'circle',
lineWidth: 2,
fill: '#fff',
radius: 5,
x: center.x,
y: center.y,
},
});
const { origin } = cfg;
group.addShape('text', {
attrs: {
x: center.x,
y: center.y + 80,
text: `${origin._origin.value}%`,
textAlign: 'center',
fontSize: 24,
fill: 'rgba(0, 0, 0, 0.85)',
},
});
group.addShape('text', {
attrs: {
x: center.x,
y: center.y + 45,
text: title,
textAlign: 'center',
fontSize: 14,
fill: 'rgba(0, 0, 0, 0.43)',
},
});
return shape;
},
}); });
} group.addShape('line', {
attrs: {
renderChart(nextProps) { x1: center.x,
const { y1: center.y,
height, color = primaryColor, bgColor = backgroundColor, title, percent, format, x2: point.x,
} = nextProps || this.props; y2: point.y,
const data = [{ name: title, value: percent }]; stroke: cfg.color,
lineWidth: 2,
if (this.chart) { lineCap: 'round',
this.chart.clear();
}
if (this.node) {
this.node.innerHTML = '';
}
this.initChart(nextProps);
const chart = new G2.Chart({
container: this.node,
forceFit: true,
height,
animate: false,
plotCfg: {
margin: [10, 10, 30, 10],
}, },
}); });
return group.addShape('circle', {
chart.source(data); attrs: {
x: center.x,
chart.tooltip(false); y: center.y,
r: 6,
chart.coord('gauge', { stroke: cfg.color,
startAngle: -1.2 * Math.PI, lineWidth: 3,
endAngle: 0.20 * Math.PI, fill: '#fff',
});
chart.col('value', {
type: 'linear',
nice: true,
min: 0,
max: 100,
tickCount: 6,
});
chart.axis('value', {
subTick: false,
tickLine: {
stroke: color,
lineWidth: 2,
value: -14,
}, },
labelOffset: -12,
formatter: format,
}); });
chart.point().position('value').shape('dashBoard'); },
draw(data); });
/* eslint no-shadow: 0 */
function draw(data) {
const val = data[0].value;
const lineWidth = 12;
chart.guide().clear();
chart.guide().arc(() => {
return [0, 0.95];
}, () => {
return [val, 0.95];
}, {
stroke: color,
lineWidth,
});
chart.guide().arc(() => {
return [val, 0.95];
}, (arg) => {
return [arg.max, 0.95];
}, {
stroke: bgColor,
lineWidth,
});
chart.changeData(data);
}
this.chart = chart;
}
@autoHeight()
export default class Gauge extends React.Component {
render() { render() {
const {
title,
height,
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 data = [{ value: percent / 10 }];
return ( return (
<div ref={this.handleRef} /> <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={null} />
<Axis
line={null}
tickLine={null}
subTickLine={null}
name="value"
zIndex={2}
gird={null}
label={{
offset: -12,
formatter,
textStyle: {
fontSize: 12,
fill: 'rgba(0, 0, 0, 0.65)',
textAlign: 'center',
},
}}
/>
<Guide>
<Line
start={[3, 0.905]}
end={[3, 0.85]}
lineStyle={{
stroke: color,
lineDash: null,
lineWidth: 2,
}}
/>
<Line
start={[5, 0.905]}
end={[5, 0.85]}
lineStyle={{
stroke: color,
lineDash: null,
lineWidth: 3,
}}
/>
<Line
start={[7, 0.905]}
end={[7, 0.85]}
lineStyle={{
stroke: color,
lineDash: null,
lineWidth: 3,
}}
/>
<Arc
zIndex={0}
start={[0, 0.965]}
end={[10, 0.965]}
style={{
stroke: bgColor,
lineWidth: 10,
}}
/>
<Arc
zIndex={1}
start={[0, 0.965]}
end={[data[0].value, 0.965]}
style={{
stroke: color,
lineWidth: 10,
}}
/>
<Html
position={['50%', '95%']}
html={() => {
return `
<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}%
</p>
</div>`;
}}
/>
</Guide>
<Geom
line={false}
type="point"
position="value*1"
shape="pointer"
color={color}
active={false}
/>
</Chart>
); );
} }
} }
export default Gauge;
import React, { PureComponent } from 'react'; import React from 'react';
import G2 from 'g2'; import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
import equal from '../equal'; import autoHeight from '../autoHeight';
import styles from '../index.less'; import styles from '../index.less';
class MiniArea extends PureComponent { @autoHeight()
static defaultProps = { export default class MiniArea extends React.Component {
borderColor: '#1890FF', render() {
color: 'rgba(24, 144, 255, 0.2)',
};
componentDidMount() {
this.renderChart(this.props.data);
}
componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) {
this.renderChart(nextProps.data);
}
}
componentWillUnmount() {
if (this.chart) {
this.chart.destroy();
}
}
handleRef = (n) => {
this.node = n;
}
renderChart(data) {
const { const {
height = 0, fit = true, color, borderWidth = 2, line, xAxis, yAxis, animate = true, height,
data = [],
forceFit = true,
color = 'rgba(24, 144, 255, 0.2)',
scale = {},
borderWidth = 2,
line,
xAxis,
yAxis,
animate = true,
} = this.props; } = this.props;
const borderColor = this.props.borderColor || color;
if (!data || (data && data.length < 1)) {
return;
}
// clean
this.node.innerHTML = '';
const chart = new G2.Chart({ const borderColor = this.props.borderColor || color;
container: this.node,
forceFit: fit,
height: height + 54,
animate,
plotCfg: {
margin: [36, 5, 30, 5],
},
legend: null,
});
if (!xAxis && !yAxis) {
chart.axis(false);
}
if (xAxis) {
chart.axis('x', xAxis);
} else {
chart.axis('x', false);
}
if (yAxis) { const padding = [36, 5, 30, 5];
chart.axis('y', yAxis);
} else {
chart.axis('y', false);
}
const dataConfig = { const scaleProps = {
x: { x: {
type: 'cat', type: 'cat',
range: [0, 1], range: [0, 1],
...xAxis, ...scale.x,
}, },
y: { y: {
min: 0, min: 0,
...yAxis, ...scale.y,
}, },
}; };
chart.tooltip({ const tooltip = [
title: null, 'x*y',
crosshairs: false, (x, y) => ({
map: { name: x,
title: null, value: y,
name: 'x', }),
value: 'y', ];
},
});
const view = chart.createView();
view.source(data, dataConfig);
view.area().position('x*y').color(color).shape('smooth') const chartHeight = height + 54;
.style({ fillOpacity: 1 });
if (line) {
const view2 = chart.createView();
view2.source(data, dataConfig);
view2.line().position('x*y').color(borderColor).size(borderWidth)
.shape('smooth');
view2.tooltip(false);
}
chart.render();
this.chart = chart;
}
render() {
const { height } = this.props;
return ( return (
<div className={styles.miniChart} style={{ height }}> <div className={styles.miniChart} style={{ height }}>
<div className={styles.chartContent}> <div className={styles.chartContent}>
<div ref={this.handleRef} /> {height > 0 && (
<Chart
animate={animate}
scale={scaleProps}
height={chartHeight}
forceFit={forceFit}
data={data}
padding={padding}
>
<Axis name="x" label={false} line={false} tickLine={false} grid={false} {...xAxis} />
<Axis 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>
</div> </div>
); );
} }
} }
export default MiniArea;
import React, { PureComponent } from 'react'; import React from 'react';
import G2 from 'g2'; import { Chart, Tooltip, Geom } from 'bizcharts';
import equal from '../equal'; import autoHeight from '../autoHeight';
import styles from '../index.less'; import styles from '../index.less';
class MiniBar extends PureComponent { @autoHeight()
componentDidMount() { export default class MiniBar extends React.Component {
this.renderChart(this.props.data); render() {
} const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) {
this.renderChart(nextProps.data);
}
}
componentWillUnmount() {
if (this.chart) {
this.chart.destroy();
}
}
handleRef = (n) => {
this.node = n;
}
renderChart(data) {
const { height = 0, fit = true, color = '#1890FF' } = this.props;
if (!data || (data && data.length < 1)) {
return;
}
// clean
this.node.innerHTML = '';
const { Frame } = G2;
const frame = new Frame(data);
const chart = new G2.Chart({
container: this.node,
forceFit: fit,
height: height + 54,
plotCfg: {
margin: [36, 5, 30, 5],
},
legend: null,
});
chart.axis(false);
chart.source(frame, { const scale = {
x: { x: {
type: 'cat', type: 'cat',
}, },
y: { y: {
min: 0, min: 0,
}, },
}); };
chart.tooltip({ const padding = [36, 5, 30, 5];
title: null,
crosshairs: false,
map: {
name: 'x',
},
});
chart.interval().position('x*y').color(color);
chart.render();
this.chart = chart; const tooltip = [
} 'x*y',
(x, y) => ({
name: x,
value: y,
}),
];
render() { // for tooltip not to be hide
const { height } = this.props; const chartHeight = height + 54;
return ( return (
<div className={styles.miniChart} style={{ height }}> <div className={styles.miniChart} style={{ height }}>
<div className={styles.chartContent}> <div className={styles.chartContent}>
<div ref={this.handleRef} /> <Chart
scale={scale}
height={chartHeight}
forceFit={forceFit}
data={data}
padding={padding}
>
<Tooltip showTitle={false} crosshairs={false} />
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
</Chart>
</div> </div>
</div> </div>
); );
} }
} }
export default MiniBar;
...@@ -4,7 +4,7 @@ export interface PieProps { ...@@ -4,7 +4,7 @@ export interface PieProps {
color?: string; color?: string;
height: number; height: number;
hasLegend?: boolean; hasLegend?: boolean;
margin?: [number, number, number, number]; padding?: [number, number, number, number];
percent?: number; percent?: number;
data?: Array<{ data?: Array<{
x: string; x: string;
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import G2 from 'g2'; import { Chart, Tooltip, Geom, Coord } from 'bizcharts';
import { DataView } from '@antv/data-set';
import { Divider } from 'antd'; import { Divider } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import ReactFitText from 'react-fittext'; import ReactFitText from 'react-fittext';
import Debounce from 'lodash-decorators/debounce'; import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind'; import Bind from 'lodash-decorators/bind';
import equal from '../equal'; import autoHeight from '../autoHeight';
import styles from './index.less'; import styles from './index.less';
/* eslint react/no-danger:0 */ /* eslint react/no-danger:0 */
class Pie extends Component { @autoHeight()
export default class Pie extends Component {
state = { state = {
legendData: [], legendData: [],
legendBlock: true, legendBlock: false,
}; };
componentDidMount() { componentDidMount() {
this.renderChart(); this.getLengendData();
this.resize(); this.resize();
window.addEventListener('resize', this.resize); window.addEventListener('resize', this.resize);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) { if (this.props.data !== nextProps.data) {
this.renderChart(nextProps.data); this.getLengendData();
} }
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.resize); window.removeEventListener('resize', this.resize);
if (this.chart) {
this.chart.destroy();
}
this.resize.cancel(); 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() @Bind()
@Debounce(300) @Debounce(300)
resize() { resize() {
...@@ -47,26 +71,18 @@ class Pie extends Component { ...@@ -47,26 +71,18 @@ class Pie extends Component {
if (!this.state.legendBlock) { if (!this.state.legendBlock) {
this.setState({ this.setState({
legendBlock: true, legendBlock: true,
}, () => {
this.renderChart();
}); });
} }
} else if (this.state.legendBlock) { } else if (this.state.legendBlock) {
this.setState({ this.setState({
legendBlock: false, legendBlock: false,
}, () => {
this.renderChart();
}); });
} }
} }
handleRef = (n) => {
this.node = n;
}
handleRoot = (n) => { handleRoot = (n) => {
this.root = n; this.root = n;
} };
handleLegendClick = (item, i) => { handleLegendClick = (item, i) => {
const newItem = item; const newItem = item;
...@@ -75,37 +91,57 @@ class Pie extends Component { ...@@ -75,37 +91,57 @@ class Pie extends Component {
const { legendData } = this.state; const { legendData } = this.state;
legendData[i] = newItem; legendData[i] = newItem;
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.x);
if (this.chart) { if (this.chart) {
const filterItem = legendData.filter(l => l.checked).map(l => l.x); this.chart.filter('x', val => filteredLegendData.indexOf(val) > -1);
this.chart.filter('x', filterItem);
this.chart.repaint();
} }
this.setState({ this.setState({
legendData, legendData,
}); });
} };
renderChart(d) {
let data = d || this.props.data;
render() {
const { const {
height = 0, valueFormat,
hasLegend, subTitle,
fit = true, total,
margin = [12, 0, 12, 0], percent, color, hasLegend = false,
className,
style,
height,
forceFit = true,
percent = 0,
color,
inner = 0.75, inner = 0.75,
animate = true, animate = true,
colors, colors,
lineWidth = 0, lineWidth = 1,
} = this.props; } = this.props;
const defaultColors = colors; 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 selected = this.props.selected || true;
let tooltip = this.props.tooltips || true; let tooltip = this.props.tooltip || true;
let formatColor; let formatColor;
const scale = {
x: {
type: 'cat',
range: [0, 1],
},
y: {
min: 0,
},
};
if (percent) { if (percent) {
selected = false; selected = false;
tooltip = false; tooltip = false;
...@@ -117,7 +153,6 @@ class Pie extends Component { ...@@ -117,7 +153,6 @@ class Pie extends Component {
} }
}; };
/* eslint no-param-reassign: */
data = [ data = [
{ {
x: '占比', x: '占比',
...@@ -130,131 +165,81 @@ class Pie extends Component { ...@@ -130,131 +165,81 @@ class Pie extends Component {
]; ];
} }
if (!data || (data && data.length < 1)) { const tooltipFormat = [
return; 'x*percent',
} (x, p) => ({
name: x,
// clean value: `${(p * 100).toFixed(2)}%`,
this.node.innerHTML = ''; }),
];
const { Stat } = G2;
const padding = [12, 0, 12, 0];
const chart = new G2.Chart({
container: this.node, const dv = new DataView();
forceFit: fit, dv.source(data).transform({
height, type: 'percent',
plotCfg: { field: 'y',
margin, dimension: 'x',
}, as: 'percent',
animate,
});
if (!tooltip) {
chart.tooltip(false);
} else {
chart.tooltip({
title: null,
});
}
chart.axis(false);
chart.legend(false);
chart.source(data, {
x: {
type: 'cat',
range: [0, 1],
},
y: {
min: 0,
},
});
chart.coord('theta', {
inner,
});
chart
.intervalStack()
.position(Stat.summary.percent('y'))
.style({ lineWidth, stroke: '#fff' })
.color('x', percent ? formatColor : defaultColors)
.selected(selected);
chart.render();
this.chart = chart;
let legendData = [];
if (hasLegend) {
const geom = chart.getGeoms()[0]; // 获取所有的图形
const items = geom.getData(); // 获取图形对应的数据
legendData = items.map((item) => {
/* eslint no-underscore-dangle:0 */
const origin = item._origin;
origin.color = item.color;
origin.checked = true;
return origin;
});
}
this.setState({
legendData,
});
}
render() {
const { valueFormat, subTitle, total, hasLegend, className, style } = this.props;
const { legendData, legendBlock } = this.state;
const pieClassName = classNames(styles.pie, className, {
[styles.hasLegend]: !!hasLegend,
[styles.legendBlock]: legendBlock,
}); });
return ( return (
<div ref={this.handleRoot} className={pieClassName} style={style}> <div ref={this.handleRoot} className={pieClassName} style={style}>
<ReactFitText maxFontSize={25}> <ReactFitText maxFontSize={25}>
<div className={styles.chart}> <div className={styles.chart}>
<div ref={this.handleRef} style={{ fontSize: 0 }} /> <Chart
{ scale={scale}
(subTitle || total) && ( height={height}
<div className={styles.total}> forceFit={forceFit}
{subTitle && <h4 className="pie-sub-title">{subTitle}</h4>} data={dv}
{ padding={padding}
// eslint-disable-next-line animate={animate}
total && <div className="pie-stat" dangerouslySetInnerHTML={{ __html: total }} /> onGetG2Instance={this.getG2Instance}
} >
</div> {!!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" dangerouslySetInnerHTML={{ __html: total }} />}
</div>
)}
</div> </div>
</ReactFitText> </ReactFitText>
{ {hasLegend && (
hasLegend && ( <ul className={styles.legend}>
<ul className={styles.legend}> {legendData.map((item, i) => (
{ <li key={item.x} onClick={() => this.handleLegendClick(item, i)}>
legendData.map((item, i) => ( <span
<li key={item.x} onClick={() => this.handleLegendClick(item, i)}> className={styles.dot}
<span className={styles.dot} style={{ backgroundColor: !item.checked ? '#aaa' : item.color }} /> style={{ backgroundColor: !item.checked ? '#aaa' : item.color }}
<span className={styles.legendTitle}>{item.x}</span> />
<Divider type="vertical" /> <span className={styles.legendTitle}>{item.x}</span>
<span className={styles.percent}>{`${(item['..percent'] * 100).toFixed(2)}%`}</span> <Divider type="vertical" />
<span <span className={styles.percent}>{`${(item.percent * 100).toFixed(2)}%`}</span>
className={styles.value} <span
dangerouslySetInnerHTML={{ className={styles.value}
__html: valueFormat ? valueFormat(item.y) : item.y, dangerouslySetInnerHTML={{
}} __html: valueFormat ? valueFormat(item.y) : item.y,
/> }}
</li> />
)) </li>
} ))}
</ul> </ul>
) )}
}
</div> </div>
); );
} }
} }
export default Pie;
...@@ -2,7 +2,7 @@ import * as React from "react"; ...@@ -2,7 +2,7 @@ import * as React from "react";
export interface RadarProps { export interface RadarProps {
title?: React.ReactNode; title?: React.ReactNode;
height: number; height: number;
margin?: [number, number, number, number]; padding?: [number, number, number, number];
hasLegend?: boolean; hasLegend?: boolean;
data: Array<{ data: Array<{
name: string; name: string;
......
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import G2 from 'g2'; import { Chart, Tooltip, Geom, Coord, Axis } from 'bizcharts';
import { Row, Col } from 'antd'; import { Row, Col } from 'antd';
import equal from '../equal'; import autoHeight from '../autoHeight';
import styles from './index.less'; import styles from './index.less';
/* eslint react/no-danger:0 */ /* eslint react/no-danger:0 */
class Radar extends PureComponent { @autoHeight()
export default class Radar extends Component {
state = { state = {
legendData: [], legendData: [],
} };
componentDidMount() { componentDidMount() {
this.renderChart(this.props.data); this.getLengendData();
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (!equal(this.props, nextProps)) { if (this.props.data !== nextProps.data) {
this.renderChart(nextProps.data); this.getLengendData();
} }
} }
componentWillUnmount() { getG2Instance = (chart) => {
if (this.chart) { this.chart = chart;
this.chart.destroy(); };
}
} // 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-disable-next-line
const origins = item.map(t => t._origin);
const result = {
name: origins[0].name,
color: item[0].color,
checked: true,
value: origins.reduce((p, n) => p + n.value, 0),
};
return result;
});
this.setState({
legendData,
});
};
handleRef = (n) => { handleRef = (n) => {
this.node = n; this.node = n;
} };
handleLegendClick = (item, i) => { handleLegendClick = (item, i) => {
const newItem = item; const newItem = item;
...@@ -37,153 +60,121 @@ class Radar extends PureComponent { ...@@ -37,153 +60,121 @@ class Radar extends PureComponent {
const { legendData } = this.state; const { legendData } = this.state;
legendData[i] = newItem; legendData[i] = newItem;
const filteredLegendData = legendData.filter(l => l.checked).map(l => l.name);
if (this.chart) { if (this.chart) {
const filterItem = legendData.filter(l => l.checked).map(l => l.name); this.chart.filter('name', val => filteredLegendData.indexOf(val) > -1);
this.chart.filter('name', filterItem);
this.chart.repaint(); this.chart.repaint();
} }
this.setState({ this.setState({
legendData, legendData,
}); });
} };
renderChart(data) {
const { height = 0,
hasLegend = true,
fit = true,
tickCount = 4,
margin = [24, 30, 16, 30] } = this.props;
const colors = [ render() {
'#1890FF', '#FACC14', '#2FC25B', '#8543E0', '#F04864', '#13C2C2', '#fa8c16', '#a0d911', const defaultColors = [
'#1890FF',
'#FACC14',
'#2FC25B',
'#8543E0',
'#F04864',
'#13C2C2',
'#fa8c16',
'#a0d911',
]; ];
if (!data || (data && data.length < 1)) { const {
return; data = [],
} height = 0,
title,
// clean hasLegend = false,
this.node.innerHTML = ''; forceFit = true,
tickCount = 4,
const chart = new G2.Chart({ padding = [35, 30, 16, 30],
container: this.node, animate = true,
forceFit: fit, colors = defaultColors,
height: height - (hasLegend ? 80 : 22), } = this.props;
plotCfg: {
margin,
},
});
this.chart = chart; const { legendData } = this.state;
chart.source(data, { const scale = {
value: { value: {
min: 0, min: 0,
tickCount, tickCount,
}, },
}); };
chart.coord('polar');
chart.legend(false);
chart.axis('label', {
line: null,
labelOffset: 8,
labels: {
label: {
fill: 'rgba(0, 0, 0, .65)',
},
},
grid: {
line: {
stroke: '#e9e9e9',
lineWidth: 1,
lineDash: [0, 0],
},
},
});
chart.axis('value', {
grid: {
type: 'polygon',
line: {
stroke: '#e9e9e9',
lineWidth: 1,
lineDash: [0, 0],
},
},
labels: {
label: {
fill: 'rgba(0, 0, 0, .65)',
},
},
});
chart.line().position('label*value').color('name', colors);
chart.point().position('label*value').color('name', colors).shape('circle')
.size(3);
chart.render();
if (hasLegend) {
const geom = chart.getGeoms()[0]; // 获取所有的图形
const items = geom.getData(); // 获取图形对应的数据
const legendData = items.map((item) => {
/* eslint no-underscore-dangle:0 */
const origin = item._origin;
const result = {
name: origin[0].name,
color: item.color,
checked: true,
value: origin.reduce((p, n) => p + n.value, 0),
};
return result;
});
this.setState({
legendData,
});
}
}
render() { const chartHeight = height - (hasLegend ? 80 : 22);
const { height, title, hasLegend } = this.props;
const { legendData } = this.state;
return ( return (
<div className={styles.radar} style={{ height }}> <div className={styles.radar} style={{ height }}>
<div> <div>
{title && <h4>{title}</h4>} {title && <h4>{title}</h4>}
<div ref={this.handleRef} /> <Chart
{ scale={scale}
hasLegend && ( height={chartHeight}
<Row className={styles.legend}> forceFit={forceFit}
{ data={data}
legendData.map((item, i) => ( padding={padding}
<Col animate={animate}
span={(24 / legendData.length)} onGetG2Instance={this.getG2Instance}
key={item.name} >
onClick={() => this.handleLegendClick(item, i)} <Tooltip />
> <Coord type="polar" />
<div className={styles.legendItem}> <Axis
<p> name="label"
<span className={styles.dot} style={{ backgroundColor: !item.checked ? '#aaa' : item.color }} /> line={null}
<span>{item.name}</span> tickLine={null}
</p> grid={{
<h6>{item.value}</h6> lineStyle: {
</div> lineDash: null,
</Col> },
)) hideFirstLine: false,
} }}
</Row> />
) <Axis
} name="value"
grid={{
type: 'polygon',
lineStyle: {
lineDash: null,
},
}}
/>
<Geom type="line" position="label*value" color={['name', colors]} size={1} />
<Geom
type="point"
position="label*value"
color={['name', colors]}
shape="circle"
size={3}
/>
</Chart>
{hasLegend && (
<Row className={styles.legend}>
{legendData.map((item, i) => (
<Col
span={24 / legendData.length}
key={item.name}
onClick={() => this.handleLegendClick(item, i)}
>
<div className={styles.legendItem}>
<p>
<span
className={styles.dot}
style={{ backgroundColor: !item.checked ? '#aaa' : item.color }}
/>
<span>{item.name}</span>
</p>
<h6>{item.value}</h6>
</div>
</Col>
))}
</Row>
)}
</div> </div>
</div> </div>
); );
} }
} }
export default Radar;
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import classNames from 'classnames'; import { Chart, Geom, Coord, Shape } from 'bizcharts';
import G2 from 'g2'; import DataSet from '@antv/data-set';
import Cloud from 'g-cloud';
import Debounce from 'lodash-decorators/debounce'; import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind'; import Bind from 'lodash-decorators/bind';
import classNames from 'classnames';
import autoHeight from '../autoHeight';
import styles from './index.less'; import styles from './index.less';
/* eslint no-underscore-dangle: 0 */ /* eslint no-underscore-dangle: 0 */
...@@ -11,157 +12,130 @@ import styles from './index.less'; ...@@ -11,157 +12,130 @@ import styles from './index.less';
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'; const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
class TagCloud extends PureComponent { @autoHeight()
class TagCloud extends Component {
state = {
dv: null,
};
componentDidMount() { componentDidMount() {
this.initTagCloud(); this.initTagCloud();
this.renderChart(); this.renderChart();
window.addEventListener('resize', this.resize); window.addEventListener('resize', this.resize);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (this.props.data !== nextProps.data) { if (JSON.stringify(nextProps.data) !== JSON.stringify(this.props.data)) {
this.renderChart(nextProps.data); this.renderChart(nextProps);
} }
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.resize); window.removeEventListener('resize', this.resize);
this.renderChart.cancel();
} }
resize = () => { resize = () => {
this.renderChart(); this.renderChart();
} };
initTagCloud = () => { saveRootRef = (node) => {
const { Util, Shape } = G2; this.root = node;
};
initTagCloud = () => {
function getTextAttrs(cfg) { function getTextAttrs(cfg) {
const textAttrs = Util.mix(true, {}, { return Object.assign(
fillOpacity: cfg.opacity, {},
fontSize: cfg.size, {
rotate: cfg.origin._origin.rotate, fillOpacity: cfg.opacity,
// rotate: cfg.origin._origin.rotate, fontSize: cfg.origin._origin.size,
text: cfg.origin._origin.text, rotate: cfg.origin._origin.rotate,
textAlign: 'center', text: cfg.origin._origin.text,
fill: cfg.color, textAlign: 'center',
textBaseline: 'Alphabetic', fontFamily: cfg.origin._origin.font,
}, cfg.style); fill: cfg.color,
return textAttrs; textBaseline: 'Alphabetic',
},
cfg.style
);
} }
// 给point注册一个词云的shape // 给point注册一个词云的shape
Shape.registShape('point', 'cloud', { Shape.registerShape('point', 'cloud', {
drawShape(cfg, container) { drawShape(cfg, container) {
cfg.points = this.parsePoints(cfg.points);
const attrs = getTextAttrs(cfg); const attrs = getTextAttrs(cfg);
const shape = container.addShape('text', { return container.addShape('text', {
attrs: Util.mix(attrs, { attrs: Object.assign(attrs, {
x: cfg.points[0].x, x: cfg.x,
y: cfg.points[0].y, y: cfg.y,
}), }),
}); });
return shape;
}, },
}); });
} };
saveRootRef = (node) => {
this.root = node;
}
saveNodeRef = (node) => {
this.node = node;
}
@Bind() @Bind()
@Debounce(500) @Debounce(500)
renderChart(newData) { renderChart = (nextProps) => {
const data = newData || this.props.data; // const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
if (!data || data.length < 1) { const { data, height } = nextProps || this.props;
return;
}
const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C']; if (data.length < 1 || !this.root) {
return;
const height = this.props.height * 4;
let width = 0;
if (this.root) {
width = this.root.offsetWidth * 4;
} }
data.sort((a, b) => b.value - a.value); const h = height * 4;
const w = this.root.offsetWidth * 4;
const max = data[0].value;
const min = data[data.length - 1].value; const imageMask = new Image();
imageMask.crossOrigin = '';
// 构造一个词云布局对象 imageMask.src = imgUrl;
const layout = new Cloud({
words: data, imageMask.onload = () => {
width, const dv = new DataSet.View().source(data);
height, const range = dv.range('value');
const [min, max] = range;
rotate: () => 0, dv.transform({
type: 'tag-cloud',
// 设定文字大小配置函数(默认为12-24px的随机大小) fields: ['name', 'value'],
size: words => (((words.value - min) / (max - min)) * 50) + 30, imageMask,
font: 'Verdana',
// 设定文字内容 size: [w, h], // 宽高设置最好根据 imageMask 做调整
text: words => words.name, padding: 5,
}); timeInterval: 5000, // max execute time
rotate() {
layout.image(imgUrl, (imageCloud) => { return 0;
// clean },
if (this.node) { fontSize(d) {
this.node.innerHTML = ''; // eslint-disable-next-line
} return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
},
// 执行词云布局函数,并在回调函数中调用G2对结果进行绘制 });
imageCloud.exec((texts) => {
const chart = new G2.Chart({
container: this.node,
width,
height,
plotCfg: {
margin: 0,
},
});
chart.legend(false);
chart.axis(false);
chart.tooltip(false);
chart.source(texts);
// 将词云坐标系调整为G2的坐标系
chart.coord().reflect();
chart
.point()
.position('x*y')
.color('text', colors)
.size('size', size => size)
.shape('cloud')
.style({
fontStyle: texts[0].style,
fontFamily: texts[0].font,
fontWeight: texts[0].weight,
});
chart.render(); this.setState({
dv,
w,
h,
}); });
}); };
} };
render() { render() {
const { className, height } = this.props;
const { dv, w, h } = this.state;
return ( return (
<div <div
className={classNames(styles.tagCloud, this.props.className)} className={classNames(styles.tagCloud, className)}
style={{ width: '100%', height }}
ref={this.saveRootRef} ref={this.saveRootRef}
style={{ width: '100%' }}
> >
<div ref={this.saveNodeRef} style={{ height: this.props.height }} /> {dv && (
<Chart width={w} height={h} data={dv} padding={0}>
<Coord reflect="y" />
<Geom type="point" position="x*y" color="text" shape="cloud" />
</Chart>
)}
</div> </div>
); );
} }
......
...@@ -6,6 +6,7 @@ export interface TimelineChartProps { ...@@ -6,6 +6,7 @@ export interface TimelineChartProps {
y2: string; y2: string;
}>; }>;
titleMap: { y1: string; y2: string }; titleMap: { y1: string; y2: string };
padding?: [number, number, number, number];
height?: number; height?: number;
} }
......
import React, { Component } from 'react'; import React from 'react';
import G2 from 'g2'; import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts';
import Slider from 'g2-plugin-slider'; import DataSet from '@antv/data-set';
import Slider from 'bizcharts-plugin-slider';
import autoHeight from '../autoHeight';
import styles from './index.less'; import styles from './index.less';
class TimelineChart extends Component { @autoHeight()
componentDidMount() { export default class TimelineChart extends React.Component {
this.renderChart(this.props.data); render() {
} const {
title,
componentWillReceiveProps(nextProps) { height = 400,
if (nextProps.data !== this.props.data) { padding = [60, 20, 40, 40],
this.renderChart(nextProps.data); titleMap = {
} y1: 'y1',
} y2: 'y2',
componentWillUnmount() {
if (this.chart) {
this.chart.destroy();
}
if (this.slider) {
this.slider.destroy();
}
}
sliderId = `timeline-chart-slider-${Math.random() * 1000}`
handleRef = (n) => {
this.node = n;
}
renderChart(data) {
const { height = 400, margin = [60, 20, 40, 40], titleMap, borderWidth = 2 } = this.props;
if (!data || (data && data.length < 1)) {
return;
}
// clean
if (this.sliderId) {
document.getElementById(this.sliderId).innerHTML = '';
}
this.node.innerHTML = '';
const chart = new G2.Chart({
container: this.node,
forceFit: true,
height,
plotCfg: {
margin,
}, },
}); borderWidth = 2,
data = [
chart.axis('x', { {
title: false, x: 0,
}); y1: 0,
chart.axis('y1', { y2: 0,
title: false, },
}); ],
chart.axis('y2', false); } = this.props;
chart.legend({ data.sort((a, b) => a.x - b.x);
mode: false,
position: 'top',
});
let max; let max;
if (data[0] && data[0].y1 && data[0].y2) { if (data[0] && data[0].y1 && data[0].y2) {
max = Math.max(data.sort((a, b) => b.y1 - a.y1)[0].y1, max = Math.max(
data.sort((a, b) => b.y2 - a.y2)[0].y2); [...data].sort((a, b) => b.y1 - a.y1)[0].y1,
[...data].sort((a, b) => b.y2 - a.y2)[0].y2
);
} }
chart.source(data, { const ds = new DataSet({
x: { state: {
type: 'timeCat', start: data[0].x,
tickCount: 16, end: data[data.length - 1].x,
mask: 'HH:MM',
range: [0, 1],
},
y1: {
alias: titleMap.y1,
max,
min: 0,
}, },
y2: { });
alias: titleMap.y2,
const dv = ds.createView();
dv
.source(data)
.transform({
type: 'filter',
callback: (obj) => {
const date = obj.x;
return date <= ds.state.end && date >= ds.state.start;
},
})
.transform({
type: 'map',
callback(row) {
const newRow = { ...row };
newRow[titleMap.y1] = row.y1;
newRow[titleMap.y2] = row.y2;
return newRow;
},
})
.transform({
type: 'fold',
fields: [titleMap.y1, titleMap.y2], // 展开字段集
key: 'key', // key字段
value: 'value', // value字段
});
const timeScale = {
type: 'time',
tickCount: 10,
mask: 'HH:MM',
range: [0, 1],
};
const cols = {
x: timeScale,
value: {
max, max,
min: 0, min: 0,
}, },
}); };
chart.line().position('x*y1').color('#1890FF').size(borderWidth); const SliderGen = () => (
chart.line().position('x*y2').color('#2FC25B').size(borderWidth); <Slider
padding={[0, padding[1] + 20, 0, padding[3]]}
this.chart = chart; width="auto"
height={26}
/* eslint new-cap:0 */ xAxis="x"
const slider = new Slider({ yAxis="y1"
domId: this.sliderId, scales={{ x: timeScale }}
height: 26, data={data}
xDim: 'x', start={ds.state.start}
yDim: 'y1', end={ds.state.end}
charts: [chart], backgroundChart={{ type: 'line' }}
}); onChange={({ startValue, endValue }) => {
slider.render(); ds.setState('start', startValue);
ds.setState('end', endValue);
this.slider = slider; }}
} />
);
render() {
const { height, title } = this.props;
return ( return (
<div className={styles.timelineChart} style={{ height }}> <div className={styles.timelineChart} style={{ height: height + 30 }}>
<div> <div>
{ title && <h4>{title}</h4>} {title && <h4>{title}</h4>}
<div ref={this.handleRef} /> <Chart height={height} padding={padding} data={dv} scale={cols} forceFit>
<div id={this.sliderId} /> <Axis name="x" />
<Tooltip />
<Legend name="key" position="top" />
<Geom type="line" position="x*value" size={borderWidth} color="key" />
</Chart>
<div style={{ marginRight: -20 }}>
<SliderGen />
</div>
</div> </div>
</div> </div>
); );
} }
} }
export default TimelineChart;
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import autoHeight from '../autoHeight';
import styles from './index.less'; import styles from './index.less';
/* eslint no-return-assign: 0 */ /* eslint no-return-assign: 0 */
/* eslint no-mixed-operators: 0 */
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90 // riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
class WaterWave extends PureComponent { @autoHeight()
static defaultProps = { export default class WaterWave extends PureComponent {
height: 160,
}
state = { state = {
radio: 1, radio: 1,
} };
componentDidMount() { componentDidMount() {
this.renderChart(); this.renderChart();
...@@ -33,7 +33,7 @@ class WaterWave extends PureComponent { ...@@ -33,7 +33,7 @@ class WaterWave extends PureComponent {
this.setState({ this.setState({
radio: offsetWidth < height ? offsetWidth / height : 1, radio: offsetWidth < height ? offsetWidth / height : 1,
}); });
} };
renderChart() { renderChart() {
const { percent, color = '#1890FF' } = this.props; const { percent, color = '#1890FF' } = this.props;
...@@ -51,12 +51,12 @@ class WaterWave extends PureComponent { ...@@ -51,12 +51,12 @@ class WaterWave extends PureComponent {
const canvasHeight = canvas.height; const canvasHeight = canvas.height;
const radius = canvasWidth / 2; const radius = canvasWidth / 2;
const lineWidth = 2; const lineWidth = 2;
const cR = radius - (lineWidth); const cR = radius - lineWidth;
ctx.beginPath(); ctx.beginPath();
ctx.lineWidth = lineWidth * 2; ctx.lineWidth = lineWidth * 2;
const axisLength = canvasWidth - (lineWidth); const axisLength = canvasWidth - lineWidth;
const unit = axisLength / 8; const unit = axisLength / 8;
const range = 0.2; // 振幅 const range = 0.2; // 振幅
let currRange = range; let currRange = range;
...@@ -66,15 +66,12 @@ class WaterWave extends PureComponent { ...@@ -66,15 +66,12 @@ class WaterWave extends PureComponent {
const waveupsp = 0.005; // 水波上涨速度 const waveupsp = 0.005; // 水波上涨速度
let arcStack = []; let arcStack = [];
const bR = radius - (lineWidth); const bR = radius - lineWidth;
const circleOffset = -(Math.PI / 2); const circleOffset = -(Math.PI / 2);
let circleLock = true; let circleLock = true;
for (let i = circleOffset; i < circleOffset + (2 * Math.PI); i += 1 / (8 * Math.PI)) { for (let i = circleOffset; i < circleOffset + 2 * Math.PI; i += 1 / (8 * Math.PI)) {
arcStack.push([ arcStack.push([radius + bR * Math.cos(i), radius + bR * Math.sin(i)]);
radius + (bR * Math.cos(i)),
radius + (bR * Math.sin(i)),
]);
} }
const cStartPoint = arcStack.shift(); const cStartPoint = arcStack.shift();
...@@ -87,10 +84,10 @@ class WaterWave extends PureComponent { ...@@ -87,10 +84,10 @@ class WaterWave extends PureComponent {
const sinStack = []; const sinStack = [];
for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) { for (let i = xOffset; i <= xOffset + axisLength; i += 20 / axisLength) {
const x = sp + ((xOffset + i) / unit); const x = sp + (xOffset + i) / unit;
const y = Math.sin(x) * currRange; const y = Math.sin(x) * currRange;
const dx = i; const dx = i;
const dy = ((2 * cR * (1 - currData)) + (radius - cR)) - (unit * y); const dy = 2 * cR * (1 - currData) + (radius - cR) - unit * y;
ctx.lineTo(dx, dy); ctx.lineTo(dx, dy);
sinStack.push([dx, dy]); sinStack.push([dx, dy]);
...@@ -130,7 +127,7 @@ class WaterWave extends PureComponent { ...@@ -130,7 +127,7 @@ class WaterWave extends PureComponent {
ctx.beginPath(); ctx.beginPath();
ctx.save(); ctx.save();
ctx.arc(radius, radius, radius - (3 * lineWidth), 0, 2 * Math.PI, 1); ctx.arc(radius, radius, radius - 3 * lineWidth, 0, 2 * Math.PI, 1);
ctx.restore(); ctx.restore();
ctx.clip(); ctx.clip();
...@@ -157,10 +154,10 @@ class WaterWave extends PureComponent { ...@@ -157,10 +154,10 @@ class WaterWave extends PureComponent {
currRange -= t; currRange -= t;
} }
} }
if ((data - currData) > 0) { if (data - currData > 0) {
currData += waveupsp; currData += waveupsp;
} }
if ((data - currData) < 0) { if (data - currData < 0) {
currData -= waveupsp; currData -= waveupsp;
} }
...@@ -177,7 +174,11 @@ class WaterWave extends PureComponent { ...@@ -177,7 +174,11 @@ class WaterWave extends PureComponent {
const { radio } = this.state; const { radio } = this.state;
const { percent, title, height } = this.props; const { percent, title, height } = this.props;
return ( return (
<div className={styles.waterWave} ref={n => (this.root = n)} style={{ transform: `scale(${radio})` }}> <div
className={styles.waterWave}
ref={n => (this.root = n)}
style={{ transform: `scale(${radio})` }}
>
<div style={{ width: height, height, overflow: 'hidden' }}> <div style={{ width: height, height, overflow: 'hidden' }}>
<canvas <canvas
className={styles.waterWaveCanvasWrapper} className={styles.waterWaveCanvasWrapper}
...@@ -187,14 +188,10 @@ class WaterWave extends PureComponent { ...@@ -187,14 +188,10 @@ class WaterWave extends PureComponent {
/> />
</div> </div>
<div className={styles.text} style={{ width: height }}> <div className={styles.text} style={{ width: height }}>
{ {title && <span>{title}</span>}
title && <span>{title}</span>
}
<h4>{percent}%</h4> <h4>{percent}%</h4>
</div> </div>
</div> </div>
); );
} }
} }
export default WaterWave;
/* eslint eqeqeq: 0 */
import React from 'react';
function computeHeight(node) {
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) {
if (!n) {
return 0;
}
let node = n;
let height = computeHeight(node);
while (!height) {
node = node.parentNode;
if (node) {
height = computeHeight(node);
} else {
break;
}
}
return height;
}
const autoHeight = () => (WrappedComponent) => {
return class extends React.Component {
state = {
computedHeight: 0,
};
componentDidMount() {
const { height } = this.props;
if (!height) {
const h = getAutoHeight(this.root);
// eslint-disable-next-line
this.setState({ computedHeight: h });
}
}
handleRoot = (node) => {
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>
);
}
};
};
export default autoHeight;
/* eslint eqeqeq: 0 */
function equal(old, target) {
let r = true;
for (const prop in old) {
if (typeof old[prop] === 'function' && typeof target[prop] === 'function') {
if (old[prop].toString() != target[prop].toString()) {
r = false;
}
} else if (old[prop] != target[prop]) {
r = false;
}
}
return r;
}
export default equal;
// 全局 G2 设置 // 全局 G2 设置
import G2 from 'g2'; import { track } from 'bizcharts';
G2.track(false); track(false);
const colors = [
'#8543E0', '#F04864', '#FACC14', '#1890FF', '#13C2C2', '#2FC25B', '#fa8c16', '#a0d911',
];
const config = {
...G2.Theme,
defaultColor: '#1089ff',
colors: {
default: colors,
intervalStack: colors,
},
tooltip: {
background: {
radius: 4,
fill: '#000',
fillOpacity: 0.75,
},
},
};
G2.Global.setTheme(config);
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
...@@ -7,8 +8,9 @@ ...@@ -7,8 +8,9 @@
<title>Ant Design Pro</title> <title>Ant Design Pro</title>
<link rel="icon" href="/favicon.png" type="image/x-icon"> <link rel="icon" href="/favicon.png" type="image/x-icon">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="https://gw.alipayobjects.com/as/g/??datavis/g2/2.3.12/index.js,datavis/g-cloud/1.0.2/index.js,datavis/g2-plugin-slider/1.2.1/slider.js"></script>
</body> </body>
</html>
</html>
\ No newline at end of file
html, body, :global(#root) { html,
body,
:global(#root) {
height: 100%; height: 100%;
} }
canvas {
display: block;
}
body { body {
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
......
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'dva'; import { connect } from 'dva';
import { Row, Col, Icon, Card, Tabs, Table, Radio, DatePicker, Tooltip, Menu, Dropdown } from 'antd'; import {
Row,
Col,
Icon,
Card,
Tabs,
Table,
Radio,
DatePicker,
Tooltip,
Menu,
Dropdown,
} from 'antd';
import numeral from 'numeral'; import numeral from 'numeral';
import { import {
ChartCard, yuan, MiniArea, MiniBar, MiniProgress, Field, Bar, Pie, TimelineChart, ChartCard,
yuan,
MiniArea,
MiniBar,
MiniProgress,
Field,
Bar,
Pie,
TimelineChart,
} from '../../components/Charts'; } from '../../components/Charts';
import Trend from '../../components/Trend'; import Trend from '../../components/Trend';
import NumberInfo from '../../components/NumberInfo'; import NumberInfo from '../../components/NumberInfo';
...@@ -30,7 +50,7 @@ export default class Analysis extends Component { ...@@ -30,7 +50,7 @@ export default class Analysis extends Component {
salesType: 'all', salesType: 'all',
currentTabKey: '', currentTabKey: '',
rangePickerValue: getTimeDistance('year'), rangePickerValue: getTimeDistance('year'),
} };
componentDidMount() { componentDidMount() {
this.props.dispatch({ this.props.dispatch({
...@@ -49,13 +69,13 @@ export default class Analysis extends Component { ...@@ -49,13 +69,13 @@ export default class Analysis extends Component {
this.setState({ this.setState({
salesType: e.target.value, salesType: e.target.value,
}); });
} };
handleTabChange = (key) => { handleTabChange = (key) => {
this.setState({ this.setState({
currentTabKey: key, currentTabKey: key,
}); });
} };
handleRangePickerChange = (rangePickerValue) => { handleRangePickerChange = (rangePickerValue) => {
this.setState({ this.setState({
...@@ -65,7 +85,7 @@ export default class Analysis extends Component { ...@@ -65,7 +85,7 @@ export default class Analysis extends Component {
this.props.dispatch({ this.props.dispatch({
type: 'chart/fetchSalesData', type: 'chart/fetchSalesData',
}); });
} };
selectDate = (type) => { selectDate = (type) => {
this.setState({ this.setState({
...@@ -75,7 +95,7 @@ export default class Analysis extends Component { ...@@ -75,7 +95,7 @@ export default class Analysis extends Component {
this.props.dispatch({ this.props.dispatch({
type: 'chart/fetchSalesData', type: 'chart/fetchSalesData',
}); });
} };
isActive(type) { isActive(type) {
const { rangePickerValue } = this.state; const { rangePickerValue } = this.state;
...@@ -83,7 +103,10 @@ export default class Analysis extends Component { ...@@ -83,7 +103,10 @@ export default class Analysis extends Component {
if (!rangePickerValue[0] || !rangePickerValue[1]) { if (!rangePickerValue[0] || !rangePickerValue[1]) {
return; return;
} }
if (rangePickerValue[0].isSame(value[0], 'day') && rangePickerValue[1].isSame(value[1], 'day')) { if (
rangePickerValue[0].isSame(value[0], 'day') &&
rangePickerValue[1].isSame(value[1], 'day')
) {
return styles.currentDate; return styles.currentDate;
} }
} }
...@@ -104,10 +127,10 @@ export default class Analysis extends Component { ...@@ -104,10 +127,10 @@ export default class Analysis extends Component {
loading, loading,
} = chart; } = chart;
const salesPieData = salesType === 'all' ? const salesPieData =
salesTypeData salesType === 'all'
: ? salesTypeData
(salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline); : salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
const menu = ( const menu = (
<Menu> <Menu>
...@@ -191,13 +214,13 @@ export default class Analysis extends Component { ...@@ -191,13 +214,13 @@ export default class Analysis extends Component {
subTitle="转化率" subTitle="转化率"
gap={2} gap={2}
total={`${data.cvr * 100}%`} total={`${data.cvr * 100}%`}
theme={(currentKey !== data.name) && 'light'} theme={currentKey !== data.name && 'light'}
/> />
</Col> </Col>
<Col span={12} style={{ paddingTop: 36 }}> <Col span={12} style={{ paddingTop: 36 }}>
<Pie <Pie
animate={false} animate={false}
color={(currentKey !== data.name) && '#BDE4FF'} color={currentKey !== data.name && '#BDE4FF'}
inner={0.55} inner={0.55}
tooltip={false} tooltip={false}
margin={[0, 0, 0, 0]} margin={[0, 0, 0, 0]}
...@@ -224,7 +247,11 @@ export default class Analysis extends Component { ...@@ -224,7 +247,11 @@ export default class Analysis extends Component {
<ChartCard <ChartCard
bordered={false} bordered={false}
title="总销售额" title="总销售额"
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>} action={
<Tooltip title="指标说明">
<Icon type="info-circle-o" />
</Tooltip>
}
total={yuan(126560)} total={yuan(126560)}
footer={<Field label="日均销售额" value={`¥${numeral(12423).format('0,0')}`} />} footer={<Field label="日均销售额" value={`¥${numeral(12423).format('0,0')}`} />}
contentHeight={46} contentHeight={46}
...@@ -241,38 +268,43 @@ export default class Analysis extends Component { ...@@ -241,38 +268,43 @@ export default class Analysis extends Component {
<ChartCard <ChartCard
bordered={false} bordered={false}
title="访问量" title="访问量"
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>} action={
<Tooltip title="指标说明">
<Icon type="info-circle-o" />
</Tooltip>
}
total={numeral(8846).format('0,0')} total={numeral(8846).format('0,0')}
footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />} footer={<Field label="日访问量" value={numeral(1234).format('0,0')} />}
contentHeight={46} contentHeight={46}
> >
<MiniArea <MiniArea color="#975FE4" data={visitData} />
color="#975FE4"
height={46}
data={visitData}
/>
</ChartCard> </ChartCard>
</Col> </Col>
<Col {...topColResponsiveProps}> <Col {...topColResponsiveProps}>
<ChartCard <ChartCard
bordered={false} bordered={false}
title="支付笔数" title="支付笔数"
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>} action={
<Tooltip title="指标说明">
<Icon type="info-circle-o" />
</Tooltip>
}
total={numeral(6560).format('0,0')} total={numeral(6560).format('0,0')}
footer={<Field label="转化率" value="60%" />} footer={<Field label="转化率" value="60%" />}
contentHeight={46} contentHeight={46}
> >
<MiniBar <MiniBar data={visitData} />
height={46}
data={visitData}
/>
</ChartCard> </ChartCard>
</Col> </Col>
<Col {...topColResponsiveProps}> <Col {...topColResponsiveProps}>
<ChartCard <ChartCard
bordered={false} bordered={false}
title="运营活动效果" title="运营活动效果"
action={<Tooltip title="指标说明"><Icon type="info-circle-o" /></Tooltip>} action={
<Tooltip title="指标说明">
<Icon type="info-circle-o" />
</Tooltip>
}
total="78%" total="78%"
footer={ footer={
<div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}> <div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
...@@ -291,37 +323,27 @@ export default class Analysis extends Component { ...@@ -291,37 +323,27 @@ export default class Analysis extends Component {
</Col> </Col>
</Row> </Row>
<Card <Card loading={loading} bordered={false} bodyStyle={{ padding: 0 }}>
loading={loading}
bordered={false}
bodyStyle={{ padding: 0 }}
>
<div className={styles.salesCard}> <div className={styles.salesCard}>
<Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}> <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{ marginBottom: 24 }}>
<TabPane tab="销售额" key="sales"> <TabPane tab="销售额" key="sales">
<Row> <Row>
<Col xl={16} lg={12} md={12} sm={24} xs={24}> <Col xl={16} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesBar}> <div className={styles.salesBar}>
<Bar <Bar height={270} title="销售额趋势" data={salesData} />
height={295}
title="销售额趋势"
data={salesData}
/>
</div> </div>
</Col> </Col>
<Col xl={8} lg={12} md={12} sm={24} xs={24}> <Col xl={8} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesRank}> <div className={styles.salesRank}>
<h4 className={styles.rankingTitle}>门店销售额排名</h4> <h4 className={styles.rankingTitle}>门店销售额排名</h4>
<ul className={styles.rankingList}> <ul className={styles.rankingList}>
{ {rankingListData.map((item, i) => (
rankingListData.map((item, i) => ( <li key={item.title}>
<li key={item.title}> <span className={i < 3 ? styles.active : ''}>{i + 1}</span>
<span className={(i < 3) ? styles.active : ''}>{i + 1}</span> <span>{item.title}</span>
<span>{item.title}</span> <span>{numeral(item.total).format('0,0')}</span>
<span>{numeral(item.total).format('0,0')}</span> </li>
</li> ))}
))
}
</ul> </ul>
</div> </div>
</Col> </Col>
...@@ -331,26 +353,20 @@ export default class Analysis extends Component { ...@@ -331,26 +353,20 @@ export default class Analysis extends Component {
<Row> <Row>
<Col xl={16} lg={12} md={12} sm={24} xs={24}> <Col xl={16} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesBar}> <div className={styles.salesBar}>
<Bar <Bar height={292} title="访问量趋势" data={salesData} />
height={292}
title="访问量趋势"
data={salesData}
/>
</div> </div>
</Col> </Col>
<Col xl={8} lg={12} md={12} sm={24} xs={24}> <Col xl={8} lg={12} md={12} sm={24} xs={24}>
<div className={styles.salesRank}> <div className={styles.salesRank}>
<h4 className={styles.rankingTitle}>门店访问量排名</h4> <h4 className={styles.rankingTitle}>门店访问量排名</h4>
<ul className={styles.rankingList}> <ul className={styles.rankingList}>
{ {rankingListData.map((item, i) => (
rankingListData.map((item, i) => ( <li key={item.title}>
<li key={item.title}> <span className={i < 3 && styles.active}>{i + 1}</span>
<span className={(i < 3) && styles.active}>{i + 1}</span> <span>{item.title}</span>
<span>{item.title}</span> <span>{numeral(item.total).format('0,0')}</span>
<span>{numeral(item.total).format('0,0')}</span> </li>
</li> ))}
))
}
</ul> </ul>
</div> </div>
</Col> </Col>
...@@ -385,11 +401,7 @@ export default class Analysis extends Component { ...@@ -385,11 +401,7 @@ export default class Analysis extends Component {
status="up" status="up"
subTotal={17.1} subTotal={17.1}
/> />
<MiniArea <MiniArea line height={45} data={visitData2} />
line
height={45}
data={visitData2}
/>
</Col> </Col>
<Col sm={12} xs={24} style={{ marginBottom: 24 }}> <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
<NumberInfo <NumberInfo
...@@ -399,11 +411,7 @@ export default class Analysis extends Component { ...@@ -399,11 +411,7 @@ export default class Analysis extends Component {
subTotal={26.2} subTotal={26.2}
gap={8} gap={8}
/> />
<MiniArea <MiniArea line height={45} data={visitData2} />
line
height={45}
data={visitData2}
/>
</Col> </Col>
</Row> </Row>
<Table <Table
...@@ -425,7 +433,7 @@ export default class Analysis extends Component { ...@@ -425,7 +433,7 @@ export default class Analysis extends Component {
bordered={false} bordered={false}
title="销售额类别占比" title="销售额类别占比"
bodyStyle={{ padding: 24 }} bodyStyle={{ padding: 24 }}
extra={( extra={
<div className={styles.salesCardExtra}> <div className={styles.salesCardExtra}>
{iconGroup} {iconGroup}
<div className={styles.salesTypeRadio}> <div className={styles.salesTypeRadio}>
...@@ -436,7 +444,7 @@ export default class Analysis extends Component { ...@@ -436,7 +444,7 @@ export default class Analysis extends Component {
</Radio.Group> </Radio.Group>
</div> </div>
</div> </div>
)} }
style={{ marginTop: 24, minHeight: 509 }} style={{ marginTop: 24, minHeight: 509 }}
> >
<h4 style={{ marginTop: 8, marginBottom: 32 }}>销售额</h4> <h4 style={{ marginTop: 8, marginBottom: 32 }}>销售额</h4>
...@@ -460,25 +468,18 @@ export default class Analysis extends Component { ...@@ -460,25 +468,18 @@ export default class Analysis extends Component {
bodyStyle={{ padding: '0 0 32px 0' }} bodyStyle={{ padding: '0 0 32px 0' }}
style={{ marginTop: 32 }} style={{ marginTop: 32 }}
> >
<Tabs <Tabs activeKey={activeKey} onChange={this.handleTabChange}>
activeKey={activeKey} {offlineData.map(shop => (
onChange={this.handleTabChange} <TabPane tab={<CustomTab data={shop} currentTabKey={activeKey} />} key={shop.name}>
> <div style={{ padding: '0 24px' }}>
{ <TimelineChart
offlineData.map(shop => ( height={400}
<TabPane data={offlineChartData}
tab={<CustomTab data={shop} currentTabKey={activeKey} />} titleMap={{ y1: '客流量', y2: '支付笔数' }}
key={shop.name} />
> </div>
<div style={{ padding: '0 24px' }}> </TabPane>
<TimelineChart ))}
data={offlineChartData}
titleMap={{ y1: '客流量', y2: '支付笔数' }}
/>
</div>
</TabPane>)
)
}
</Tabs> </Tabs>
</Card> </Card>
</div> </div>
......
...@@ -40,16 +40,10 @@ export default class Monitor extends PureComponent { ...@@ -40,16 +40,10 @@ export default class Monitor extends PureComponent {
/> />
</Col> </Col>
<Col md={6} sm={12} xs={24}> <Col md={6} sm={12} xs={24}>
<NumberInfo <NumberInfo subTitle="销售目标完成率" total="92%" />
subTitle="销售目标完成率"
total="92%"
/>
</Col> </Col>
<Col md={6} sm={12} xs={24}> <Col md={6} sm={12} xs={24}>
<NumberInfo <NumberInfo subTitle="活动剩余时间" total={<CountDown target={targetTime} />} />
subTitle="活动剩余时间"
total={<CountDown target={targetTime} />}
/>
</Col> </Col>
<Col md={6} sm={12} xs={24}> <Col md={6} sm={12} xs={24}>
<NumberInfo <NumberInfo
...@@ -61,7 +55,10 @@ export default class Monitor extends PureComponent { ...@@ -61,7 +55,10 @@ export default class Monitor extends PureComponent {
</Row> </Row>
<div className={styles.mapChart}> <div className={styles.mapChart}>
<Tooltip title="等待后期实现"> <Tooltip title="等待后期实现">
<img src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png" alt="map" /> <img
src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png"
alt="map"
/>
</Tooltip> </Tooltip>
</div> </div>
</Card> </Card>
...@@ -143,20 +140,17 @@ export default class Monitor extends PureComponent { ...@@ -143,20 +140,17 @@ export default class Monitor extends PureComponent {
</Card> </Card>
</Col> </Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}> <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card title="热门搜索" bordered={false} bodyStyle={{ overflow: 'hidden' }}> <Card title="热门搜索" bordered={false}>
<TagCloud <TagCloud data={tags} height={161} />
data={tags}
height={161}
/>
</Card> </Card>
</Col> </Col>
<Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}> <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card title="资源剩余" bodyStyle={{ textAlign: 'center', fontSize: 0 }} bordered={false}> <Card
<WaterWave title="资源剩余"
height={161} bodyStyle={{ textAlign: 'center', fontSize: 0 }}
title="补贴资金剩余" bordered={false}
percent={34} >
/> <WaterWave height={161} title="补贴资金剩余" percent={34} />
</Card> </Card>
</Col> </Col>
</Row> </Row>
......
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