Skip to content

Commit

Permalink
charts 'no data available' handling (#141)
Browse files Browse the repository at this point in the history
* feat: added 'no data available' checks to ChartOperations and 'no data available' text to ChartContainer

* feat: cleanup
  • Loading branch information
plaume8 authored Dec 18, 2024
1 parent a2797d4 commit 4fc3495
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 6 deletions.
18 changes: 18 additions & 0 deletions src/app/elements/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ export default async function Elements() {
},
],
};

const emptyDummyChartData: LineChartData = {
type: LineChartDataType.LINE_CHART_DATA,
xAxisType: 'linear',
yAxisLabel: 'Mill',
predictionVerticalLineX: 3,
lines: [
{
name: 'Category A',
showRange: true,
dataPoints: [],
},
],
};

return (
<div className="w-full flex flex-col items-center justify-center">
<div className="svg-icon">
Expand Down Expand Up @@ -181,6 +196,9 @@ export default async function Elements() {
<div className="w-400px h-fit">
<CategoricalChart title="" data={categoricalDummyChartData1} />
</div>
<div className="w-400px h-fit">
<LineChart title="Forecast XYZ" data={emptyDummyChartData} />
</div>
</div>
<MapSkeleton />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Charts/CategoricalChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function CategoricalChart({
const { theme } = useTheme();

// build chart options for 'Highcharts'
const defaultChartOptions: Highcharts.Options = CategoricalChartOperations.getHighChartOptions(data);
const defaultChartOptions: Highcharts.Options | undefined = CategoricalChartOperations.getHighChartOptions(data);

// controlling if a bar or pie chart is rendered; bar chart is the default
const [showPieChart, setShowPieChart] = useState(false);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Charts/LineChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function LineChart({

// convert data to `LineChartData` and build chart options for 'Highcharts' (line and bar chart)
const lineChartData: LineChartData = LineChartOperations.convertToLineChartData(data);
const lineChartOptions: Highcharts.Options = LineChartOperations.getHighChartOptions(lineChartData);
const lineChartOptions: Highcharts.Options | undefined = LineChartOperations.getHighChartOptions(lineChartData);

// the `selectedXAxisRange` saves the to be rendered x-axis range of the chart
// can be changed using the `LinkeChartXAxisSlider` if the param `xAxisSlider==true`
Expand Down
21 changes: 20 additions & 1 deletion src/components/Charts/helpers/ChartContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function ChartContainer({
const ICON_BUTTON_SIZE = small ? 3 : 4;
const HEADER_PADDING = title ? 3 : 0;
const MAIN_BOX_PADDING_FACTOR = noPadding ? 0 : 1;
const BOX_BACKGROUND = transparentBackground ? 'bg-transparent' : 'bg-background';

const chartRef = useRef<HighchartsReact.RefObject | null>(null);

Expand All @@ -51,9 +52,24 @@ export function ChartContainer({

// handling the x-axis range slider visibility
const [showSlider, setShowSlider] = useState(false);

// if chartOptions is undefined -> display "no data available"
if (!chartOptions) {
return (
<div
className={`w-full h-40 flex-col rounded-md ${BOX_BACKGROUND} text-secondary text-xs flex flex-row justify-center items-center`}
>
<p>no data available</p>
<p className="max-w-52 pt-1 text-[0.65rem] font-light text-center">
&#39;{title}&#39; chart cannot be displayed
</p>
</div>
);
}

return (
<>
<div className={`w-full h-fit flex-col rounded-md ${transparentBackground ? 'bg-transparent' : 'bg-background'}`}>
<div className={`w-full h-fit flex-col rounded-md ${BOX_BACKGROUND}`}>
<div
className={`w-full h-fit flex flex-row justify-between items-start gap-1 pl-${3 * MAIN_BOX_PADDING_FACTOR} pb-${HEADER_PADDING}`}
>
Expand Down Expand Up @@ -93,6 +109,7 @@ export function ChartContainer({
}
</div>
</div>

{
// description text element should only be rendered if description is available
description && (
Expand All @@ -101,6 +118,7 @@ export function ChartContainer({
</p>
)
}

{/* the actual chart */}
<HighchartsReact
highcharts={Highcharts}
Expand All @@ -114,6 +132,7 @@ export function ChartContainer({
},
}}
/>

{
// slider to e.g. manipulate the plotted x-axis range of the chart
showSlider && sliderProps && <ChartSlider {...sliderProps} />
Expand Down
2 changes: 1 addition & 1 deletion src/domain/props/ChartContainerProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface ChartDownloadButtonProps {
*/

export default interface ChartContainerProps {
chartOptions: Highcharts.Options;
chartOptions?: Highcharts.Options;
chartData: LineChartData | CategoricalChartData;

title?: string;
Expand Down
7 changes: 6 additions & 1 deletion src/operations/charts/CategoricalChartOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ export default class CategoricalChartOperations {
* for a bar chart or pie chart, out of a given `CategoricalChartData` instance.
* @param data `CategoricalChartData` object, containing all data to be plotted in the chart
* @param pieChart if true, a pie chart instead of a bar chart is created
* @return 'Highcharts.Options' ready to be passed to the Highcharts component,
* or 'undefined' if there is no data available to be plotted in the chart (to be interpreted as "no data available")
*/
public static getHighChartOptions(data: CategoricalChartData, pieChart?: boolean): Highcharts.Options {
public static getHighChartOptions(data: CategoricalChartData, pieChart?: boolean): Highcharts.Options | undefined {
const seriesData = [];
const categories = [];
const defaultCategoriesColors = CategoricalChartOperations.getCategoriesColorList();
Expand All @@ -84,6 +86,9 @@ export default class CategoricalChartOperations {
});
}

// if there is not a single series -> we return 'undefined' -> 'undefined' is to be interpreted as "no data available"
if (seriesData.length === 0) return undefined;

// constructing the final HighCharts.Options
return {
title: {
Expand Down
10 changes: 9 additions & 1 deletion src/operations/charts/LineChartOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,15 @@ export default class LineChartOperations {
* @param barChart if true, bars are plotted instead of lines
* @param xAxisSelectedMinIdx index of selected x-axis range min value
* @param xAxisSelectedMaxIdx index of selected x-axis range max value
* @return 'Highcharts.Options' ready to be passed to the Highcharts component,
* or 'undefined' if there is no data available to be plotted in the chart (to be interpreted as "no data available")
*/
public static getHighChartOptions(
data: LineChartData,
xAxisSelectedMinIdx?: number,
xAxisSelectedMaxIdx?: number,
barChart?: boolean
): Highcharts.Options {
): Highcharts.Options | undefined {
// get selected x-axis range min and max values
const xAxisDistinctValues = LineChartOperations.getDistinctXAxisValues(data);
const xAxisSelectedMin = xAxisSelectedMinIdx !== undefined ? xAxisDistinctValues[xAxisSelectedMinIdx] : undefined;
Expand All @@ -198,6 +200,7 @@ export default class LineChartOperations {
const series: SeriesOptionsType[] = [];
const defaultLineColors = LineChartOperations.getLineColorList();
const defaultPredictionsDashStyles = LineChartOperations.getPredictionsDashStyles();
let atLeastOneSeriesAvailable = false;
for (let i = 0; i < data.lines.length; i += 1) {
const lineData = data.lines[i];

Expand Down Expand Up @@ -236,6 +239,8 @@ export default class LineChartOperations {
// make sure data is sorted (required by highchart)
seriesData.sort((a, b) => a.x! - b.x!);

if (seriesData.length > 0) atLeastOneSeriesAvailable = true;

// build series object for highchart
if (barChart) {
// plot series as bars
Expand Down Expand Up @@ -316,6 +321,9 @@ export default class LineChartOperations {
}
}

// not a single non-empty series -> we return 'undefined' ('undefined' is to be interpreted as "no data available")
if (!atLeastOneSeriesAvailable) return undefined;

// build all vertical lines and plot bands
const verticalBands = data.verticalBands ? [...data.verticalBands] : [];
const verticalLines = data.verticalLines ? [...data.verticalLines] : [];
Expand Down

0 comments on commit 4fc3495

Please sign in to comment.