From 30d8e1de512db8b31be0769fd6c760798f6016b9 Mon Sep 17 00:00:00 2001 From: weinStag Date: Sun, 6 Jul 2025 08:57:39 -0300 Subject: [PATCH 01/23] Add ChartVisualization component with Chart.js integration - Support for Bar, Line, and Pie charts - Responsive design with gradients and animations - Chart.js integration with all required components - Proper data processing for numeric values --- .../ChartVisualization.react.js | 532 ++++++++++++++++++ .../ChartVisualization.scss | 199 +++++++ 2 files changed, 731 insertions(+) create mode 100644 src/components/ChartVisualization/ChartVisualization.react.js create mode 100644 src/components/ChartVisualization/ChartVisualization.scss diff --git a/src/components/ChartVisualization/ChartVisualization.react.js b/src/components/ChartVisualization/ChartVisualization.react.js new file mode 100644 index 000000000..2aa21915e --- /dev/null +++ b/src/components/ChartVisualization/ChartVisualization.react.js @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +import React, { useMemo, useState } from 'react'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + LineElement, + PointElement, + ArcElement, + Title, + Tooltip, + Legend, + TimeScale, +} from 'chart.js'; +import { Bar, Line, Pie } from 'react-chartjs-2'; +import 'chartjs-adapter-date-fns'; +import styles from './ChartVisualization.scss'; + +// Registrar os componentes necessários do Chart.js +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + LineElement, + PointElement, + ArcElement, + Title, + Tooltip, + Legend, + TimeScale +); + +const ChartVisualization = ({ + selectedData, + selectedCells, + data, + order +}) => { + const [chartType, setChartType] = useState('bar'); + + // Processar dados selecionados para determinar o tipo de visualização + const chartData = useMemo(() => { + if (!selectedData || selectedData.length === 0 || !selectedCells) { + return null; + } + + const { rowStart, rowEnd, colStart, colEnd } = selectedCells; + + // Verificar se temos dados válidos + if (rowStart === -1 || colStart === -1) { + return null; + } + + // Determinar se é time series de forma mais rigorosa + // Time series só se: primeira coluna é data E temos múltiplas colunas E a primeira coluna É REALMENTE data + const firstColumnName = order[colStart]?.name; + let isTimeSeries = false; + + // Só considerar time series se temos múltiplas colunas E a primeira coluna é explicitamente data/datetime + if (colEnd > colStart && firstColumnName) { + // Verificar se o nome da coluna sugere data + const isDateColumn = /^(date|time|created|updated|when|at)$/i.test(firstColumnName) || + firstColumnName.toLowerCase().includes('date') || + firstColumnName.toLowerCase().includes('time'); + + if (isDateColumn) { + // Verificar se a primeira coluna contém realmente datas válidas + let dateCount = 0; + const totalRows = Math.min(3, rowEnd - rowStart + 1); // Verificar até 3 linhas + + for (let rowIndex = rowStart; rowIndex < rowStart + totalRows; rowIndex++) { + const value = data[rowIndex]?.attributes[firstColumnName]; + if (value instanceof Date || + (typeof value === 'string' && !isNaN(Date.parse(value)) && new Date(value).getFullYear() > 1900)) { + dateCount++; + } + } + + isTimeSeries = dateCount >= totalRows * 0.8; // 80% devem ser datas válidas + } + } + + // Forçar number series se não temos evidências claras de time series + isTimeSeries = false; // TEMPORÁRIO: forçar number series para debug + + if (isTimeSeries && colEnd > colStart) { + // Time Series: primeira coluna é data, outras são números + const datasets = []; + + // Criar um dataset para cada coluna numérica + for (let colIndex = colStart + 1; colIndex <= colEnd; colIndex++) { + const columnName = order[colIndex]?.name; + const dataPoints = []; + + for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { + const timeValue = data[rowIndex]?.attributes[firstColumnName]; + const numericValue = data[rowIndex]?.attributes[columnName]; + + if (timeValue && typeof numericValue === 'number' && !isNaN(numericValue)) { + dataPoints.push({ + x: new Date(timeValue), + y: numericValue + }); + } + } + + if (dataPoints.length > 0) { + datasets.push({ + label: columnName, + data: dataPoints, + borderColor: `hsl(${(colIndex - colStart) * 60}, 70%, 50%)`, + backgroundColor: `hsla(${(colIndex - colStart) * 60}, 70%, 50%, 0.1)`, + tension: 0.1 + }); + } + } + + return { + type: 'timeSeries', + datasets, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + type: 'time', + time: { + displayFormats: { + day: 'MMM dd', + hour: 'HH:mm' + } + }, + title: { + display: true, + text: firstColumnName + } + }, + y: { + title: { + display: true, + text: 'Value' + } + } + }, + plugins: { + title: { + display: true, + text: 'Time Series Visualization' + }, + legend: { + display: datasets.length > 1 + } + } + } + }; + } else { + // Number Series: apenas valores numéricos + const labels = []; + const dataPoints = []; + + // Se múltiplas colunas, criar rótulos baseados nos nomes das colunas + if (colEnd > colStart) { + for (let colIndex = colStart; colIndex <= colEnd; colIndex++) { + const columnName = order[colIndex]?.name; + if (!columnName) { + continue; + } + + labels.push(columnName); + + // Calcular média dos valores desta coluna + let sum = 0; + let count = 0; + for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { + const value = data[rowIndex]?.attributes[columnName]; + if (typeof value === 'number' && !isNaN(value)) { + sum += value; + count++; + } + } + dataPoints.push(count > 0 ? sum / count : 0); + } + } else { + // Única coluna: usar índices das linhas como rótulos + const columnName = order[colStart]?.name; + if (columnName) { + for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { + labels.push(`Row ${rowIndex + 1}`); + const value = data[rowIndex]?.attributes[columnName]; + dataPoints.push(typeof value === 'number' && !isNaN(value) ? value : 0); + } + } + } + + if (labels.length === 0 || dataPoints.length === 0) { + return null; + } + + return { + type: 'numberSeries', + data: { + labels, + datasets: [{ + label: 'Selected Values', + data: dataPoints, + backgroundColor: chartType === 'bar' + ? dataPoints.map((_, index) => `hsla(${index * 360 / dataPoints.length}, 70%, 60%, 0.8)`) + : 'rgba(22, 156, 238, 0.7)', + borderColor: chartType === 'bar' + ? dataPoints.map((_, index) => `hsl(${index * 360 / dataPoints.length}, 70%, 50%)`) + : 'rgba(22, 156, 238, 1)', + borderWidth: 2, + borderRadius: chartType === 'bar' ? 4 : 0, + tension: chartType === 'line' ? 0.4 : 0 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + plugins: { + title: { + display: true, + text: 'Selected Data Visualization', + font: { + size: 16, + weight: 'bold' + }, + color: '#333' + }, + legend: { + display: false + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: '#169cee', + borderWidth: 1 + } + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Value', + font: { + size: 14, + weight: 'bold' + }, + color: '#555' + }, + grid: { + color: 'rgba(0, 0, 0, 0.1)' + }, + ticks: { + color: '#666' + } + }, + x: { + title: { + display: true, + text: 'Categories', + font: { + size: 14, + weight: 'bold' + }, + color: '#555' + }, + grid: { + color: 'rgba(0, 0, 0, 0.1)' + }, + ticks: { + color: '#666' + } + } + } + } + }; + } + }, [selectedData, selectedCells, data, order]); + + if (!chartData) { + return ( +
+

