Skip to content
Open
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
23 changes: 23 additions & 0 deletions components/charts/ChartCustomizations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,29 @@ function PieChartCustomizations({
</SelectContent>
</Select>
</div>

<div className="space-y-2">
<Label htmlFor="labelThreshold">
Percentage Threshold
<span className="ml-1 text-xs text-muted-foreground">(Hide labels below %)</span>
</Label>
<Input
id="labelThreshold"
type="number"
min="0"
max="100"
step="0.1"
value={customizations.labelThreshold ?? 5}
onChange={(e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value >= 0 && value <= 100) {
updateCustomization('labelThreshold', value);
}
}}
disabled={disabled}
className="w-32"
/>
</div>
</>
)}
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/charts/ChartExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Download, FileImage, FileText, Loader2 } from 'lucide-react';
import { ChartExporter, generateFilename } from '@/lib/chart-export';
import { toastSuccess, toastError } from '@/lib/toast';
import * as echarts from 'echarts';
import { processChartConfig } from '@/lib/chart-config-processor';

interface ChartExportProps {
chartId: number;
Expand Down Expand Up @@ -86,10 +87,12 @@ export default function ChartExport({

try {
const chart = echarts.init(tempDiv);
chart.setOption({
// Process the config to handle special features like label thresholds
const processedConfig = processChartConfig({
...response.chart_config,
animation: false,
});
chart.setOption(processedConfig);

await new Promise((resolve) => setTimeout(resolve, 1000));

Expand Down
5 changes: 4 additions & 1 deletion components/charts/ChartExportDropdownForList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { toastSuccess, toastError } from '@/lib/toast';
import { ChartExporter, generateFilename, type TableData } from '@/lib/chart-export';
import { MapExportHandler } from '@/lib/map-export-handler';
import { processChartConfig } from '@/lib/chart-config-processor';

interface ChartExportDropdownForListProps {
chartId: number;
Expand Down Expand Up @@ -114,7 +115,9 @@ export function ChartExportDropdownForList({
},
};

chartInstance.setOption(config);
// Process the config to handle special features like label thresholds
const processedConfig = processChartConfig(config);
chartInstance.setOption(processedConfig);

// Wait for rendering
await new Promise((resolve) => setTimeout(resolve, 500));
Expand Down
6 changes: 5 additions & 1 deletion components/charts/ChartPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as echarts from 'echarts';
import { Loader2, AlertCircle, BarChart2 } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { TableChart } from './TableChart';
import { processChartConfig } from '@/lib/chart-config-processor';

interface ChartPreviewProps {
config?: Record<string, any>;
Expand Down Expand Up @@ -62,8 +63,11 @@ export function ChartPreview({
},
};

// Process the config to handle special features like label thresholds
const processedConfig = processChartConfig(modifiedConfig);

// Set chart option
chartInstance.current.setOption(modifiedConfig);
chartInstance.current.setOption(processedConfig);

// Notify parent component that chart is ready
if (onChartReady) {
Expand Down
5 changes: 4 additions & 1 deletion components/charts/MiniChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DatasetComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { processChartConfig } from '@/lib/chart-config-processor';

// Register necessary ECharts components
echarts.use([
Expand Down Expand Up @@ -80,7 +81,9 @@ export function MiniChart({

// Set chart option
const option = generateMiniOption();
chartInstance.current.setOption(option, true); // Use true to clear previous options
// Process the config to handle special features like label thresholds
const processedOption = processChartConfig(option);
chartInstance.current.setOption(processedOption, true); // Use true to clear previous options
}

// Cleanup on unmount
Expand Down
6 changes: 5 additions & 1 deletion components/dashboard/chart-element-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
GeoComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { processChartConfig } from '@/lib/chart-config-processor';

// Register necessary ECharts components
echarts.use([
Expand Down Expand Up @@ -650,8 +651,11 @@ export function ChartElementV2({
},
};

// Process the config to handle special features like label thresholds
const processedConfig = processChartConfig(modifiedConfig);

// Set chart option with animation disabled for better performance
chartInstance.current.setOption(modifiedConfig, {
chartInstance.current.setOption(processedConfig, {
notMerge: true,
lazyUpdate: false,
silent: false,
Expand Down
6 changes: 5 additions & 1 deletion components/dashboard/chart-element-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from '@/lib/dashboard-filter-utils';
import type { ChartDataPayload } from '@/types/charts';
import { useFullscreen } from '@/hooks/useFullscreen';
import { processChartConfig } from '@/lib/chart-config-processor';
import * as echarts from 'echarts/core';
import {
BarChart,
Expand Down Expand Up @@ -890,10 +891,13 @@ export function ChartElementView({
const rect = chartRef.current.getBoundingClientRect();

try {
// Process the config to handle special features like label thresholds
const processedConfig = processChartConfig(styledConfig);

// Use notMerge: true on first render after filter change, false otherwise
const notMerge = filtersChanged || !chartInstance.current.getOption();

chartInstance.current.setOption(styledConfig, notMerge);
chartInstance.current.setOption(processedConfig, notMerge);

// Click event listeners for non-map charts only (maps use MapPreview component)
if (!isMapChart) {
Expand Down
115 changes: 115 additions & 0 deletions lib/chart-config-processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Process chart configuration to handle special features like label thresholds
* This utility transforms backend chart configs to add dynamic formatter functions
*/

/**
* Process ECharts configuration to add dynamic formatters and other frontend-specific logic
* @param config - Raw ECharts configuration from backend
* @returns Processed configuration ready for ECharts
*/
export function processChartConfig(config: any): any {
if (!config) {
return config;
}

// Create a deep copy to avoid mutating the original
const processedConfig = JSON.parse(JSON.stringify(config));

// Process series if it exists
if (processedConfig.series && Array.isArray(processedConfig.series)) {
processedConfig.series = processedConfig.series.map((series: any) => {
// Handle pie chart label threshold
if (series.type === 'pie' && series.label) {
const labelThreshold = series.label.labelThreshold;
const labelFormat = series.label.labelFormat;

// Only apply formatter if threshold is defined and greater than 0
if (labelThreshold !== undefined && labelThreshold > 0) {
// Calculate total value for percentage calculations
const totalValue =
series.data?.reduce((sum: number, item: any) => sum + (item.value || 0), 0) || 1;

// Modify each data item to have individual label and labelLine settings
if (series.data && Array.isArray(series.data)) {
series.data = series.data.map((item: any) => {
const percentage = (item.value / totalValue) * 100;
const showLabel = percentage >= labelThreshold;

return {
...item,
label: {
show: showLabel,
formatter: showLabel
? labelFormat === 'percentage'
? '{d}%'
: labelFormat === 'value'
? '{c}'
: labelFormat === 'name_percentage'
? '{b}\n{d}%'
: labelFormat === 'name_value'
? '{b}\n{c}'
: '{d}%'
: '',
},
labelLine: {
show: showLabel,
},
};
});
}

// Keep the series-level formatter as fallback but it won't be used for items with individual configs
series.label.formatter = function (params: any) {
if (params.percent < labelThreshold) {
return '';
}
switch (labelFormat) {
case 'percentage':
return params.percent.toFixed(1) + '%';
case 'value':
return String(params.value);
case 'name_value':
return params.name + '\n' + params.value;
case 'name_percentage':
return params.name + '\n' + params.percent.toFixed(1) + '%';
default:
return params.percent.toFixed(1) + '%';
}
};
} else if (labelThreshold === 0 || labelThreshold === undefined) {
// No threshold or zero threshold - use default formatters
// Keep the existing formatter string from backend if no threshold
// This ensures backward compatibility
}

// Clean up custom properties that aren't valid ECharts options
delete series.label.labelThreshold;
delete series.label.labelFormat;
}

return series;
});
}

return processedConfig;
}

/**
* Check if a chart configuration needs processing
* @param config - Chart configuration
* @returns true if the config needs processing
*/
export function needsProcessing(config: any): boolean {
if (!config || !config.series) {
return false;
}

// Check if any series has custom properties that need processing
return config.series.some((series: any) => {
if (series.type === 'pie' && series.label) {
return series.label.labelThreshold !== undefined;
}
return false;
});
}
Loading