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

[Graphs] Data Table Expandable & Data Issue Note #2258

Merged
merged 5 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions src/common/ExpandableSection.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@
padding: 1em 1.3em;
width: 100%;
}

/* Table CSS within Graphs */
.expandable-section .bold-font {
font-family: SourceSansProBold;
}
25 changes: 20 additions & 5 deletions src/data-browser/graphs/Graph.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { hideUnselectedLines } from './utils/graphHelpers'
import { useCallback, useRef } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import Highcharts from 'highcharts'
import HighchartsExport from 'highcharts/modules/exporting'
import HighchartsExportData from 'highcharts/modules/export-data'
import HighchartsAccessibility from 'highcharts/modules/accessibility'
import HighchartsReact from 'highcharts-react-official'
import { hideUnselectedLines } from './utils/graphHelpers'
import useGraphLoading from './useGraphLoading'
import { AvoidJumpToDataTable } from './highchartsCustomModules'
import { DataTable } from './quarterly/DataTable'
import { useTableData } from './quarterly/useTableData'

HighchartsExport(Highcharts) // Enable export to image
HighchartsExportData(Highcharts) // Enable export of underlying data
Expand All @@ -22,6 +24,10 @@ Highcharts.setOptions({
export const Graph = ({ options, loading, seriesForURL }) => {
const chartRef = useRef()

// No longer using HighCharts Data Table and instead we are generating our own to allow for more custom styling
const { tableData, isSeriesVisible, generateTableData } =
useTableData(chartRef)

/**
* Note about onLoad:
*
Expand All @@ -33,17 +39,25 @@ export const Graph = ({ options, loading, seriesForURL }) => {
*
* See the following link for context on how these images are generated.
* https://github.com/highcharts/highcharts-react/issues/315
*
* Additionally we are now manually generating the data table instead of having HighCharts automatically creating it
*/
const onLoad = useCallback(
(ref) => {
// Hide series based on URL query parameters
hideUnselectedLines(ref, seriesForURL)
(chart) => {
hideUnselectedLines(chart, seriesForURL)
generateTableData()
},
[seriesForURL],
)

useGraphLoading(chartRef, loading, options)

useEffect(() => {
if (chartRef.current && chartRef.current.chart) {
generateTableData()
}
}, [options])

return (
<div className='graph-wrapper'>
<div className='export-charts'>
Expand All @@ -54,6 +68,7 @@ export const Graph = ({ options, loading, seriesForURL }) => {
callback={onLoad}
/>
</div>
{isSeriesVisible && <DataTable tableData={tableData} />}
</div>
)
}
21 changes: 21 additions & 0 deletions src/data-browser/graphs/highchartsConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,27 @@ export const baseConfig = {
sourceHeight: 595,
sourceWidth: 842,
showTable: hmda_charts.config.showDataTable, // OPTION: Show/hide underlying data (already available in export menu)
buttons: { // Removed Data Table Button as we are using our own custom DataTable component
contextButton: {
menuItems: [
'viewFullscreen',
'printChart',
'separator',
'downloadPNG',
'downloadJPEG',
'downloadPDF',
'downloadSVG',
'separator',
'downloadCSV',
'downloadXLS',
],
},
},
},
navigation: {
buttonOptions: {
enabled: true,
},
},
colors: seriesColors,
chart: {
Expand Down
36 changes: 36 additions & 0 deletions src/data-browser/graphs/quarterly/DataTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { ExpandableSection } from '../../../common/ExpandableSection'

export const DataTable = ({ tableData }) => {
if (!tableData) return null

return (
<div style={{ marginTop: '.8em' }}>
<ExpandableSection label={'Data Table'}>
<table>
<thead>
<tr>
{tableData.headers.map((header, index) => (
<th key={index}>{header}</th>
))}
</tr>
</thead>
<tbody>
{tableData.rows.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, cellIndex) => (
<td
key={cellIndex}
className={cell.includes('Q') ? 'bold-font' : ''}
>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</ExpandableSection>
</div>
)
}
7 changes: 7 additions & 0 deletions src/data-browser/graphs/quarterly/SectionGraphs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,13 @@ export const SectionGraphs = ({
yAxis: [selectedGraphData?.yLabel],
})}
/>

<div className='alert' style={{ marginTop: '1.7em' }}>
<p style={{ margin: 0 }}>
Data points which would be generated based on fewer than 100 loans are
not shown. This may cause gaps in the graph and table.
</p>
</div>
</>
)
}
89 changes: 89 additions & 0 deletions src/data-browser/graphs/quarterly/useTableData.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useState, useCallback, useEffect } from 'react'
import { useSelector } from 'react-redux'
import Highcharts from 'highcharts'
import { graphs } from '../slice'
import { SELECTED_GRAPH_DATA } from '../slice/graphConfigs'

export const useTableData = (chartRef) => {
const [tableData, setTableData] = useState(null)
const [isSeriesVisible, setIsSeriesVisible] = useState(true)

const graphsConfigStore = useSelector(({ graphsConfig }) => graphsConfig)
const selectedGraphData = graphs.getConfig(
graphsConfigStore,
SELECTED_GRAPH_DATA,
)

const formatNumber = (value, decimalPrecision) => {
if (value === null || value === undefined || isNaN(value)) return ''
const formatted = parseFloat(value).toFixed(decimalPrecision)
const [wholePart, decimalPart] = formatted.split('.')
const withCommas = wholePart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return decimalPart ? `${withCommas}.${decimalPart}` : withCommas
}

const generateTableData = useCallback(() => {
if (chartRef.current && chartRef.current.chart) {
const chart = chartRef.current.chart
const series = chart.series.filter((s) => s.visible)

if (series.length === 0) {
setIsSeriesVisible(false)
setTableData(null)
return null
}

const categories = chart.xAxis[0].categories || []
const decimalPrecision = selectedGraphData?.decimalPrecision || 0

const tableData = {
headers: ['Year Quarter', ...series.map((s) => s.name)],
rows: categories.map((category, index) => {
return [
category,
...series.map((s) => {
const value = s.data[index] ? s.data[index].y : ''
return formatNumber(value, decimalPrecision)
}),
]
}),
}

setTableData(tableData)
setIsSeriesVisible(true)

// Hide the HighCharts data table
const highchartsDataTable = document.querySelector('.highcharts-data-table');
if (highchartsDataTable) {
highchartsDataTable.style.display = 'none';
}
}
}, [selectedGraphData, chartRef])

useEffect(() => {
if (chartRef.current && chartRef.current.chart) {
const chart = chartRef.current.chart
generateTableData()

// Use Highcharts.addEvent for proper event binding
const hideHandler = Highcharts.addEvent(
chart.series,
'hide',
generateTableData,
)
const showHandler = Highcharts.addEvent(
chart.series,
'show',
generateTableData,
)

return () => {
// Clean up event listeners
if (hideHandler) hideHandler()
if (showHandler) showHandler()
}
}
}, [chartRef])

return { tableData, isSeriesVisible, generateTableData }
}