diff --git a/DashAI/front/src/components/DatasetVisualization.jsx b/DashAI/front/src/components/DatasetVisualization.jsx
index be0f80a4d..cd43850db 100644
--- a/DashAI/front/src/components/DatasetVisualization.jsx
+++ b/DashAI/front/src/components/DatasetVisualization.jsx
@@ -60,6 +60,12 @@ export default function DatasetVisualization({
datasetsContext?.setDatasetTab ??
modelsContext?.setDatasetTab ??
(() => {});
+ const scrollToColumn =
+ datasetsContext?.scrollToColumn ?? modelsContext?.scrollToColumn ?? null;
+ const setScrollToColumn =
+ datasetsContext?.setScrollToColumn ??
+ modelsContext?.setScrollToColumn ??
+ (() => {});
const [datasetInfo, setDatasetInfo] = useState(null);
const [columnTypes, setColumnTypes] = useState({});
@@ -436,14 +442,24 @@ export default function DatasetVisualization({
/>
)}
{tab === 1 && (
-
+
)}
{tab === 2 && (
)}
- {tab === 3 && }
+ {tab === 3 && (
+
+ )}
{tab === 4 && (
{
const [uploadDataloader, setUploadDataloader] = useState(null);
const [datasetInfo, setDatasetInfo] = useState(null);
const [datasetTab, setDatasetTab] = useState(0);
+ const [scrollToColumn, setScrollToColumn] = useState(null);
useEffect(() => {
fetchNotebooks();
@@ -102,6 +103,8 @@ export const DatasetsAndNotebooksProvider = ({ children }) => {
setDatasetInfo,
datasetTab,
setDatasetTab,
+ scrollToColumn,
+ setScrollToColumn,
uploadDataloader,
setUploadDataloader,
};
diff --git a/DashAI/front/src/components/notebooks/dataset/ColumnInsights.jsx b/DashAI/front/src/components/notebooks/dataset/ColumnInsights.jsx
index a0c32fb15..915d337e6 100644
--- a/DashAI/front/src/components/notebooks/dataset/ColumnInsights.jsx
+++ b/DashAI/front/src/components/notebooks/dataset/ColumnInsights.jsx
@@ -23,6 +23,7 @@ export default function ColumnInsights({
const { t } = useTranslation(["datasets"]);
const context = useDatasetsAndNotebooks();
const setDatasetTab = onNavigateTab ?? context?.setDatasetTab ?? (() => {});
+ const setScrollToColumn = context?.setScrollToColumn ?? (() => {});
const insights = useMemo(() => {
const items = [];
@@ -78,24 +79,23 @@ export default function ColumnInsights({
const handleClick = useCallback(
(insight) => {
- setDatasetTab(insight.tab);
- // Wait for the tab content to render, then scroll to the card
- setTimeout(() => {
- const card = document.querySelector(
- `[data-column-card="${insight.column}"]`,
- );
- if (card) {
- card.scrollIntoView({ behavior: "smooth", block: "center" });
- // Brief highlight effect
- card.style.transition = "box-shadow 0.3s";
- card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
- setTimeout(() => {
- card.style.boxShadow = "";
- }, 2000);
- }
- }, 100);
+ const card = document.querySelector(
+ `[data-column-card="${insight.column}"]`,
+ );
+ if (card) {
+ setDatasetTab(insight.tab);
+ card.scrollIntoView({ behavior: "smooth", block: "center" });
+ card.style.transition = "box-shadow 0.3s";
+ card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
+ setTimeout(() => {
+ card.style.boxShadow = "";
+ }, 2000);
+ } else {
+ setDatasetTab(insight.tab);
+ setScrollToColumn(insight.column);
+ }
},
- [setDatasetTab, theme],
+ [setDatasetTab, setScrollToColumn, theme],
);
const colorMap = {
diff --git a/DashAI/front/src/components/notebooks/dataset/tabs/CategoricalTab.jsx b/DashAI/front/src/components/notebooks/dataset/tabs/CategoricalTab.jsx
index 86eb89c88..dd0036c3e 100644
--- a/DashAI/front/src/components/notebooks/dataset/tabs/CategoricalTab.jsx
+++ b/DashAI/front/src/components/notebooks/dataset/tabs/CategoricalTab.jsx
@@ -1,5 +1,5 @@
import React, { useState } from "react";
-import { Box, Typography, CardContent } from "@mui/material";
+import { Box, Typography, CardContent, Button } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import TitleIcon from "@mui/icons-material/Title";
import {
@@ -18,14 +18,20 @@ import { StatBox } from "../StatBox";
import ExportableCard from "../ExportableCard";
import { useTranslation } from "react-i18next";
+const BATCH_SIZE = 10;
+
export const CategoricalTab = ({ categoricalStats }) => {
const { t } = useTranslation(["datasets", "common"]);
const theme = useTheme();
const [activeIndices, setActiveIndices] = useState({});
+ const entries = Object.entries(categoricalStats ?? {});
+ const [visibleCount, setVisibleCount] = useState(BATCH_SIZE);
+ const visibleEntries = entries.slice(0, visibleCount);
+ const remaining = entries.length - visibleCount;
return (
- {Object.entries(categoricalStats).map(([column, stats]) => (
+ {visibleEntries.map(([column, stats]) => (
{
))}
+ {remaining > 0 && (
+
+
+
+ )}
);
};
diff --git a/DashAI/front/src/components/notebooks/dataset/tabs/CorrelationsTab.jsx b/DashAI/front/src/components/notebooks/dataset/tabs/CorrelationsTab.jsx
index 49ae36372..8dfb9461d 100644
--- a/DashAI/front/src/components/notebooks/dataset/tabs/CorrelationsTab.jsx
+++ b/DashAI/front/src/components/notebooks/dataset/tabs/CorrelationsTab.jsx
@@ -10,7 +10,7 @@ const CorrelationsTab = ({ correlations }) => {
const theme = useTheme();
const { columns, zValues, strongCorrelations, leftMargin } = useMemo(() => {
- const cols = Object.keys(correlations);
+ const cols = Object.keys(correlations ?? {});
// Build symmetric matrix
const z = cols.map((col1) =>
@@ -67,12 +67,18 @@ const CorrelationsTab = ({ correlations }) => {
],
zmin: -1,
zmax: 1,
- text: zValues.map((row) => row.map((val) => val.toFixed(3))),
- texttemplate: "%{text}",
- textfont: {
- color: theme.palette.text.primary,
- size: 11,
- },
+ ...(columns.length <= 30
+ ? {
+ text: zValues.map((row) =>
+ row.map((val) => val.toFixed(3)),
+ ),
+ texttemplate: "%{text}",
+ textfont: {
+ color: theme.palette.text.primary,
+ size: 11,
+ },
+ }
+ : {}),
hovertemplate: "%{x} — %{y}
r = %{z:.3f}",
showscale: true,
colorbar: {
diff --git a/DashAI/front/src/components/notebooks/dataset/tabs/NumericTab.jsx b/DashAI/front/src/components/notebooks/dataset/tabs/NumericTab.jsx
index da25b899c..1e6709a58 100644
--- a/DashAI/front/src/components/notebooks/dataset/tabs/NumericTab.jsx
+++ b/DashAI/front/src/components/notebooks/dataset/tabs/NumericTab.jsx
@@ -1,5 +1,5 @@
-import React from "react";
-import { Box, Typography, CardContent, Alert } from "@mui/material";
+import React, { useState, useRef, useLayoutEffect } from "react";
+import { Box, Typography, CardContent, Alert, Button } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import TrendingUpIcon from "@mui/icons-material/TrendingUp";
import InfoIcon from "@mui/icons-material/Info";
@@ -9,9 +9,56 @@ import { MetricRow } from "../MetricRow";
import ExportableCard from "../ExportableCard";
import { Trans, useTranslation } from "react-i18next";
-export const NumericTab = ({ numericStats }) => {
+const BATCH_SIZE = 10;
+
+export const NumericTab = ({
+ numericStats,
+ scrollToColumn,
+ setScrollToColumn,
+}) => {
const { t } = useTranslation(["datasets"]);
const theme = useTheme();
+ const entries = Object.entries(numericStats ?? {});
+ const [visibleCount, setVisibleCount] = useState(BATCH_SIZE);
+ const visibleEntries = entries.slice(0, visibleCount);
+ const remaining = entries.length - visibleCount;
+ const pendingScrollRef = useRef(null);
+
+ useLayoutEffect(() => {
+ if (!scrollToColumn) {
+ if (!pendingScrollRef.current) return;
+ const col = pendingScrollRef.current;
+ const card = document.querySelector(`[data-column-card="${col}"]`);
+ if (!card) return;
+ pendingScrollRef.current = null;
+ card.scrollIntoView({ behavior: "smooth", block: "center" });
+ card.style.transition = "box-shadow 0.3s";
+ card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
+ setTimeout(() => {
+ card.style.boxShadow = "";
+ }, 2000);
+ return;
+ }
+ const idx = entries.findIndex(([col]) => col === scrollToColumn);
+ if (idx === -1) return;
+ if (idx >= visibleCount) {
+ pendingScrollRef.current = scrollToColumn;
+ setVisibleCount(idx + 1);
+ } else {
+ const card = document.querySelector(
+ `[data-column-card="${scrollToColumn}"]`,
+ );
+ if (card) {
+ card.scrollIntoView({ behavior: "smooth", block: "center" });
+ card.style.transition = "box-shadow 0.3s";
+ card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
+ setTimeout(() => {
+ card.style.boxShadow = "";
+ }, 2000);
+ }
+ }
+ setScrollToColumn(null);
+ }, [scrollToColumn, visibleCount]);
const toNumberOrNull = (value) => {
if (
@@ -33,7 +80,7 @@ export const NumericTab = ({ numericStats }) => {
return (
- {Object.entries(numericStats ?? {}).map(([column, stats]) => (
+ {visibleEntries.map(([column, stats]) => (
{
))}
+ {remaining > 0 && (
+
+
+
+ )}
);
};
diff --git a/DashAI/front/src/components/notebooks/dataset/tabs/TextTab.jsx b/DashAI/front/src/components/notebooks/dataset/tabs/TextTab.jsx
index f4e2ad541..7919ea577 100644
--- a/DashAI/front/src/components/notebooks/dataset/tabs/TextTab.jsx
+++ b/DashAI/front/src/components/notebooks/dataset/tabs/TextTab.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useState, useRef, useLayoutEffect } from "react";
import {
Box,
Typography,
@@ -6,6 +6,7 @@ import {
Chip,
Alert,
Tooltip,
+ Button,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import TextFieldsIcon from "@mui/icons-material/TextFields";
@@ -24,13 +25,57 @@ import { MetricRow } from "../MetricRow";
import ExportableCard from "../ExportableCard";
import { useTranslation } from "react-i18next";
-export const TextTab = ({ textStats }) => {
+const BATCH_SIZE = 10;
+
+export const TextTab = ({ textStats, scrollToColumn, setScrollToColumn }) => {
const theme = useTheme();
const { t } = useTranslation(["datasets", "common"]);
const [activeIndices, setActiveIndices] = useState({});
+ const entries = Object.entries(textStats ?? {});
+ const [visibleCount, setVisibleCount] = useState(BATCH_SIZE);
+ const visibleEntries = entries.slice(0, visibleCount);
+ const remaining = entries.length - visibleCount;
+ const pendingScrollRef = useRef(null);
+
+ useLayoutEffect(() => {
+ if (!scrollToColumn) {
+ if (!pendingScrollRef.current) return;
+ const col = pendingScrollRef.current;
+ const card = document.querySelector(`[data-column-card="${col}"]`);
+ if (!card) return;
+ pendingScrollRef.current = null;
+ card.scrollIntoView({ behavior: "smooth", block: "center" });
+ card.style.transition = "box-shadow 0.3s";
+ card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
+ setTimeout(() => {
+ card.style.boxShadow = "";
+ }, 2000);
+ return;
+ }
+ const idx = entries.findIndex(([col]) => col === scrollToColumn);
+ if (idx === -1) return;
+ if (idx >= visibleCount) {
+ pendingScrollRef.current = scrollToColumn;
+ setVisibleCount(idx + 1);
+ } else {
+ const card = document.querySelector(
+ `[data-column-card="${scrollToColumn}"]`,
+ );
+ if (card) {
+ card.scrollIntoView({ behavior: "smooth", block: "center" });
+ card.style.transition = "box-shadow 0.3s";
+ card.style.boxShadow = `0 0 0 2px ${theme.palette.warning.main}`;
+ setTimeout(() => {
+ card.style.boxShadow = "";
+ }, 2000);
+ }
+ }
+ setScrollToColumn(null);
+ }, [scrollToColumn, visibleCount]);
+
return (
- {Object.entries(textStats).map(([column, stats]) => {
+ {visibleEntries.map(([column, stats]) => {
const lengthData = [
{
label: t("datasets:label.min"),
@@ -249,6 +294,16 @@ export const TextTab = ({ textStats }) => {
);
})}
+ {remaining > 0 && (
+
+
+
+ )}
);
};