UNPKG

dce-charts

Version:
576 lines (556 loc) 18.9 kB
import React, { useReducer } from 'react'; import { Doughnut, Line, Bar } from 'react-chartjs-2'; import { Chart, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, PointElement, LineElement, Title, BarElement } from 'chart.js'; import clone from 'fast-clone'; import Papa from 'papaparse'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDownload } from '@fortawesome/free-solid-svg-icons'; /** * Get a css color based on a color object and opacity * @author Jackson Parsells * @param color the color object * @param opacity opacity of the color * @returns the css color as a string */ const getRGBA = (color, opacity) => { return `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity})`; }; /** * Blue preset color * @author Gabe Abrams */ const BlueColor = { r: 54, g: 162, b: 235, }; /** * Green preset color * @author Gabe Abrams */ const GreenColor = { r: 75, g: 192, b: 192, }; /** * Orange preset color * @author Gabe Abrams */ const OrangeColor = { r: 255, g: 159, b: 64, }; /** * Purple preset color * @author Gabe Abrams */ const PurpleColor = { r: 153, g: 102, b: 255, }; /** * Red preset color * @author Gabe Abrams */ const RedColor = { r: 255, g: 99, b: 132, }; /** * Yellow preset color * @author Gabe Abrams */ const YellowColor = { r: 255, g: 206, b: 86, }; // Import colors /** * Preset available colors * @author Gabe Abrams */ const presetColors = { red: RedColor, blue: BlueColor, yellow: YellowColor, green: GreenColor, purple: PurpleColor, orange: OrangeColor, }; /** * Returns true if the two colors are the same * @author Gabe Abrams * @param a first color * @param b second color * @returns true if colors are the same */ const colorsAreSame = (a, b) => { return (a.r === b.r && a.g === b.g && a.b === b.b); }; // Import libs // List of all colors in the order they will be distributed const allColors = [ presetColors.red, presetColors.blue, presetColors.yellow, presetColors.green, presetColors.orange, presetColors.purple, ]; /** * Add colors to elements that don't have colors * @author Gabe Abrams * @param items list of items that may or may not have a color prop * @returns items with a color prop defined on every item */ const addColors = (items) => { var _a; // If one or zero items, this is simple: just give it a color if (items.length === 0) { return []; } if (items.length === 1) { return [Object.assign(Object.assign({}, items), { color: ((_a = items[0].color) !== null && _a !== void 0 ? _a : allColors[0]) })]; } // first clone items const itemsClone = clone(items); // Index of the next color let nextColorIndex = 0; itemsClone.forEach((item, index) => { // If user includes a color, just keep it if (item.color) { return; } // Get the index of the previous and next colors const prevItemColor = itemsClone[(index === 0 ? itemsClone.length - 1 : index - 1)]; const nextItemColor = itemsClone[(index === itemsClone.length - 1 ? 0 : index + 1)]; // Loop until we get a unique color let color = allColors[nextColorIndex]; for (let i = 0; i < 3; i++) { // Get the next candidate color const candidateColor = allColors[(nextColorIndex + i) % allColors.length]; // Check if this candidate color matches the prev/next color const candidateColorIsSame = (colorsAreSame(candidateColor, prevItemColor) || colorsAreSame(candidateColor, nextItemColor)); // If this is a unique color, keep it! if (!candidateColorIsSame) { // Save the color color = candidateColor; // Increment next color index nextColorIndex += (i + 1); break; } } // Save the color to the item itemsClone[index].color = color; }); // return the cloned list return itemsClone; }; /** * Component that's a clickable button to download the contents of a chart as * a CSV file * @author Jackson Parsells * @author Gabe Abrams */ /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const DownloadButton = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ // destructure props const { title, chartData, filename, } = props; /*------------------------------------------------------------------------*/ /* Render */ /*------------------------------------------------------------------------*/ /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ // Generate CSV text const csvText = Papa.unparse(chartData); // Download button return (React.createElement("a", { href: `data:text/csv;charset=utf-8,${encodeURI(csvText)}`, download: filename, className: "btn btn-sm btn-primary", style: { borderTopLeftRadius: 0, borderBottomRightRadius: 0, }, title: "Download Data as CSV", "aria-label": `download the data of ${title} as a csv file` }, React.createElement(FontAwesomeIcon, { icon: faDownload }))); }; /** * Download box container that wraps a chart * @author Gabe Abrams * @author Jackson Parsells */ /*------------------------------------------------------------------------*/ /* Style */ /*------------------------------------------------------------------------*/ const style = ` .DownloadBox-download { position: absolute; top: 0; right: 15px; cursor: pointer; } .DownloadBox-no-outline { border: 1px solid transparent; } .DownloadBox-outlined { border: 1px solid #007bff; } `; /* ------------- Actions ------------ */ // Types of actions var ActionType; (function (ActionType) { // Start hovering ActionType["StartHovering"] = "start-hovering"; // Stop hovering ActionType["StopHovering"] = "stop-hovering"; })(ActionType || (ActionType = {})); /** * Reducer that executes actions * @author Gabe Abrams * @param state current state * @param action action to execute */ const reducer = (state, action) => { switch (action.type) { case ActionType.StartHovering: { return Object.assign(Object.assign({}, state), { hovering: true }); } case ActionType.StopHovering: { return Object.assign(Object.assign({}, state), { hovering: false }); } default: { return state; } } }; /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const DownloadBox = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ // Destructure all props const { title, chartData, children, } = props; /* -------------- State ------------- */ // Initial state const initialState = { hovering: false, }; // Initialize state const [state, dispatch] = useReducer(reducer, initialState); // Destructure common state const { hovering, } = state; /*------------------------------------------------------------------------*/ /* Render */ /*------------------------------------------------------------------------*/ /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ return (React.createElement("div", { // use similar logic to below to add for focus and hovering className: hovering ? 'DownloadBox-outlined' : 'DownloadBox-no-outline', onMouseEnter: () => { dispatch({ type: ActionType.StartHovering }); }, onFocus: () => { dispatch({ type: ActionType.StartHovering }); }, onMouseLeave: () => { dispatch({ type: ActionType.StopHovering }); }, onBlur: () => { dispatch({ type: ActionType.StopHovering }); } }, React.createElement("style", null, style), children, React.createElement("div", { className: `DownloadBox-download ${hovering ? '' : 'sr-only'}` }, React.createElement(DownloadButton, { title: title, chartData: chartData, filename: `${title}.csv` })))); }; /** * DoughnutChart, wrapper around the ChartJS Doughnut chart for DCE widgets * @author Jackson Parsells */ // chart JS registry Chart.register(ArcElement, Tooltip, Legend); /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const DoughnutChart = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ const { title, showTitle = true, segments, showLegend = true, } = props; // Add colors to props const segmentsWithColors = addColors(segments); // Create label list const labels = segmentsWithColors.map((segment) => { return segment.label; }); // Create dataset const dataset = { label: '', data: segmentsWithColors.map((segment) => { return segment.value; }), backgroundColor: segmentsWithColors.map((segment) => { return getRGBA(segment.color, 0.2); }), borderColor: segmentsWithColors.map((segment) => { return getRGBA(segment.color, 1); }), borderWidth: 1, }; // data for CSV const dataForCSV = segments.map((segment) => { return { Name: segment.label, Value: segment.value, }; }); // chart data const chartData = { labels, datasets: [dataset], }; // chart options const options = { responsive: true, plugins: { legend: { display: showLegend, position: 'top', }, title: { display: showTitle, text: title, }, }, }; /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ return (React.createElement(DownloadBox, { chartData: dataForCSV, title: title }, React.createElement(Doughnut, { data: chartData, options: options }))); }; /** * LineChart, wrapper around the ChartJS Line chart for DCE widgets * @author Jackson Parsells */ // chart JS registry Chart.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const LineChart = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ const { title, showTitle = true, points, } = props; // Add colors to props const pointsWithColors = addColors(points); // Create label list const labels = pointsWithColors.map((point) => { return point.label; }); // Create dataset const dataset = { label: '', data: pointsWithColors.map((point) => { return point.value; }), backgroundColor: pointsWithColors.map((point) => { return getRGBA(point.color, 0.2); }), borderColor: pointsWithColors.map((point) => { return getRGBA(point.color, 1); }), borderWidth: 1, }; // data for CSV const dataForCSV = points.map((point) => { return { Name: point.label, Value: point.value, }; }); // chart data const chartData = { labels, datasets: [dataset], }; // chart options const options = { responsive: true, plugins: { legend: { display: false, position: 'top', }, title: { text: title, display: showTitle, }, }, }; /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ return (React.createElement(DownloadBox, { chartData: dataForCSV, title: title }, React.createElement(Line, { data: chartData, options: options }))); }; /** * HorizontalBarChart, wrapper around the ChartJS Horizontal Bar chart for DCE * widgets * @author Jackson Parsells */ // chart JS registry Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const HorizontalBarChart = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ // destructure props const { title, showTitle = true, bars, } = props; // add colors to props const barsWithColors = addColors(bars); // create label list const labels = barsWithColors.map((bar) => { return bar.label; }); // create dataset const dataset = { label: '', data: barsWithColors.map((bar) => { return bar.value; }), backgroundColor: barsWithColors.map((bar) => { return getRGBA(bar.color, 0.2); }), borderColor: barsWithColors.map((bar) => { return getRGBA(bar.color, 1); }), borderWidth: 1, }; // data for CSV const dataForCSV = bars.map((bar) => { return { Name: bar.label, Value: bar.value, }; }); // chart data const chartData = { labels, datasets: [dataset], }; // chart options to have the bars extend horizontally const options = { indexAxis: 'y', responsive: true, plugins: { legend: { display: false, position: 'top', }, title: { display: showTitle, text: title, }, }, }; /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ return (React.createElement(DownloadBox, { chartData: dataForCSV, title: title }, React.createElement(Bar, { data: chartData, options: options }))); }; /** * VerticalBarChart, wrapper around the ChartJS Vertical Bar chart for DCE * widgets * @author Jackson Parsells */ // chart JS registry Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); /*------------------------------------------------------------------------*/ /* Component */ /*------------------------------------------------------------------------*/ const VerticalBarChart = (props) => { /*------------------------------------------------------------------------*/ /* Setup */ /*------------------------------------------------------------------------*/ /* -------------- Props ------------- */ const { title, showTitle = true, bars, } = props; // add colors to props const barsWithColors = addColors(bars); // create label list const labels = barsWithColors.map((bar) => { return bar.label; }); // create dataset const dataset = { label: '', data: barsWithColors.map((bar) => { return bar.value; }), backgroundColor: barsWithColors.map((bar) => { return getRGBA(bar.color, 0.2); }), borderColor: barsWithColors.map((bar) => { return getRGBA(bar.color, 1); }), borderWidth: 1, }; // data for CSV const dataForCSV = bars.map((bar) => { return { Name: bar.label, Value: bar.value, }; }); // chart data const chartData = { labels, datasets: [dataset], }; // chart options to make the bar vertical const options = { responsive: true, plugins: { legend: { display: false, position: 'top', }, title: { display: showTitle, text: title, }, }, }; /*----------------------------------------*/ /* Main UI */ /*----------------------------------------*/ return (React.createElement(DownloadBox, { chartData: dataForCSV, title: title }, React.createElement(Bar, { data: chartData, options: options }))); }; export { BlueColor, DoughnutChart, GreenColor, HorizontalBarChart, LineChart, OrangeColor, PurpleColor, RedColor, VerticalBarChart, YellowColor }; //# sourceMappingURL=index.js.map