Compare commits
2 Commits
83f5cad36f
...
3c9d74e8f6
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9d74e8f6 | |||
| 9ce6586662 |
@ -17,6 +17,14 @@
|
|||||||
"style": "file:./style-index.css",
|
"style": "file:./style-index.css",
|
||||||
"viewScript": "file:./view.js",
|
"viewScript": "file:./view.js",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"enableHierarchicalView": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"selectedParentId": {
|
||||||
|
"type": "string",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
"chartColorSource": {
|
"chartColorSource": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "default"
|
"default": "default"
|
||||||
@ -116,6 +124,10 @@
|
|||||||
"gridOpacity": {
|
"gridOpacity": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 0.5
|
"default": 0.5
|
||||||
|
},
|
||||||
|
"enableStackedBars": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1 +1 @@
|
|||||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => 'd3e2bb3c261756c0085a');
|
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '79dffd7b201fcf20bc7c');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
<?php return array('dependencies' => array(), 'version' => '70f08b60c296a36b16fe');
|
<?php return array('dependencies' => array(), 'version' => 'cd3eca3e743bd29f75fd');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -17,6 +17,14 @@
|
|||||||
"style": "file:./style-index.css",
|
"style": "file:./style-index.css",
|
||||||
"viewScript": "file:./view.js",
|
"viewScript": "file:./view.js",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
|
"enableHierarchicalView": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"selectedParentId": {
|
||||||
|
"type": "string",
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
"chartColorSource": {
|
"chartColorSource": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "default"
|
"default": "default"
|
||||||
@ -116,6 +124,10 @@
|
|||||||
"gridOpacity": {
|
"gridOpacity": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 0.5
|
"default": 0.5
|
||||||
|
},
|
||||||
|
"enableStackedBars": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,33 +19,108 @@ const BarGraph = ({
|
|||||||
colorSource,
|
colorSource,
|
||||||
defaultBarColor,
|
defaultBarColor,
|
||||||
customColors,
|
customColors,
|
||||||
barOpacity
|
barOpacity,
|
||||||
|
enableHierarchicalView,
|
||||||
|
enableStackedBars,
|
||||||
|
selectedParentId,
|
||||||
|
setAttributes
|
||||||
}) => {
|
}) => {
|
||||||
const svgRef = useRef();
|
const svgRef = useRef();
|
||||||
const containerRef = useRef();
|
const containerRef = useRef();
|
||||||
|
|
||||||
|
// Helper function to darken a color
|
||||||
|
const darkenColor = (color, amount) => {
|
||||||
|
const rgb = d3.color(color).rgb();
|
||||||
|
return d3.rgb(
|
||||||
|
Math.max(0, rgb.r - amount),
|
||||||
|
Math.max(0, rgb.g - amount),
|
||||||
|
Math.max(0, rgb.b - amount)
|
||||||
|
).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get children of a node
|
||||||
|
const getChildren = (nodeId) => {
|
||||||
|
const children = [];
|
||||||
|
Object.entries(data).forEach(([dataset, items]) => {
|
||||||
|
items.forEach(item => {
|
||||||
|
if (item.parent === nodeId) {
|
||||||
|
children.push({
|
||||||
|
...item,
|
||||||
|
dataset,
|
||||||
|
color: item.color || defaultBarColor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data || !svgRef.current || !containerRef.current) return;
|
if (!data || !svgRef.current || !containerRef.current) return;
|
||||||
|
|
||||||
// Clear previous content
|
// Clear previous content
|
||||||
d3.select(svgRef.current).selectAll("*").remove();
|
d3.select(svgRef.current).selectAll("*").remove();
|
||||||
|
|
||||||
// Convert data to simple array
|
// Filter data based on hierarchical view
|
||||||
const chartData = [];
|
let displayData = data;
|
||||||
for (const [dataset, items] of Object.entries(data)) {
|
if (enableHierarchicalView && !enableStackedBars) {
|
||||||
for (const item of items) {
|
displayData = {};
|
||||||
chartData.push({
|
Object.entries(data).forEach(([dataset, items]) => {
|
||||||
dataset,
|
const filteredItems = items.filter(item => item.parent === selectedParentId);
|
||||||
label: item.label,
|
if (filteredItems.length > 0) {
|
||||||
value: parseInt(item.value, 10),
|
displayData[dataset] = filteredItems;
|
||||||
color: item.color || '#FF6384'
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert data to array format
|
||||||
|
const chartDataArray = [];
|
||||||
|
Object.entries(displayData).forEach(([dataset, items]) => {
|
||||||
|
items.forEach(item => {
|
||||||
|
if (!enableStackedBars || item.parent === null) {
|
||||||
|
const baseColor = item.color || defaultBarColor;
|
||||||
|
const children = getChildren(item.id);
|
||||||
|
|
||||||
|
// Create the main bar
|
||||||
|
const barData = {
|
||||||
|
id: item.id,
|
||||||
|
dataset,
|
||||||
|
label: item.label || '',
|
||||||
|
value: parseFloat(item.value) || 0,
|
||||||
|
color: baseColor,
|
||||||
|
hasChildren: children.length > 0,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// If stacked bars are enabled, add children data
|
||||||
|
if (enableStackedBars && children.length > 0) {
|
||||||
|
let usedColors = new Set([baseColor]);
|
||||||
|
children.forEach((child, index) => {
|
||||||
|
let childColor = child.color || baseColor;
|
||||||
|
|
||||||
|
// If color is already used, darken it
|
||||||
|
while (usedColors.has(childColor)) {
|
||||||
|
childColor = darkenColor(childColor, 30);
|
||||||
|
}
|
||||||
|
usedColors.add(childColor);
|
||||||
|
|
||||||
|
barData.children.push({
|
||||||
|
id: child.id,
|
||||||
|
label: child.label,
|
||||||
|
value: parseFloat(child.value) || 0,
|
||||||
|
color: childColor
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
chartDataArray.push(barData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Get container dimensions
|
// Get container dimensions
|
||||||
const containerWidth = containerRef.current.clientWidth;
|
const containerWidth = containerRef.current.clientWidth;
|
||||||
const containerHeight = containerRef.current.clientHeight;
|
const containerHeight = typeof height === 'number' ? height : 400;
|
||||||
|
|
||||||
// Basic dimensions with extra margin for axis labels
|
// Basic dimensions with extra margin for axis labels
|
||||||
const margin = {
|
const margin = {
|
||||||
@ -54,13 +129,14 @@ const BarGraph = ({
|
|||||||
bottom: xAxisLabel ? 50 : 30,
|
bottom: xAxisLabel ? 50 : 30,
|
||||||
left: yAxisLabel ? 60 : 40
|
left: yAxisLabel ? 60 : 40
|
||||||
};
|
};
|
||||||
const innerWidth = containerWidth - margin.left - margin.right;
|
const innerWidth = Math.max(containerWidth - margin.left - margin.right, 0);
|
||||||
const innerHeight = containerHeight - margin.top - margin.bottom;
|
const innerHeight = Math.max(containerHeight - margin.top - margin.bottom, 0);
|
||||||
|
|
||||||
// Create SVG
|
// Create SVG
|
||||||
const svg = d3.select(svgRef.current)
|
const svg = d3.select(svgRef.current)
|
||||||
.attr('width', containerWidth)
|
.attr('width', containerWidth)
|
||||||
.attr('height', containerHeight);
|
.attr('height', containerHeight)
|
||||||
|
.style('background-color', backgroundColor);
|
||||||
|
|
||||||
// Add title if present
|
// Add title if present
|
||||||
if (title) {
|
if (title) {
|
||||||
@ -78,12 +154,17 @@ const BarGraph = ({
|
|||||||
|
|
||||||
// Create scales
|
// Create scales
|
||||||
const xScale = d3.scaleBand()
|
const xScale = d3.scaleBand()
|
||||||
.domain(chartData.map(d => d.label))
|
.domain(chartDataArray.map(d => d.label))
|
||||||
.range([0, innerWidth])
|
.range([0, innerWidth])
|
||||||
.padding(0.1);
|
.padding(0.1);
|
||||||
|
|
||||||
const yScale = d3.scaleLinear()
|
const yScale = d3.scaleLinear()
|
||||||
.domain([0, d3.max(chartData, d => d.value)])
|
.domain([0, d3.max(chartDataArray, d => {
|
||||||
|
if (enableStackedBars) {
|
||||||
|
return d.value + d.children.reduce((sum, child) => sum + child.value, 0);
|
||||||
|
}
|
||||||
|
return d.value;
|
||||||
|
}) || 100])
|
||||||
.range([innerHeight, 0]);
|
.range([innerHeight, 0]);
|
||||||
|
|
||||||
// Get color for a datapoint based on color source
|
// Get color for a datapoint based on color source
|
||||||
@ -92,7 +173,7 @@ const BarGraph = ({
|
|||||||
case 'singleColor':
|
case 'singleColor':
|
||||||
return defaultBarColor;
|
return defaultBarColor;
|
||||||
case 'customColors':
|
case 'customColors':
|
||||||
const customColor = customColors.find(
|
const customColor = customColors?.find(
|
||||||
c => c.dataset === datapoint.dataset && c.label === datapoint.label
|
c => c.dataset === datapoint.dataset && c.label === datapoint.label
|
||||||
);
|
);
|
||||||
return customColor ? customColor.color : datapoint.color;
|
return customColor ? customColor.color : datapoint.color;
|
||||||
@ -126,34 +207,111 @@ const BarGraph = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add bars
|
// Add bars
|
||||||
const bars = g.selectAll('rect')
|
const barGroups = g.selectAll('.bar-group')
|
||||||
.data(chartData)
|
.data(chartDataArray)
|
||||||
.enter()
|
.enter()
|
||||||
.append('rect')
|
.append('g')
|
||||||
.attr('x', d => xScale(d.label))
|
.attr('class', 'bar-group')
|
||||||
|
.attr('transform', d => `translate(${xScale(d.label)},0)`);
|
||||||
|
|
||||||
|
// Add main bars
|
||||||
|
barGroups.append('rect')
|
||||||
|
.attr('class', 'main-bar')
|
||||||
.attr('y', d => yScale(d.value))
|
.attr('y', d => yScale(d.value))
|
||||||
.attr('width', xScale.bandwidth())
|
.attr('width', xScale.bandwidth())
|
||||||
.attr('height', d => innerHeight - yScale(d.value))
|
.attr('height', d => innerHeight - yScale(d.value))
|
||||||
.attr('fill', d => getColor(d))
|
.attr('fill', d => getColor(d))
|
||||||
.style('opacity', barOpacity);
|
.style('opacity', barOpacity)
|
||||||
|
.style('cursor', d => (d.hasChildren && !enableStackedBars) ? 'pointer' : 'default');
|
||||||
|
|
||||||
|
// Add stacked child bars if enabled
|
||||||
|
if (enableStackedBars) {
|
||||||
|
barGroups.each(function(d) {
|
||||||
|
let yOffset = d.value;
|
||||||
|
d.children.forEach(child => {
|
||||||
|
d3.select(this)
|
||||||
|
.append('rect')
|
||||||
|
.attr('class', 'child-bar')
|
||||||
|
.attr('y', () => yScale(yOffset + child.value))
|
||||||
|
.attr('width', xScale.bandwidth())
|
||||||
|
.attr('height', () => innerHeight - yScale(child.value))
|
||||||
|
.attr('fill', child.color)
|
||||||
|
.style('opacity', barOpacity);
|
||||||
|
yOffset += child.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handlers for hierarchical view
|
||||||
|
if (enableHierarchicalView && !enableStackedBars) {
|
||||||
|
barGroups.selectAll('.main-bar').on('click', function(event, d) {
|
||||||
|
if (d.hasChildren && setAttributes) {
|
||||||
|
setAttributes({ selectedParentId: d.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add back button if not at root level
|
||||||
|
if (selectedParentId !== null) {
|
||||||
|
svg.append('text')
|
||||||
|
.attr('x', margin.left)
|
||||||
|
.attr('y', margin.top / 2)
|
||||||
|
.attr('class', 'back-button')
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.style('font-weight', 'bold')
|
||||||
|
.text('← Back')
|
||||||
|
.on('click', function() {
|
||||||
|
if (setAttributes) {
|
||||||
|
let parentParentId = null;
|
||||||
|
Object.values(data).forEach(items => {
|
||||||
|
const currentItem = items.find(item => item.id === selectedParentId);
|
||||||
|
if (currentItem) {
|
||||||
|
parentParentId = currentItem.parent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setAttributes({ selectedParentId: parentParentId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add bar values
|
// Add bar values
|
||||||
if (showBarValues) {
|
if (showBarValues) {
|
||||||
g.selectAll('.bar-value')
|
barGroups.each(function(d) {
|
||||||
.data(chartData)
|
// Add value for main bar
|
||||||
.enter()
|
d3.select(this)
|
||||||
.append('text')
|
.append('text')
|
||||||
.attr('class', 'bar-value')
|
.attr('class', 'bar-value')
|
||||||
.attr('x', d => xScale(d.label) + xScale.bandwidth() / 2)
|
.attr('x', xScale.bandwidth() / 2)
|
||||||
.attr('y', d => yScale(d.value) - 5)
|
.attr('y', yScale(d.value) - 5)
|
||||||
.attr('text-anchor', 'middle')
|
.attr('text-anchor', 'middle')
|
||||||
.text(d => d.value);
|
.text(d.value);
|
||||||
|
|
||||||
|
// Add values for stacked bars if enabled
|
||||||
|
if (enableStackedBars) {
|
||||||
|
let yOffset = d.value;
|
||||||
|
d.children.forEach(child => {
|
||||||
|
d3.select(this)
|
||||||
|
.append('text')
|
||||||
|
.attr('class', 'bar-value')
|
||||||
|
.attr('x', xScale.bandwidth() / 2)
|
||||||
|
.attr('y', yScale(yOffset + child.value) - 5)
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.text(child.value);
|
||||||
|
yOffset += child.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add X axis
|
// Add X axis
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('transform', `translate(0,${innerHeight})`)
|
.attr('transform', `translate(0,${innerHeight})`)
|
||||||
.call(d3.axisBottom(xScale));
|
.call(d3.axisBottom(xScale))
|
||||||
|
.selectAll('text')
|
||||||
|
.style('text-anchor', 'end')
|
||||||
|
.attr('dx', '-.8em')
|
||||||
|
.attr('dy', '.15em')
|
||||||
|
.attr('transform', 'rotate(-45)');
|
||||||
|
|
||||||
// Add Y axis
|
// Add Y axis
|
||||||
g.append('g')
|
g.append('g')
|
||||||
@ -182,7 +340,7 @@ const BarGraph = ({
|
|||||||
.text(yAxisLabel);
|
.text(yAxisLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [data, width, height, title, showGridX, showGridY, xGridColor, yGridColor, xGridWidth, yGridWidth, showBarValues, xAxisLabel, yAxisLabel, colorSource, defaultBarColor, customColors, barOpacity]);
|
}, [data, width, height, title, showGridX, showGridY, xGridColor, yGridColor, xGridWidth, yGridWidth, showBarValues, xAxisLabel, yAxisLabel, colorSource, defaultBarColor, customColors, barOpacity, enableHierarchicalView, enableStackedBars, selectedParentId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -12,7 +12,8 @@ import {
|
|||||||
TabPanel,
|
TabPanel,
|
||||||
TextControl,
|
TextControl,
|
||||||
Button,
|
Button,
|
||||||
Popover
|
Popover,
|
||||||
|
TextareaControl
|
||||||
} from '@wordpress/components';
|
} from '@wordpress/components';
|
||||||
|
|
||||||
import {useState} from '@wordpress/element';
|
import {useState} from '@wordpress/element';
|
||||||
@ -50,7 +51,10 @@ export default function Edit({ attributes, setAttributes }) {
|
|||||||
xAxisLabel,
|
xAxisLabel,
|
||||||
yAxisLabel,
|
yAxisLabel,
|
||||||
chartColorSource,
|
chartColorSource,
|
||||||
chartCustomColors } = attributes;
|
chartCustomColors,
|
||||||
|
enableHierarchicalView,
|
||||||
|
enableStackedBars,
|
||||||
|
selectedParentId } = attributes;
|
||||||
|
|
||||||
const blockProps = useBlockProps();
|
const blockProps = useBlockProps();
|
||||||
|
|
||||||
@ -163,6 +167,43 @@ export default function Edit({ attributes, setAttributes }) {
|
|||||||
// Debug log to check chartData
|
// Debug log to check chartData
|
||||||
console.log('Chart data:', chartData);
|
console.log('Chart data:', chartData);
|
||||||
|
|
||||||
|
// Ensure chartData is an object
|
||||||
|
if (!attributes.chartData || typeof attributes.chartData !== 'object') {
|
||||||
|
setAttributes({ chartData: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateChartData = (value) => {
|
||||||
|
try {
|
||||||
|
// Attempt to parse the JSON input
|
||||||
|
const parsedData = JSON.parse(value);
|
||||||
|
|
||||||
|
// Validate the structure
|
||||||
|
if (typeof parsedData === 'object' && parsedData !== null) {
|
||||||
|
// Check if the data follows our expected format
|
||||||
|
let isValid = true;
|
||||||
|
Object.entries(parsedData).forEach(([dataset, items]) => {
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
items.forEach(item => {
|
||||||
|
if (!item.id || typeof item.id !== 'string') {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
setAttributes({ chartData: parsedData });
|
||||||
|
} else {
|
||||||
|
console.error('Invalid data structure');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Invalid JSON:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...blockProps}>
|
<div {...blockProps}>
|
||||||
<InspectorControls>
|
<InspectorControls>
|
||||||
@ -192,6 +233,7 @@ export default function Edit({ attributes, setAttributes }) {
|
|||||||
return (
|
return (
|
||||||
<Panel>
|
<Panel>
|
||||||
<PanelBody title="Data Settings">
|
<PanelBody title="Data Settings">
|
||||||
|
|
||||||
<LCPDataSelector
|
<LCPDataSelector
|
||||||
value={chartData}
|
value={chartData}
|
||||||
onChange={handleDataChange}
|
onChange={handleDataChange}
|
||||||
@ -199,7 +241,17 @@ export default function Edit({ attributes, setAttributes }) {
|
|||||||
onDataSourceChange={handleDataSourceChange}
|
onDataSourceChange={handleDataSourceChange}
|
||||||
/>
|
/>
|
||||||
</PanelBody>
|
</PanelBody>
|
||||||
<PanelBody title="Chart Settings">
|
<PanelBody title={__('Chart Settings', 'lcp')} initialOpen={true}>
|
||||||
|
<ToggleControl
|
||||||
|
label={__('Enable Hierarchical View', 'lcp')}
|
||||||
|
checked={enableHierarchicalView}
|
||||||
|
onChange={(value) => setAttributes({ enableHierarchicalView: value })}
|
||||||
|
/>
|
||||||
|
<ToggleControl
|
||||||
|
label={__('Enable Stacked Bars', 'lcp')}
|
||||||
|
checked={enableStackedBars}
|
||||||
|
onChange={(value) => setAttributes({ enableStackedBars: value })}
|
||||||
|
/>
|
||||||
<ToggleControl
|
<ToggleControl
|
||||||
label={__('Display Chart Title', 'lcp')}
|
label={__('Display Chart Title', 'lcp')}
|
||||||
checked={displayChartTitle}
|
checked={displayChartTitle}
|
||||||
@ -439,17 +491,19 @@ export default function Edit({ attributes, setAttributes }) {
|
|||||||
showGridX={showGridX}
|
showGridX={showGridX}
|
||||||
showGridY={showGridY}
|
showGridY={showGridY}
|
||||||
yGridColor={yGridColor}
|
yGridColor={yGridColor}
|
||||||
xGridColor={xGridColor}
|
xGridColor={xGridColor}
|
||||||
xGridWidth={xGridWidth}
|
xGridWidth={xGridWidth}
|
||||||
yGridWidth={yGridWidth}
|
yGridWidth={yGridWidth}
|
||||||
title={displayChartTitle ? chartTitle : ''}
|
title={displayChartTitle ? chartTitle : ''}
|
||||||
showSorting={showSorting}
|
|
||||||
showFiltering={showFiltering}
|
|
||||||
showBarValues={showBarValues}
|
showBarValues={showBarValues}
|
||||||
xAxisLabel={xAxisLabel}
|
xAxisLabel={xAxisLabel}
|
||||||
yAxisLabel={yAxisLabel}
|
yAxisLabel={yAxisLabel}
|
||||||
colorSource={chartColorSource}
|
colorSource={chartColorSource}
|
||||||
customColors={chartCustomColors}
|
customColors={chartCustomColors}
|
||||||
|
enableHierarchicalView={enableHierarchicalView}
|
||||||
|
enableStackedBars={enableStackedBars}
|
||||||
|
selectedParentId={selectedParentId}
|
||||||
|
setAttributes={setAttributes}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -23,12 +23,8 @@
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
// Check if we have any bar graphs to render
|
if (!window.lcpBarGraphData) return;
|
||||||
if (!window.lcpBarGraphData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render each bar graph
|
|
||||||
Object.entries(window.lcpBarGraphData).forEach(([blockId, data]) => {
|
Object.entries(window.lcpBarGraphData).forEach(([blockId, data]) => {
|
||||||
const { attributes } = data;
|
const { attributes } = data;
|
||||||
const container = document.getElementById(blockId);
|
const container = document.getElementById(blockId);
|
||||||
@ -38,36 +34,52 @@ window.addEventListener('load', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function filterDataByParent(chartData, parentId) {
|
||||||
|
const filteredData = {};
|
||||||
|
|
||||||
|
Object.entries(chartData).forEach(([dataset, items]) => {
|
||||||
|
const filteredItems = items.filter(item => item.parent === parentId);
|
||||||
|
if (filteredItems.length > 0) {
|
||||||
|
filteredData[dataset] = filteredItems;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasChildren(chartData, itemId) {
|
||||||
|
return Object.values(chartData).some(items =>
|
||||||
|
items.some(item => item.parent === itemId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderBarGraph(container, attrs) {
|
function renderBarGraph(container, attrs) {
|
||||||
// Clear any existing content
|
|
||||||
const graphContainer = container.querySelector('.bar-graph-container');
|
const graphContainer = container.querySelector('.bar-graph-container');
|
||||||
if (!graphContainer) return;
|
if (!graphContainer) return;
|
||||||
graphContainer.innerHTML = '';
|
graphContainer.innerHTML = '';
|
||||||
|
|
||||||
// Get the actual width of the container
|
|
||||||
const containerWidth = graphContainer.clientWidth;
|
const containerWidth = graphContainer.clientWidth;
|
||||||
|
|
||||||
// Set up dimensions
|
|
||||||
const margin = { top: 40, right: 20, bottom: 60, left: 60 };
|
const margin = { top: 40, right: 20, bottom: 60, left: 60 };
|
||||||
const width = containerWidth - margin.left - margin.right;
|
const width = containerWidth - margin.left - margin.right;
|
||||||
const height = attrs.chartHeight - margin.top - margin.bottom;
|
const height = attrs.chartHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
// Get data for current level
|
||||||
|
const currentData = filterDataByParent(attrs.chartData, attrs.selectedParentId);
|
||||||
|
|
||||||
// Convert data to array format
|
// Convert data to array format
|
||||||
const chartDataArray = [];
|
const chartDataArray = [];
|
||||||
if (attrs.chartData && typeof attrs.chartData === 'object') {
|
Object.entries(currentData).forEach(([dataset, items]) => {
|
||||||
Object.entries(attrs.chartData).forEach(([dataset, items]) => {
|
items.forEach(item => {
|
||||||
if (Array.isArray(items)) {
|
chartDataArray.push({
|
||||||
items.forEach(item => {
|
id: item.id,
|
||||||
chartDataArray.push({
|
dataset,
|
||||||
dataset,
|
label: item.label || '',
|
||||||
label: item.label || '',
|
value: parseFloat(item.value) || 0,
|
||||||
value: parseFloat(item.value) || 0,
|
color: item.color || attrs.barColor,
|
||||||
color: item.color || attrs.barColor
|
hasChildren: hasChildren(attrs.chartData, item.id)
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
// Create SVG
|
// Create SVG
|
||||||
const svg = d3.select(graphContainer)
|
const svg = d3.select(graphContainer)
|
||||||
@ -105,7 +117,7 @@ function renderBarGraph(container, attrs) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add X grid
|
// Add grids
|
||||||
if (attrs.showGridX) {
|
if (attrs.showGridX) {
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('class', 'grid x-grid')
|
.attr('class', 'grid x-grid')
|
||||||
@ -117,7 +129,6 @@ function renderBarGraph(container, attrs) {
|
|||||||
.tickFormat(''));
|
.tickFormat(''));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Y grid
|
|
||||||
if (attrs.showGridY) {
|
if (attrs.showGridY) {
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('class', 'grid y-grid')
|
.attr('class', 'grid y-grid')
|
||||||
@ -129,7 +140,7 @@ function renderBarGraph(container, attrs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add bars
|
// Add bars
|
||||||
g.selectAll('rect')
|
const bars = g.selectAll('rect')
|
||||||
.data(chartDataArray)
|
.data(chartDataArray)
|
||||||
.enter()
|
.enter()
|
||||||
.append('rect')
|
.append('rect')
|
||||||
@ -138,7 +149,45 @@ function renderBarGraph(container, attrs) {
|
|||||||
.attr('width', xScale.bandwidth())
|
.attr('width', xScale.bandwidth())
|
||||||
.attr('height', d => height - yScale(d.value))
|
.attr('height', d => height - yScale(d.value))
|
||||||
.attr('fill', d => getColor(d))
|
.attr('fill', d => getColor(d))
|
||||||
.style('opacity', attrs.barOpacity);
|
.style('opacity', attrs.barOpacity)
|
||||||
|
.style('cursor', d => d.hasChildren ? 'pointer' : 'default');
|
||||||
|
|
||||||
|
// Add click handlers for bars
|
||||||
|
bars.on('click', function(event, d) {
|
||||||
|
if (d.hasChildren) {
|
||||||
|
const blockData = window.lcpBarGraphData[container.id];
|
||||||
|
if (blockData) {
|
||||||
|
blockData.attributes.selectedParentId = d.id;
|
||||||
|
renderBarGraph(container, blockData.attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add back button if not at root level
|
||||||
|
if (attrs.selectedParentId !== null) {
|
||||||
|
svg.append('text')
|
||||||
|
.attr('x', margin.left)
|
||||||
|
.attr('y', margin.top / 2)
|
||||||
|
.attr('class', 'back-button')
|
||||||
|
.style('cursor', 'pointer')
|
||||||
|
.style('font-weight', 'bold')
|
||||||
|
.text('← Back')
|
||||||
|
.on('click', function() {
|
||||||
|
const blockData = window.lcpBarGraphData[container.id];
|
||||||
|
if (blockData) {
|
||||||
|
// Find current item's parent
|
||||||
|
let parentParentId = null;
|
||||||
|
Object.values(attrs.chartData).forEach(items => {
|
||||||
|
const currentItem = items.find(item => item.id === attrs.selectedParentId);
|
||||||
|
if (currentItem) {
|
||||||
|
parentParentId = currentItem.parent;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
blockData.attributes.selectedParentId = parentParentId;
|
||||||
|
renderBarGraph(container, blockData.attributes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add bar values
|
// Add bar values
|
||||||
if (attrs.showBarValues) {
|
if (attrs.showBarValues) {
|
||||||
@ -153,7 +202,7 @@ function renderBarGraph(container, attrs) {
|
|||||||
.text(d => d.value);
|
.text(d => d.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add X axis
|
// Add axes
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('class', 'x-axis')
|
.attr('class', 'x-axis')
|
||||||
.attr('transform', `translate(0,${height})`)
|
.attr('transform', `translate(0,${height})`)
|
||||||
@ -164,7 +213,6 @@ function renderBarGraph(container, attrs) {
|
|||||||
.attr('dy', '.15em')
|
.attr('dy', '.15em')
|
||||||
.attr('transform', 'rotate(-45)');
|
.attr('transform', 'rotate(-45)');
|
||||||
|
|
||||||
// Add Y axis
|
|
||||||
g.append('g')
|
g.append('g')
|
||||||
.attr('class', 'y-axis')
|
.attr('class', 'y-axis')
|
||||||
.call(d3.axisLeft(yScale));
|
.call(d3.axisLeft(yScale));
|
||||||
@ -179,7 +227,7 @@ function renderBarGraph(container, attrs) {
|
|||||||
.text(attrs.chartTitle);
|
.text(attrs.chartTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add X axis label
|
// Add axis labels
|
||||||
if (attrs.xAxisLabel) {
|
if (attrs.xAxisLabel) {
|
||||||
svg.append('text')
|
svg.append('text')
|
||||||
.attr('x', containerWidth / 2)
|
.attr('x', containerWidth / 2)
|
||||||
@ -188,7 +236,6 @@ function renderBarGraph(container, attrs) {
|
|||||||
.text(attrs.xAxisLabel);
|
.text(attrs.xAxisLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Y axis label
|
|
||||||
if (attrs.yAxisLabel) {
|
if (attrs.yAxisLabel) {
|
||||||
svg.append('text')
|
svg.append('text')
|
||||||
.attr('transform', 'rotate(-90)')
|
.attr('transform', 'rotate(-90)')
|
||||||
@ -202,7 +249,6 @@ function renderBarGraph(container, attrs) {
|
|||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
const newWidth = graphContainer.clientWidth;
|
const newWidth = graphContainer.clientWidth;
|
||||||
if (newWidth !== containerWidth) {
|
if (newWidth !== containerWidth) {
|
||||||
// Clear and redraw
|
|
||||||
graphContainer.innerHTML = '';
|
graphContainer.innerHTML = '';
|
||||||
renderBarGraph(container, attrs);
|
renderBarGraph(container, attrs);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user