Refactored LCPDataGrid

This commit is contained in:
Jeremy Rangel
2025-01-24 03:06:04 -08:00
parent 23944b0052
commit be3a4fb9ff
12 changed files with 427 additions and 689 deletions

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useCallback } from '@wordpress/element';
import { Button, Modal, SelectControl, ToggleControl } from '@wordpress/components';
import { TextControl, Button, Modal, SelectControl, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import LCPDataGrid from './LCPDataGrid';
import LCPDataUploader from './LCPDataUploader';
@ -8,16 +8,133 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
const [isOpen, setIsOpen] = useState(false);
const [options, setOptions] = useState([]);
// List of CSS color names
const CSS_COLOR_NAMES = [
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black',
'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan',
'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen',
'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue',
'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray',
'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite',
'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred',
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen',
'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue',
'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue',
'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid',
'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff',
'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue',
'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue',
'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle',
'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen'
];
// Helper function to check if a string is a valid hex color
const isHexColor = (str) => {
return /^#([0-9A-F]{3}){1,2}$/i.test(str) || /^#[0-9A-F]{6}$/i.test(str);
};
// Helper function to check if a string is a valid color name
const isColorName = (str) => {
return CSS_COLOR_NAMES.includes(str.toLowerCase());
};
// Helper function to guess column type based on value
const guessColumnType = useCallback((value) => {
if (value === null || value === undefined) return 'text';
// Handle numeric values
if (typeof value === 'number') return 'number';
if (typeof value === 'string') {
const trimmedValue = value.trim();
// Try parsing as number first
if (!isNaN(trimmedValue) && !isNaN(parseFloat(trimmedValue))) {
return 'number';
}
// Check for colors (hex or named)
if (isHexColor(trimmedValue) || isColorName(trimmedValue)) {
return 'lcpColor';
}
// Check for date
if (!isNaN(Date.parse(trimmedValue))) {
return 'lcpDate';
}
// Check for HTML
if (/<[a-z][\s\S]*>/i.test(trimmedValue)) {
return 'html';
}
}
return 'text';
}, []);
// Function to analyze and update column types
const analyzeColumnTypes = useCallback((data) => {
if (!data || !data.length) return {};
// Get all unique keys except special columns
const keys = Array.from(new Set(
data.flatMap(item => Object.keys(item))
)).filter(key => !['ID', 'Parent'].includes(key));
// For each key, collect all values and determine type
const newTypes = keys.reduce((acc, key) => {
const values = data.map(item => item[key]).filter(v => v != null);
// Try to determine the most appropriate type
const types = values.map(guessColumnType);
// If any value is a number, treat the whole column as number
if (types.includes('number')) {
acc[key] = 'number';
} else {
// Otherwise use the most common type
const typeCounts = types.reduce((counts, type) => {
counts[type] = (counts[type] || 0) + 1;
return counts;
}, {});
acc[key] = Object.entries(typeCounts)
.sort(([,a], [,b]) => b - a)[0][0];
}
return acc;
}, {});
return newTypes;
}, [guessColumnType]);
// Function to update chartData attribute with parsed JSON data from CSV
const handleJsonDataUpdate = (newData) => {
console.log('Received new data from CSV:', newData);
// Update chartData
setAttributes({ chartData: newData });
// Analyze and update column types
const newTypes = analyzeColumnTypes(newData);
setAttributes({ columnTypes: newTypes });
};
// Handle Edit Dataset button click
const handleEditClick = useCallback(() => {
// Update column types before opening the editor
if (chartData?.length > 0) {
const newTypes = analyzeColumnTypes(chartData);
setAttributes({ columnTypes: newTypes });
}
setIsOpen(true);
}, [chartData, analyzeColumnTypes, setAttributes]);
useEffect(() => {
if (chartData && chartData.length > 0) {
const columns = Object.keys(chartData[0]).filter(key => key !== 'lcpId');
setOptions(columns.map(col => ({ label: col, value: col })));
const columns = getAvailableColumns();
setOptions(columns.map(col => ({
label: col.name,
value: col.key
})));
}
}, [chartData]);
@ -25,12 +142,12 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
const getAvailableColumns = () => {
if (!chartData.length) return [];
// Get all unique keys from the data
const columns = Array.from(new Set(
chartData.flatMap(item => Object.keys(item))
)).filter(key => key !== 'lcpId'); // Exclude lcpId column
return columns;
// Get the first row which contains our column names
const columnNames = Object.entries(chartData[0])
.filter(([key]) => !['ID', 'Parent'].includes(key))
.map(([key, value]) => ({ key, name: value }));
return columnNames;
};
// Get column options based on type filter
@ -38,10 +155,17 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
return columns
.filter(col => {
if (!typeFilter) return true;
const columnType = attributes.columnTypes?.[col];
const columnType = attributes.columnTypes?.[col.key];
// Handle both 'number' and 'lcpNumber' for backward compatibility
if (typeFilter === 'number') {
return columnType === 'number';
}
return columnType === typeFilter;
})
.map(col => ({ label: col, value: col }));
.map(col => ({
label: col.name, // Use the column name from first row
value: col.key // Use the field key for internal reference
}));
};
const handleDataChange = (newData) => {
@ -97,7 +221,7 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
const allOptions = getColumnOptions(availableColumns);
// Get numeric column options for bar chart value
const numericOptions = getColumnOptions(availableColumns, 'lcpNumber');
const numericOptions = getColumnOptions(availableColumns, 'number');
// Determine which options to use for value column
const valueColumnOptions = chartType === 'bar' ? numericOptions : allOptions;
@ -107,9 +231,9 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
return (
<div>
<Button
variant="secondary"
onClick={() => setIsOpen(true)}
<Button
variant="secondary"
onClick={handleEditClick}
style={{ marginBottom: '10px', width: '100%' }}
>
{__('Edit Dataset', 'lcp-visualize')}
@ -138,7 +262,7 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
columnTypes: newColumnTypes,
// If we're changing from a numeric type and this is the value column, reset it
valueColumn: field === attributes.valueColumn &&
type !== 'lcpNumber' &&
type !== 'number' &&
chartType === 'bar' ? '' : attributes.valueColumn
});
}}
@ -176,6 +300,12 @@ const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes
options={options}
onChange={(value) => setAttributes({ labelsColumn: value })}
/>
<TextControl
label={__('Hierarchical Columns', 'lcp')}
help={__('Categorical columns', 'lcp')}
value={attributes.hierarchicalColumnOrder || ''}
onChange={(value) => setAttributes({ hierarchicalColumnOrder: value })}
/>
<LCPDataUploader onJsonDataUpdate={handleJsonDataUpdate} />
<SelectControl
label={__('Color Column', 'lcp-visualize')}