Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more visualization components #338

Merged
merged 3 commits into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"bulma-switch": "^2.0.0",
"chart.js": "^2.9.3",
"chartjs-chart-box-and-violin-plot": "^2.2.0",
"chartjs-plugin-datalabels": "^0.7.0",
"classnames": "^2.2.6",
"dataframe-js": "^1.4.3",
"dotenv": "^8.2.0",
Expand Down
177 changes: 126 additions & 51 deletions src/components/Visualizations/Chart.jsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,117 @@
import React from 'react';
import PropTypes from 'proptypes';
import Chart from 'chart.js';
import ChartJS from 'chart.js';
import 'chartjs-chart-box-and-violin-plot';
import ChartJSDataLabels from 'chartjs-plugin-datalabels';
import clx from 'classnames';
import COLORS from '@styles/COLORS';
import ExportButton from './ExportButton';

// ///////// CHARTJS DEFAULTS ///////////

Object.assign(Chart.defaults.global, {
defaultFontColor: COLORS.FONTS,
defaultFontFamily: 'Roboto',
animation: false,
responsive: true,
maintainAspectRatio: false,
legend: false,
ChartJS.helpers.merge(ChartJS.defaults, {
global: {
defaultFontColor: COLORS.FONTS,
defaultFontFamily: 'Roboto',
animation: false,
title: {
display: false,
},
legend: {
display: false,
},
tooltips: {
xPadding: 10,
yPadding: 10,
titleFontFamily: 'Open Sans',
titleFontColor: COLORS.FONTS,
titleFontSize: 14,
titleFontWeight: 'bold',
bodyFontFamily: 'Roboto',
bodyFontSize: 14,
bodyFontColor: COLORS.FONTS,
footerFontFamily: 'Roboto',
footerFontSize: 14,
footerFontColor: COLORS.FONTS,
footerFontWeight: 'bold',
backgroundColor: '#C4C4C4',
cornerRadius: 4,
},
plugins: {
datalabels: {
color: COLORS.FONTS,
font: {
size: 14,
},
},
chartArea: {
backgroundColor: COLORS.BACKGROUND,
},
},
},
scale: {
scaleLabel: {
display: true,
fontFamily: 'Open Sans',
fontSize: 14,
},
},
});

Object.assign(Chart.defaults.global.title, {
display: true,
fontFamily: 'Open Sans',
fontSize: 20,
});

Object.assign(Chart.defaults.scale.scaleLabel, {
display: true,
fontFamily: 'Open Sans',
fontWeight: 'bold',
fontSize: 15,
// ///////// CHARTJS PLUGINS ///////////

// add background color to charts
// https://stackoverflow.com/questions/37144031/background-colour-of-line-charts-in-chart-js?rq=1
ChartJS.pluginService.register({
beforeDraw: chart => {
const bgColor = chart.config.options.plugins.chartArea?.backgroundColor;
if (!bgColor) return;

const { ctx } = chart.chart;
const { chartArea } = chart;
const { padding } = chart.config.options.layout;
const pad = typeof padding === 'number' ? {
top: padding,
bottom: padding,
left: padding,
right: padding,
} : padding;

ctx.save();
ctx.fillStyle = bgColor;
ctx.fillRect(
chartArea.left - pad.left,
chartArea.top - pad.top,
chartArea.right - chartArea.left + pad.left + pad.right,
chartArea.bottom - chartArea.top + pad.top + pad.bottom,
);
ctx.restore();
},
});

Object.assign(Chart.defaults.global.tooltips, {
xPadding: 10,
yPadding: 10,
bodyFontFamily: 'Roboto',
bodyFontSize: 14,
bodyFontColor: COLORS.FONTS,
backgroundColor: 'rgb(200, 200, 200)',
cornerRadius: 4,
});
// opt-in only
ChartJS.plugins.unregister(ChartJSDataLabels);

// //////////// COMPONENT //////////////

class ReactChart extends React.Component {
class Chart extends React.Component {
canvasRef = React.createRef();

componentDidMount() {
const { type, data, options } = this.props;
const {
type,
data,
options,
datalabels,
} = this.props;

const ctx = this.canvasRef.current.getContext('2d');
this.chart = new Chart(ctx, {
this.chart = new ChartJS(ctx, {
type,
data,
options,
plugins: datalabels ? [ChartJSDataLabels] : [],
});
this.setHeight();
}

componentDidUpdate(prevProps) {
Expand All @@ -60,38 +120,53 @@ class ReactChart extends React.Component {
if (prevProps.data !== data) {
this.chart.data = data;
this.chart.update();
this.setHeight();
}
}

setHeight = () => {
const { height } = this.props;

if (height) {
const numLabels = this.chart.data.labels.length;
const heightPx = height(numLabels);
this.canvasRef.current.parentNode.style.height = `${heightPx}px`;
}
}

render() {
const {
title,
exportable,
height,
className,
} = this.props;

const canvasWrapStyle = {
position: 'relative',
height: typeof height === 'undefined'
? undefined
: `${height}px`,
};

return (
<div className="is-relative">
<canvas ref={this.canvasRef} />
<div className={clx('chart', className)}>
{ title && <h1>{ title }</h1> }
{ exportable && <ExportButton /> }
<div style={canvasWrapStyle}>
<canvas ref={this.canvasRef} />
</div>
</div>
);
}
}

export default ReactChart;
export default Chart;

ReactChart.propTypes = {
Chart.propTypes = {
type: PropTypes.string.isRequired,
data: PropTypes.shape.isRequired,
options: PropTypes.shape.isRequired,
height: PropTypes.func,
data: PropTypes.shape({}).isRequired,
options: PropTypes.shape({}).isRequired,
title: PropTypes.string,
height: PropTypes.number,
datalabels: PropTypes.bool,
exportable: PropTypes.bool,
className: PropTypes.string,
};

ReactChart.defaultProps = {
Chart.defaultProps = {
title: null,
height: undefined,
datalabels: false,
exportable: true,
className: undefined,
};
95 changes: 95 additions & 0 deletions src/components/Visualizations/Contact311.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import PropTypes from 'proptypes';
import { connect } from 'react-redux';
import Chart from './Chart';

const Contact311 = () => {
// // DATA ////

const randomInt = () => {
const min = 10;
const max = 100;
return Math.round(Math.random() * (max - min) + min);
};

const dummyData = [{
label: 'Mobile App',
color: '#1D66F2',
value: randomInt(),
}, {
label: 'Call',
color: '#D8E5FF',
value: randomInt(),
}, {
label: 'Email',
color: '#708ABD',
value: randomInt(),
}, {
label: 'Driver Self Report',
color: '#C4C6C9',
value: randomInt(),
}, {
label: 'Self Service',
color: '#0C2A64',
value: randomInt(),
}, {
label: 'Other',
color: '#6A98F1',
value: randomInt(),
}];

const total = dummyData.reduce((p, c) => p + c.value, 0);

const chartData = {
labels: dummyData.map(el => el.label),
datasets: [{
data: dummyData.map(el => el.value),
backgroundColor: dummyData.map(el => el.color),
datalabels: {
labels: {
index: {
align: 'end',
anchor: 'end',
formatter: (value, ctx) => {
const { label } = dummyData[ctx.dataIndex];
const percentage = (100 * (value / total)).toFixed(1);
return `${label}\n${percentage}%`;
},
offset: 4,
},
},
},
}],
};

// // OPTIONS ////

const chartOptions = {
aspectRatio: 1.0,
animation: false,
layout: {
padding: 65,
},
};

return (
<Chart
title="How People Contact 311"
type="pie"
data={chartData}
options={chartOptions}
datalabels
className="contact-311"
/>
);
};

const mapStateToProps = state => ({
requestTypes: state.data.requestTypes,
});

export default connect(mapStateToProps)(Contact311);

Contact311.propTypes = {
requestTypes: PropTypes.shape({}).isRequired,
};
31 changes: 31 additions & 0 deletions src/components/Visualizations/ExportButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useState } from 'react';

const ExportButton = () => {
const [open, setOpen] = useState(false);

return (
<span
className="export-button"
onMouseOver={() => setOpen(true)}
onFocus={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onBlur={() => setOpen(false)}
>
Export
{ open && (
<div className="export-dropdown">
<h3>Export Image</h3>
<div>PDF</div>
<div>Email</div>
<div>Link</div>
<div>Excel</div>
<h3>Export Data</h3>
<div>CSV</div>
<div>Excel</div>
</div>
)}
</span>
);
};

export default ExportButton;
22 changes: 8 additions & 14 deletions src/components/Visualizations/Frequency.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ const Frequency = ({
// // OPTIONS ////

const chartOptions = {
aspectRatio: 0.7,
title: {
text: 'Frequency',
fontSize: 20,
},
aspectRatio: 611 / 400,
scales: {
xAxes: [{
type: 'time',
Expand Down Expand Up @@ -74,16 +70,14 @@ const Frequency = ({
},
};

if (chartData.datasets.length === 0) return null;

return (
<div className="frequency">
<Chart
type="line"
data={chartData}
options={chartOptions}
/>
</div>
<Chart
title="Frequency"
type="line"
data={chartData}
options={chartOptions}
className="frequency"
/>
);
};

Expand Down
Loading