Select multiple cells to visualize data

+
+ ); + } + + const renderChart = () => { + if (chartData.type === 'timeSeries') { + return ( + + ); + } else { + // Para number series, suportar bar, line e pie charts + if (chartType === 'pie') { + // Para pie chart, verificar se temos dados válidos + const values = chartData.data.datasets[0].data; + const labels = chartData.data.labels; + + // Filtrar valores válidos (> 0) para pie chart + const validData = []; + const validLabels = []; + const validColors = []; + + values.forEach((value, index) => { + if (value && value > 0) { + validData.push(value); + validLabels.push(labels[index]); + validColors.push(`hsl(${index * 360 / values.length}, 75%, 65%)`); + } + }); + + if (validData.length === 0) { + return

No positive values for pie chart

; + } + + const pieData = { + labels: validLabels, + datasets: [{ + label: 'Values', + data: validData, + backgroundColor: validColors, + borderColor: validColors.map(color => color.replace('60%', '40%')), + borderWidth: 1 + }] + }; + + const pieOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: 'Data Distribution', + font: { + size: 16, + weight: 'bold' + }, + color: '#333' + }, + legend: { + display: true, + position: 'right', + labels: { + padding: 20, + usePointStyle: true, + font: { + size: 12 + } + } + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: '#169cee', + borderWidth: 1, + callbacks: { + label: function(context) { + const label = context.label || ''; + const value = context.parsed; + const total = context.dataset.data.reduce((a, b) => a + b, 0); + const percentage = Math.round((value / total) * 100); + return `${label}: ${value} (${percentage}%)`; + } + } + } + } + }; + + return ( + + ); + } else { + // Bar ou Line Chart + const ChartComponent = chartType === 'bar' ? Bar : Line; + + // Melhorar as opções para dimensionamento correto + const enhancedOptions = { + ...chartData.options, + responsive: true, + maintainAspectRatio: false, + aspectRatio: 1.6, + layout: { + padding: { + top: 20, + right: 20, + bottom: 20, + left: 20 + } + }, + elements: { + bar: { + borderRadius: 4, + borderWidth: 0 + }, + line: { + borderWidth: 3, + tension: 0.4 + }, + point: { + radius: 5, + borderWidth: 2, + hoverRadius: 7 + } + }, + plugins: { + ...chartData.options.plugins, + legend: { + display: false + }, + title: { + display: true, + text: 'Selected Data Visualization', + position: 'top', + align: 'center', + font: { + size: 16, + weight: 'bold' + }, + color: '#333', + padding: { + top: 10, + bottom: 20 + } + }, + tooltip: { + enabled: true, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: '#169cee', + borderWidth: 1, + cornerRadius: 6, + displayColors: true + } + }, + scales: { + ...chartData.options.scales, + x: { + ...chartData.options.scales.x, + grid: { + display: true, + color: 'rgba(0, 0, 0, 0.1)' + }, + ticks: { + maxRotation: 45, + minRotation: 0, + font: { + size: 12 + } + } + }, + y: { + ...chartData.options.scales.y, + grid: { + display: true, + color: 'rgba(0, 0, 0, 0.1)' + }, + ticks: { + font: { + size: 12 + } + } + } + } + }; + + return ( + + ); + } + } + }; + + return ( +
+
+

+ 📊 Data Visualization ({selectedData.length} values selected) +

+
+
+ {chartData.type === 'numberSeries' && ( +
+ + +
+ )} +
+ {chartData.type === 'timeSeries' ? 'Time Series' : 'Number Series'} | + {selectedData.length} values selected +
+
+
+ {renderChart()} +
+
+ ); +}; + +export default ChartVisualization; diff --git a/src/components/ChartVisualization/ChartVisualization.scss b/src/components/ChartVisualization/ChartVisualization.scss new file mode 100644 index 000000000..7e43dca86 --- /dev/null +++ b/src/components/ChartVisualization/ChartVisualization.scss @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2016-present, Parse, LLC + * All rights reserved. + * + * This source code is licensed under the license found in the LICENSE file in + * the root directory of this source tree. + */ +@import 'stylesheets/globals.scss'; + +.chartVisualization { + display: flex; + flex-direction: column; + height: 100%; + background: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: hidden; + min-width: 600px; + min-height: 500px; + position: relative; + z-index: 1000; +} + +.chartHeader { + background: linear-gradient(135deg, #169cee, #1976d2); + color: white; + padding: 20px 24px; + border-bottom: none; + box-shadow: 0 2px 8px rgba(22, 156, 238, 0.2); + + .chartTitle { + margin: 0; + font-size: 18px; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + + svg { + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); + } + } +} + +.chartControls { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + background: #f8f9fa; + border-bottom: 1px solid #e3e3ea; +} + +.chartTypeSelector { + display: flex; + align-items: center; + gap: 12px; + + label { + font-weight: 500; + color: #555; + font-size: 14px; + } +} + +.select { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: white; + font-size: 14px; + color: #333; + cursor: pointer; + transition: border-color 0.2s ease; + + &:focus { + outline: none; + border-color: #169cee; + box-shadow: 0 0 0 2px rgba(22, 156, 238, 0.1); + } + + &:hover { + border-color: #169cee; + } +} + +.chartInfo { + color: #666; + font-size: 13px; + font-weight: 500; + background: white; + padding: 6px 12px; + border-radius: 4px; + border: 1px solid #e0e0e0; +} + +.chartContainer { + flex: 1; + position: relative; + min-height: 450px; + height: calc(100vh - 280px); + width: 100%; + padding: 20px; + display: flex; + align-items: center; + justify-content: center; + background: white; + + > div { + width: 100% !important; + height: 100% !important; + margin: 0 0; + display: flex !important; + align-items: center !important; + justify-content: center !important; + position: relative !important; + } + + canvas { + width: 100% !important; + height: 100% !important; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + margin: 0 0; + position: relative !important; + } +} + +.noData { + display: flex; + align-items: center; + justify-content: center; + height: 200px; + color: #999; + font-style: italic; + background: #f9f9f9; + border-radius: 8px; + margin: 20px; + + p { + margin: 0; + font-size: 16px; + } +} + +// Responsividade +@media (max-width: 1200px) { + .chartContainer { + max-width: 100%; + padding: 20px; + + > div { + max-width: 100% !important; + min-width: 300px !important; + } + } +} + +@media (max-width: 768px) { + .chartContainer { + min-height: 350px; + padding: 15px; + height: calc(100vh - 250px); + + > div { + min-height: 300px !important; + min-width: 280px !important; + } + } + + .chartHeader { + padding: 16px 20px; + + .chartTitle { + font-size: 16px; + gap: 8px; + } + } + + .chartControls { + padding: 12px 16px; + flex-direction: column; + gap: 12px; + align-items: flex-start; + } +} + +@media (max-width: 480px) { + .chartContainer { + padding: 10px; + min-height: 300px; + + > div { + min-height: 250px !important; + min-width: 250px !important; + } + } +} From ea8b48c87cdaee59429f2cccc4a5bd560b89958d Mon Sep 17 00:00:00 2001 From: weinStag Date: Sun, 6 Jul 2025 08:57:39 -0300 Subject: [PATCH 02/23] feat: add chart toggle button in Toolbar component --- src/components/Toolbar/Toolbar.react.js | 29 ++++-- src/components/Toolbar/Toolbar.scss | 120 ++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 14 deletions(-) diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js index e5e664253..e69191455 100644 --- a/src/components/Toolbar/Toolbar.react.js +++ b/src/components/Toolbar/Toolbar.react.js @@ -38,7 +38,7 @@ const Stats = ({ data, classwiseCloudFunctions, className, appId, appName }) => }, { type: 'p99', - label: 'P99', + label: 'P999', getValue: data => { const sorted = data.sort((a, b) => a - b); return sorted[Math.floor(sorted.length * 0.99)]; @@ -125,6 +125,7 @@ const Stats = ({ data, classwiseCloudFunctions, className, appId, appName }) => const Toolbar = props => { const action = useNavigationType(); const navigate = useNavigate(); + let backButton; if (props.relation || (props.filters && props.filters.size && action !== NavigationType.Pop)) { backButton = ( @@ -146,13 +147,25 @@ const Toolbar = props => { {props?.selectedData?.length ? ( - +
+ + {props?.selectedData?.length > 1 && ( + + )} +
) : null}
{props.children}
{props.classwiseCloudFunctions && diff --git a/src/components/Toolbar/Toolbar.scss b/src/components/Toolbar/Toolbar.scss index 88f726117..7bb535686 100644 --- a/src/components/Toolbar/Toolbar.scss +++ b/src/components/Toolbar/Toolbar.scss @@ -87,16 +87,22 @@ body:global(.expanded) { } .stats { - position: absolute; - right: 20px; - bottom: 10px; background: $blue; - border-radius: 3px; - padding: 2px 6px; + border-radius: 6px; + padding: 8px 12px; font-size: 14px; + font-weight: 500; color: white; - box-shadow: none; + box-shadow: 0 2px 8px rgba(22, 156, 238, 0.3); border: none; + transition: all 0.3s ease; + z-index: 5; + + &:hover { + background: #1976d2; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(22, 156, 238, 0.4); + } } .stats_popover_container { @@ -153,3 +159,105 @@ body:global(.expanded) { } } } + +.dataControls { + position: absolute; + right: 20px; + bottom: 10px; // Movido para a mesma linha do stats + display: flex; + align-items: center; + gap: 16px; + z-index: 10; +} + +.chartButton { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: linear-gradient(135deg, #169cee, #1976d2); + border: none; + border-radius: 6px; + color: white; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(22, 156, 238, 0.3); + + &:hover { + background: linear-gradient(135deg, #1976d2, #1565c0); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(22, 156, 238, 0.4); + } + + &:active { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(22, 156, 238, 0.3); + } + + svg { + flex-shrink: 0; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)); + } +} + +// Responsividade para o botão do gráfico +@media (max-width: 968px) { + .stats { + right: 120px; // Menor distância em telas médias + } +} + +@media (max-width: 768px) { + .dataControls { + right: 15px; + bottom: 10px; + gap: 12px; + } + + .chartButton { + padding: 6px 12px; + font-size: 12px; + gap: 6px; + + svg { + width: 14px; + height: 14px; + } + } + + .stats { + right: 110px; // Ajustado para telas menores + bottom: 10px; + padding: 6px 10px; + font-size: 13px; + } +} + +@media (max-width: 480px) { + .dataControls { + right: 10px; + bottom: 10px; + flex-direction: row; + gap: 8px; + align-items: center; + } + + .chartButton { + padding: 5px 8px; + font-size: 11px; + + // Em telas muito pequenas, mostra apenas o ícone + span { + display: none; + } + } + + .stats { + right: 80px; // Posição ajustada para mobile + bottom: 10px; + font-size: 12px; + padding: 5px 8px; + } +} From 9eb06bbf5378ee9813539171d51848f0cf4e14a8 Mon Sep 17 00:00:00 2001 From: weinStag Date: Sun, 6 Jul 2025 08:57:40 -0300 Subject: [PATCH 03/23] feat: integrate resizable chart panel in DataBrowser - Add ResizableBox for chart panel - Fixed positioning to prevent UI collapse - Toggle functionality for chart visibility - Proper state management for panel width --- .../Data/Browser/BrowserToolbar.react.js | 5 +++ .../Data/Browser/DataBrowser.react.js | 41 +++++++++++++++-- src/dashboard/Data/Browser/Databrowser.scss | 45 +++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index cfd1c5108..4a1bceb59 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -82,6 +82,9 @@ const BrowserToolbar = ({ classwiseCloudFunctions, appId, appName, + + toggleChartPanel, + isChartPanelVisible, }) => { const selectionLength = Object.keys(selection).length; const isPendingEditCloneRows = editCloneRows && editCloneRows.length > 0; @@ -280,6 +283,8 @@ const BrowserToolbar = ({ classwiseCloudFunctions={classwiseCloudFunctions} appId={appId} appName={appName} + toggleChartPanel={toggleChartPanel} + isChartPanelVisible={isChartPanelVisible} > {onAddRow && ( diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index 50aeef95a..444a65c9a 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -15,6 +15,7 @@ import { ResizableBox } from 'react-resizable'; import styles from './Databrowser.scss'; import AggregationPanel from '../../../components/AggregationPanel/AggregationPanel'; +import ChartVisualization from '../../../components/ChartVisualization/ChartVisualization.react'; /** * DataBrowser renders the browser toolbar and data table @@ -49,6 +50,8 @@ export default class DataBrowser extends React.Component { isResizing: false, maxWidth: window.innerWidth - 300, showAggregatedData: true, + isChartPanelVisible: false, + chartPanelWidth: 400, }; this.handleResizeDiv = this.handleResizeDiv.bind(this); @@ -67,6 +70,7 @@ export default class DataBrowser extends React.Component { this.setSelectedObjectId = this.setSelectedObjectId.bind(this); this.setContextMenu = this.setContextMenu.bind(this); this.handleCellClick = this.handleCellClick.bind(this); + this.toggleChartPanelVisibility = this.toggleChartPanelVisibility.bind(this); this.saveOrderTimeout = null; } @@ -210,6 +214,12 @@ export default class DataBrowser extends React.Component { } } + toggleChartPanelVisibility() { + this.setState(prevState => ({ + isChartPanelVisible: !prevState.isChartPanelVisible + })); + } + getAllClassesSchema(schema) { const allClasses = Object.keys(schema.data.get('classes').toObject()); const schemaSimplifiedData = {}; @@ -610,9 +620,7 @@ export default class DataBrowser extends React.Component { firstSelectedCell: clickedCellKey, }); } - } - - render() { + } render() { const { className, count, @@ -682,6 +690,31 @@ export default class DataBrowser extends React.Component { )} + {this.state.isChartPanelVisible && this.state.selectedData.length > 1 && ( + this.setState({ isResizing: true })} + onResizeStop={(event, { size }) => this.setState({ + isResizing: false, + chartPanelWidth: size.width + })} + onResize={(event, { size }) => this.setState({ chartPanelWidth: size.width })} + resizeHandles={['w']} + className={styles.chartPanel} + > +
+ +
+
+ )} Date: Sun, 6 Jul 2025 09:19:09 -0300 Subject: [PATCH 04/23] fix: numeric series for multiple columns --- .../ChartVisualization.react.js | 220 ++++++++++-------- src/components/Toolbar/Toolbar.react.js | 2 +- 2 files changed, 125 insertions(+), 97 deletions(-) diff --git a/src/components/ChartVisualization/ChartVisualization.react.js b/src/components/ChartVisualization/ChartVisualization.react.js index 2aa21915e..6f9ed363e 100644 --- a/src/components/ChartVisualization/ChartVisualization.react.js +++ b/src/components/ChartVisualization/ChartVisualization.react.js @@ -165,30 +165,93 @@ const ChartVisualization = ({ const labels = []; const dataPoints = []; - // Se múltiplas colunas, criar rótulos baseados nos nomes das colunas + // Se múltiplas colunas, criar datasets separados para cada coluna if (colEnd > colStart) { + // CORREÇÃO: Em vez de calcular médias, mostrar todos os valores + const datasets = []; + for (let colIndex = colStart; colIndex <= colEnd; colIndex++) { const columnName = order[colIndex]?.name; - if (!columnName) { - continue; - } + if (!columnName) continue; - labels.push(columnName); + // Coletar todos os valores desta coluna + const columnValues = []; + const columnLabels = []; - // Calcular média dos valores desta coluna - let sum = 0; - let count = 0; for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { const value = data[rowIndex]?.attributes[columnName]; if (typeof value === 'number' && !isNaN(value)) { - sum += value; - count++; + columnValues.push(value); + columnLabels.push(`Row ${rowIndex + 1}`); } } - dataPoints.push(count > 0 ? sum / count : 0); + + if (columnValues.length > 0) { + datasets.push({ + label: columnName, + data: columnValues, + backgroundColor: `hsla(${(colIndex - colStart) * 60}, 70%, 60%, 0.8)`, + borderColor: `hsl(${(colIndex - colStart) * 60}, 70%, 50%)`, + borderWidth: 2, + borderRadius: chartType === 'bar' ? 4 : 0, + tension: chartType === 'line' ? 0.4 : 0 + }); + } } + + // Usar os labels da primeira coluna (todas devem ter o mesmo número de linhas) + for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { + labels.push(`Row ${rowIndex + 1}`); + } + + return { + type: 'numberSeries', + data: { + labels, + datasets + }, + options: { + // ...manter as opções existentes... + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, + }, + plugins: { + title: { + display: true, + text: 'Selected Data Visualization', + font: { size: 16, weight: 'bold' }, + color: '#333' + }, + legend: { + display: datasets.length > 1 // Mostrar legenda se múltiplas colunas + }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: '#169cee', + borderWidth: 1 + } + }, + scales: { + y: { + beginAtZero: true, + title: { display: true, text: 'Value', font: { size: 14, weight: 'bold' }, color: '#555' }, + grid: { color: 'rgba(0, 0, 0, 0.1)' }, + ticks: { color: '#666' } + }, + x: { + title: { display: true, text: 'Categories', font: { size: 14, weight: 'bold' }, color: '#555' }, + grid: { color: 'rgba(0, 0, 0, 0.1)' }, + ticks: { color: '#666' } + } + } + } + }; } else { - // Única coluna: usar índices das linhas como rótulos + // Única coluna: usar índices das linhas como rótulos (MANTER COMO ESTÁ) const columnName = order[colStart]?.name; if (columnName) { for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) { @@ -197,107 +260,72 @@ const ChartVisualization = ({ dataPoints.push(typeof value === 'number' && !isNaN(value) ? value : 0); } } - } - if (labels.length === 0 || dataPoints.length === 0) { - return null; - } + if (labels.length === 0 || dataPoints.length === 0) { + return null; + } - return { - type: 'numberSeries', - data: { - labels, - datasets: [{ - label: 'Selected Values', - data: dataPoints, - backgroundColor: chartType === 'bar' - ? dataPoints.map((_, index) => `hsla(${index * 360 / dataPoints.length}, 70%, 60%, 0.8)`) - : 'rgba(22, 156, 238, 0.7)', - borderColor: chartType === 'bar' - ? dataPoints.map((_, index) => `hsl(${index * 360 / dataPoints.length}, 70%, 50%)`) - : 'rgba(22, 156, 238, 1)', - borderWidth: 2, - borderRadius: chartType === 'bar' ? 4 : 0, - tension: chartType === 'line' ? 0.4 : 0 - }] - }, - options: { - responsive: true, - maintainAspectRatio: false, - interaction: { - intersect: false, + return { + type: 'numberSeries', + data: { + labels, + datasets: [{ + label: 'Selected Values', + data: dataPoints, + backgroundColor: chartType === 'bar' + ? dataPoints.map((_, index) => `hsla(${index * 360 / dataPoints.length}, 70%, 60%, 0.8)`) + : 'rgba(22, 156, 238, 0.7)', + borderColor: chartType === 'bar' + ? dataPoints.map((_, index) => `hsl(${index * 360 / dataPoints.length}, 70%, 50%)`) + : 'rgba(22, 156, 238, 1)', + borderWidth: 2, + borderRadius: chartType === 'bar' ? 4 : 0, + tension: chartType === 'line' ? 0.4 : 0 + }] }, - plugins: { - title: { - display: true, - text: 'Selected Data Visualization', - font: { - size: 16, - weight: 'bold' - }, - color: '#333' - }, - legend: { - display: false + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { + intersect: false, }, - tooltip: { - backgroundColor: 'rgba(0, 0, 0, 0.8)', - titleColor: '#fff', - bodyColor: '#fff', - borderColor: '#169cee', - borderWidth: 1 - } - }, - scales: { - y: { - beginAtZero: true, + plugins: { title: { display: true, - text: 'Value', - font: { - size: 14, - weight: 'bold' - }, - color: '#555' + text: 'Selected Data Visualization', + font: { size: 16, weight: 'bold' }, + color: '#333' }, - grid: { - color: 'rgba(0, 0, 0, 0.1)' + legend: { + display: false // Uma coluna não precisa de legenda }, - ticks: { - color: '#666' + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#fff', + bodyColor: '#fff', + borderColor: '#169cee', + borderWidth: 1 } }, - x: { - title: { - display: true, - text: 'Categories', - font: { - size: 14, - weight: 'bold' - }, - color: '#555' - }, - grid: { - color: 'rgba(0, 0, 0, 0.1)' + scales: { + y: { + beginAtZero: true, + title: { display: true, text: 'Value', font: { size: 14, weight: 'bold' }, color: '#555' }, + grid: { color: 'rgba(0, 0, 0, 0.1)' }, + ticks: { color: '#666' } }, - ticks: { - color: '#666' + x: { + title: { display: true, text: 'Categories', font: { size: 14, weight: 'bold' }, color: '#555' }, + grid: { color: 'rgba(0, 0, 0, 0.1)' }, + ticks: { color: '#666' } } } } - } - }; + }; + } } }, [selectedData, selectedCells, data, order]); - if (!chartData) { - return ( -
-

Select multiple cells to visualize data

-
- ); - } - const renderChart = () => { if (chartData.type === 'timeSeries') { return ( diff --git a/src/components/Toolbar/Toolbar.react.js b/src/components/Toolbar/Toolbar.react.js index e69191455..2d1ebf92c 100644 --- a/src/components/Toolbar/Toolbar.react.js +++ b/src/components/Toolbar/Toolbar.react.js @@ -162,7 +162,7 @@ const Toolbar = props => { title={props.isChartPanelVisible ? 'Hide Data Visualization' : 'Show Data Visualization'} > - {props.isChartPanelVisible ? 'Hide Chart' : 'Show Chart B'} + {props.isChartPanelVisible ? 'Hide Chart' : 'Show Chart D'} )} From e01af27aeede05feb49852ea6f24abb1dd2d2fb5 Mon Sep 17 00:00:00 2001 From: weinStag Date: Sun, 6 Jul 2025 09:37:47 -0300 Subject: [PATCH 05/23] fix: overflow-x deprecated --- src/dashboard/Data/Browser/BrowserTable.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 125b299cc..6f87206af 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -540,7 +540,7 @@ export default class BrowserTable extends React.Component { id="browser-table" style={{ right: rightValue, - 'overflow-x': this.props.isResizing ? 'hidden' : 'auto', + 'overflowX': this.props.isResizing ? 'hidden' : 'auto', }} > Date: Sun, 6 Jul 2025 09:56:32 -0300 Subject: [PATCH 06/23] fix: menu item array error --- src/dashboard/Data/Browser/BrowserToolbar.react.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dashboard/Data/Browser/BrowserToolbar.react.js b/src/dashboard/Data/Browser/BrowserToolbar.react.js index 4a1bceb59..ec722ccde 100644 --- a/src/dashboard/Data/Browser/BrowserToolbar.react.js +++ b/src/dashboard/Data/Browser/BrowserToolbar.react.js @@ -430,7 +430,8 @@ const BrowserToolbar = ({ )} {enableSecurityDialog ?
: