Skip to content
20 changes: 18 additions & 2 deletions DashAI/front/src/components/DatasetVisualization.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
Expand Down Expand Up @@ -436,14 +442,24 @@ export default function DatasetVisualization({
/>
)}
{tab === 1 && (
<NumericTab numericStats={datasetInfo?.numeric_stats} />
<NumericTab
numericStats={datasetInfo?.numeric_stats}
scrollToColumn={scrollToColumn}
setScrollToColumn={setScrollToColumn}
/>
)}
{tab === 2 && (
<CategoricalTab
categoricalStats={datasetInfo?.categorical_stats}
/>
)}
{tab === 3 && <TextTab textStats={datasetInfo?.text_stats} />}
{tab === 3 && (
<TextTab
textStats={datasetInfo?.text_stats}
scrollToColumn={scrollToColumn}
setScrollToColumn={setScrollToColumn}
/>
)}
{tab === 4 && (
<QualityTab
qualityInfo={datasetInfo?.quality_info}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const DatasetsAndNotebooksProvider = ({ children }) => {
const [uploadDataloader, setUploadDataloader] = useState(null);
const [datasetInfo, setDatasetInfo] = useState(null);
const [datasetTab, setDatasetTab] = useState(0);
const [scrollToColumn, setScrollToColumn] = useState(null);

useEffect(() => {
fetchNotebooks();
Expand Down Expand Up @@ -102,6 +103,8 @@ export const DatasetsAndNotebooksProvider = ({ children }) => {
setDatasetInfo,
datasetTab,
setDatasetTab,
scrollToColumn,
setScrollToColumn,
uploadDataloader,
setUploadDataloader,
};
Expand Down
34 changes: 17 additions & 17 deletions DashAI/front/src/components/notebooks/dataset/ColumnInsights.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 (
<Box display="flex" flexDirection="column" gap={8}>
{Object.entries(categoricalStats).map(([column, stats]) => (
{visibleEntries.map(([column, stats]) => (
<ExportableCard
key={column}
filename={`categorical_${column}`}
Expand Down Expand Up @@ -188,6 +194,16 @@ export const CategoricalTab = ({ categoricalStats }) => {
</CardContent>
</ExportableCard>
))}
{remaining > 0 && (
<Box display="flex" justifyContent="center" mt={1} mb={2}>
<Button
variant="outlined"
onClick={() => setVisibleCount((c) => c + BATCH_SIZE)}
>
Show more ({remaining} remaining)
</Button>
</Box>
)}
</Box>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down Expand Up @@ -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}<br>r = %{z:.3f}<extra></extra>",
showscale: true,
colorbar: {
Expand Down
65 changes: 61 additions & 4 deletions DashAI/front/src/components/notebooks/dataset/tabs/NumericTab.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 (
Expand All @@ -33,7 +80,7 @@ export const NumericTab = ({ numericStats }) => {

return (
<Box display="flex" flexDirection="column" gap={8}>
{Object.entries(numericStats ?? {}).map(([column, stats]) => (
{visibleEntries.map(([column, stats]) => (
<ExportableCard
key={column}
filename={`numeric_${column}`}
Expand Down Expand Up @@ -344,6 +391,16 @@ export const NumericTab = ({ numericStats }) => {
</CardContent>
</ExportableCard>
))}
{remaining > 0 && (
<Box display="flex" justifyContent="center" mt={1} mb={2}>
<Button
variant="outlined"
onClick={() => setVisibleCount((c) => c + BATCH_SIZE)}
>
Show more ({remaining} remaining)
</Button>
</Box>
)}
</Box>
);
};
61 changes: 58 additions & 3 deletions DashAI/front/src/components/notebooks/dataset/tabs/TextTab.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState } from "react";
import React, { useState, useRef, useLayoutEffect } from "react";
import {
Box,
Typography,
CardContent,
Chip,
Alert,
Tooltip,
Button,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import TextFieldsIcon from "@mui/icons-material/TextFields";
Expand All @@ -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 (
<Box display="flex" flexDirection="column" gap={8}>
{Object.entries(textStats).map(([column, stats]) => {
{visibleEntries.map(([column, stats]) => {
const lengthData = [
{
label: t("datasets:label.min"),
Expand Down Expand Up @@ -249,6 +294,16 @@ export const TextTab = ({ textStats }) => {
</ExportableCard>
);
})}
{remaining > 0 && (
<Box display="flex" justifyContent="center" mt={1} mb={2}>
<Button
variant="outlined"
onClick={() => setVisibleCount((c) => c + BATCH_SIZE)}
>
Show more ({remaining} remaining)
</Button>
</Box>
)}
</Box>
);
};
Loading