diff --git a/src/js/components/explorer/ExplorerWrapperPage.jsx b/src/js/components/explorer/ExplorerWrapperPage.jsx
index 1050d2271e..194f3131d2 100644
--- a/src/js/components/explorer/ExplorerWrapperPage.jsx
+++ b/src/js/components/explorer/ExplorerWrapperPage.jsx
@@ -18,16 +18,15 @@ const propTypes = {
showShareIcon: PropTypes.bool
};
-const defaultProps = {
- showShareIcon: false
-};
-
require('pages/explorer/explorerPage.scss');
const slug = 'explorer';
const emailSubject = 'USAspending.gov Federal Spending Explorer';
-const ExplorerWrapperPage = (props) => {
+const ExplorerWrapperPage = ({
+ showShareIcon = false,
+ children
+}) => {
const dispatch = useDispatch();
const handleShareDispatch = (url) => {
dispatch(showModal(url));
@@ -45,7 +44,7 @@ const ExplorerWrapperPage = (props) => {
classNames="usa-da-explorer-page"
title="Spending Explorer"
metaTagProps={explorerPageMetaTags}
- toolBarComponents={props.showShareIcon ? [
+ toolBarComponents={showShareIcon ? [
@@ -53,13 +52,12 @@ const ExplorerWrapperPage = (props) => {
- {props.children}
+ {children}
);
};
ExplorerWrapperPage.propTypes = propTypes;
-ExplorerWrapperPage.defaultProps = defaultProps;
export default ExplorerWrapperPage;
diff --git a/src/js/components/explorer/detail/ExplorerDetailPage.jsx b/src/js/components/explorer/detail/ExplorerDetailPage.jsx
index 5925122798..f16f02cb50 100644
--- a/src/js/components/explorer/detail/ExplorerDetailPage.jsx
+++ b/src/js/components/explorer/detail/ExplorerDetailPage.jsx
@@ -3,7 +3,7 @@
* Created by Kevin Li 8/16/17
*/
-import React, { useState } from 'react';
+import React, { useCallback, useState } from 'react';
import DetailContentContainer from 'containers/explorer/detail/DetailContentContainer';
import ExplorerWrapperPage from '../ExplorerWrapperPage';
import ExplorerTooltip from './visualization/ExplorerTooltip';
@@ -22,14 +22,14 @@ const ExplorerDetailPage = () => {
isAward: false
});
- const showTooltipFn = (position, data) => {
+ const showTooltipFn = useCallback((position, data) => {
setShowTooltip(true);
setTooltip(Object.assign({}, position, data));
- };
+ }, []);
- const hideTooltipFn = () => {
+ const hideTooltipFn = useCallback(() => {
setShowTooltip(false);
- };
+ }, []);
let tooltipUi = null;
diff --git a/src/js/components/explorer/detail/visualization/treemap/ExplorerTreemap.jsx b/src/js/components/explorer/detail/visualization/treemap/ExplorerTreemap.jsx
index 143d6e4ab5..a99fe6a943 100644
--- a/src/js/components/explorer/detail/visualization/treemap/ExplorerTreemap.jsx
+++ b/src/js/components/explorer/detail/visualization/treemap/ExplorerTreemap.jsx
@@ -3,7 +3,7 @@
* Created by Kevin Li 8/17/17
*/
-import React from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { hierarchy, treemap, treemapBinary } from 'd3-hierarchy';
@@ -14,60 +14,130 @@ import { measureTreemapHeader, measureTreemapValue } from 'helpers/textMeasureme
import LoadingSpinner from 'components/sharedComponents/LoadingSpinner';
import TreemapCell from 'components/sharedComponents/TreemapCell';
-import { isEqual } from 'lodash-es';
const propTypes = {
isLoading: PropTypes.bool,
width: PropTypes.number,
height: PropTypes.number,
+ total: PropTypes.number,
data: PropTypes.object,
goDeeper: PropTypes.func,
showTooltip: PropTypes.func,
hideTooltip: PropTypes.func,
- goToUnreported: PropTypes.func,
- activeSubdivision: PropTypes.object
+ goToUnreported: PropTypes.func
+ // activeSubdivision: PropTypes.object
};
-const defaultProps = {
- height: 530
-};
+export const ExplorerTreemap = ({
+ isLoading,
+ width,
+ height = 530,
+ total,
+ data,
+ goDeeper,
+ showTooltip,
+ hideTooltip,
+ goToUnreported
+}) => {
+ const [virtualChart, setVirtualChart] = useState([]);
+
+ const truncateText = (text, type, maxWidth) => {
+ // calculate the text width of the full label
+ let label = text;
+ let labelWidth = 0;
+ if (type === 'title') {
+ labelWidth = measureTreemapHeader(text);
+ }
+ else if (type === 'subtitle') {
+ labelWidth = measureTreemapValue(text);
+ }
-export default class ExplorerTreemap extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- chartReady: false,
- virtualChart: []
- };
+ // check to see if the full label will fit
+ if (labelWidth > maxWidth) {
+ // label won't fit, let's cut it down
+ // determine the average character pixel width
+ const characterWidth = Math.ceil(labelWidth / text.length);
+ // give an additional 30px for the ellipsis
+ const availableWidth = maxWidth - 30;
+ let availableLength = Math.floor(availableWidth / characterWidth);
+ if (availableLength < 1) {
+ // we must show at least one character
+ availableLength = 1;
+ }
- this.selectedCell = this.selectedCell.bind(this);
- }
+ // substring the label to this length
+ if (availableLength < text.length) {
+ label = `${label.substring(0, availableLength)}...`;
+ }
+ }
- componentDidMount() {
- this.buildVirtualChart(this.props);
- }
+ return label;
+ };
+
+ const buildVirtualCell = useCallback((item, scale, localTotal) => {
+ const localHeight = item.y1 - item.y0;
+ const localWidth = item.x1 - item.x0;
+
+ const amount = item.data.amount;
+ const percent = amount / localTotal;
+ const percentString = `${(Math.round(percent * 1000) / 10)}%`;
- componentDidUpdate(prevProps) {
- if (!isEqual(prevProps.data, this.props.data)) {
- this.buildVirtualChart(this.props);
+ // the available width is 40px less than the box width to account for 20px of padding on
+ // each side
+ const usableWidth = localWidth - 40;
+ let name = item.data.name;
+ const isUnreported = item.data.name === "Unreported Data";
+ if (isUnreported) {
+ name = "Unreported Data*";
}
- else if (prevProps.width !== this.props.width || prevProps.height !== this.props.height) {
- this.buildVirtualChart(this.props);
+ const title = truncateText(name, 'title', usableWidth);
+ const subtitle = truncateText(percentString, 'subtitle', usableWidth);
+ let color = scale(amount);
+
+ if (isUnreported) {
+ // use the gray color for unreported data, instead of the usual calculated
+ // color
+ color = 'rgb(103,103,103)';
}
- }
- buildVirtualChart(props) {
- const data = props.data.toJS();
+ const cell = {
+ width: localWidth,
+ height: localHeight,
+ x: item.x0,
+ y: item.y0,
+ data: Object.assign({}, item.data, {
+ percent,
+ percentString
+ }),
+ color,
+ title: {
+ text: title,
+ x: (localWidth / 2),
+ y: (localHeight / 2) - 5 // shift it up slightly so the full title + subtitle combo is vertically centered
+ },
+ subtitle: {
+ text: subtitle,
+ x: (localWidth / 2),
+ y: (localHeight / 2) + 15 // to place the subtitle below the title
+ }
+ };
+
+ return cell;
+ }, []);
- const total = props.total;
+ const selectedCell = useCallback((id, title) => {
+ goDeeper(id, title);
+ }, [goDeeper]);
+ const buildVirtualChart = useCallback(() => {
+ const localData = data.toJS();
// parse the inbound data into D3's treemap hierarchy structure
- const treemapData = hierarchy({ children: data })
+ const treemapData = hierarchy({ children: localData })
.sum((d) => d.amount); // tell D3 how to extract the monetary value out of the object
// set up a function for generating the treemap of the specified size and style
const tree = treemap()
- .size([props.width, props.height])
+ .size([width, height])
.tile(treemapBinary)
.paddingInner(5)
.round(true);
@@ -75,11 +145,10 @@ export default class ExplorerTreemap extends React.Component {
// generate the treemap and calculate the individual boxes
const treeItems = tree(treemapData).leaves();
- if (treeItems.length === 0 || data.length === 0) {
+ if (treeItems.length === 0 || localData.length === 0) {
// we have no data, so don't draw a chart
- this.setState({
- virtualChart: []
- });
+ setVirtualChart([]);
+
return;
}
@@ -101,145 +170,59 @@ export default class ExplorerTreemap extends React.Component {
// we can now begin creating the individual treemap cells
const cells = [];
treeItems.forEach((item) => {
- const cell = this.buildVirtualCell(item, scale, total);
+ const cell = buildVirtualCell(item, scale, total);
cells.push(cell);
});
- this.setState({
- virtualChart: cells
- });
- }
-
- buildVirtualCell(data, scale, total) {
- const height = data.y1 - data.y0;
- const width = data.x1 - data.x0;
+ setVirtualChart(cells);
+ }, [data, width, height, buildVirtualCell, total]);
- const amount = data.data.amount;
- const percent = amount / total;
- const percentString = `${(Math.round(percent * 1000) / 10)}%`;
+ useEffect(() => {
+ buildVirtualChart();
+ }, [buildVirtualChart]);
- // the available width is 40px less than the box width to account for 20px of padding on
- // each side
- const usableWidth = width - 40;
- let name = data.data.name;
- const isUnreported = data.data.name === "Unreported Data";
- if (isUnreported) {
- name = "Unreported Data*";
- }
- const title = this.truncateText(name, 'title', usableWidth);
- const subtitle = this.truncateText(percentString, 'subtitle', usableWidth);
- let color = scale(amount);
-
- if (isUnreported) {
- // use the gray color for unreported data, instead of the usual calculated
- // color
- color = 'rgb(103,103,103)';
- }
-
- const cell = {
- width,
- height,
- x: data.x0,
- y: data.y0,
- data: Object.assign({}, data.data, {
- percent,
- percentString
- }),
- color,
- title: {
- text: title,
- x: (width / 2),
- y: (height / 2) - 5 // shift it up slightly so the full title + subtitle combo is vertically centered
- },
- subtitle: {
- text: subtitle,
- x: (width / 2),
- y: (height / 2) + 15 // to place the subtitle below the title
- }
- };
-
- return cell;
- }
-
- truncateText(text, type, maxWidth) {
- // calculate the text width of the full label
- let label = text;
- let labelWidth = 0;
- if (type === 'title') {
- labelWidth = measureTreemapHeader(text);
- }
- else if (type === 'subtitle') {
- labelWidth = measureTreemapValue(text);
- }
-
- // check to see if the full label will fit
- if (labelWidth > maxWidth) {
- // label won't fit, let's cut it down
- // determine the average character pixel width
- const characterWidth = Math.ceil(labelWidth / text.length);
- // give an additional 30px for the ellipsis
- const availableWidth = maxWidth - 30;
- let availableLength = Math.floor(availableWidth / characterWidth);
- if (availableLength < 1) {
- // we must show at least one character
- availableLength = 1;
- }
-
- // substring the label to this length
- if (availableLength < text.length) {
- label = `${label.substring(0, availableLength)}...`;
- }
- }
-
- return label;
- }
-
- selectedCell(id, title) {
- this.props.goDeeper(id, title);
+ if (width <= 0) {
+ return null;
}
- render() {
- if (this.props.width <= 0) {
- return null;
- }
-
- const cells = this.state.virtualChart.map((cell) => (
-
- ));
-
- let loadingMessage = null;
- if (this.props.isLoading) {
- loadingMessage = (
-
-
-
-
Gathering your data...
-
Updating Spending Explorer.
-
This should only take a few moments...
-
+ const cells = virtualChart.map((cell) => (
+
+ ));
+
+ let loadingMessage = null;
+ if (isLoading) {
+ loadingMessage = (
+
+
+
+
Gathering your data...
+
Updating Spending Explorer.
+
This should only take a few moments...
- );
- }
-
- return (
-
- {loadingMessage}
-
);
}
-}
+
+ return (
+
+ {loadingMessage}
+
+
+ );
+};
ExplorerTreemap.propTypes = propTypes;
-ExplorerTreemap.defaultProps = defaultProps;
+
+export default ExplorerTreemap;
+