Skip to content

Commit

Permalink
Merge branch 'main' of github.com:ETH-PEACH-Lab/datascience-visualisa…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
RasanYo committed Aug 27, 2024
2 parents a4fc9c4 + 5d028e4 commit 000bf3c
Show file tree
Hide file tree
Showing 13 changed files with 370 additions and 716 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,6 @@ dmypy.json
.yarn/

out.txt
/secrets/
/secrets/

jupyterlab_apod
16 changes: 2 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cluster_viz",
"version": "0.2.1",
"version": "0.2.2",
"description": "Show a random NASA Astronomy Picture of the Day in a JupyterLab panel",
"keywords": [
"jupyter",
Expand Down Expand Up @@ -159,19 +159,7 @@
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": [
"PascalCase"
],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
Expand Down
24 changes: 19 additions & 5 deletions src/CodeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import '../style/CodeCell.css';

interface CodeCellProps {
code: string;
clusterLabel: string; // Add the cluster label as a prop
notebook_id: number; // add the notebook_id as a prop
clusterLabel: string; // Existing prop
notebook_id: number; // Existing prop
onSelectNotebook: (notebookId: [number]) => void; // New prop to handle notebook selection
setCurrentCluster: (identifier: string) => void; // Existing prop
notebook_name: string;
}

const CodeCell: React.FC<CodeCellProps> = ({ code, clusterLabel,notebook_id }) => {

const CodeCell: React.FC<CodeCellProps> = ({ code, clusterLabel, notebook_id, onSelectNotebook, setCurrentCluster, notebook_name }) => {
const editorRef = useRef<HTMLDivElement>(null);

useEffect(() => {
Expand All @@ -29,9 +33,19 @@ const CodeCell: React.FC<CodeCellProps> = ({ code, clusterLabel,notebook_id }) =

return (
<div className="code-cell-container">
<div className="notebook-id">Student {notebook_id}</div> {/* Display the notebook ID */}
<button
className="notebook-id-button"
onClick={() => onSelectNotebook([notebook_id])}
>
Notebook {notebook_id}
</button> {/* Button to select this student */}
<div ref={editorRef} className="code-editor" />
<div className="cluster-label">{clusterLabel}</div> {/* Display the cluster label */}
<button
className="cluster-label-button"
onClick={() => setCurrentCluster(clusterLabel)}
>
{clusterLabel}
</button> {/* Button to set the current cluster */}
</div>
);
};
Expand Down
104 changes: 69 additions & 35 deletions src/Flowchart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,28 @@ interface Props { }

interface State {
selectedCells: NotebookCellWithID[];
allNotebooks: boolean;
}

function resizeSVG(svgRef: React.RefObject<SVGSVGElement>): void {
const svg = svgRef.current;
// function resizeSVG(svgRef: React.RefObject<SVGSVGElement>): void {
// const svg = svgRef.current;

if (svg) {
// Get the bounds of the SVG content
const bbox = svg.getBBox();
// if (svg) {
// // Get the bounds of the SVG content
// const bbox = svg.getBBox();
// console.log(bbox);

// Update the width and height using the size of the contents
svg.setAttribute("width", (bbox.x + bbox.width + bbox.x).toString());
svg.setAttribute("height", (bbox.y + bbox.height + bbox.y).toString());
// const newWidth = bbox.x + bbox.width + bbox.x;
// const newHeight = bbox.y + bbox.height + bbox.y;
// // Update the width and height using the size of the contents
// svg.setAttribute("width", newWidth.toString());
// svg.setAttribute("height", newHeight.toString());

console.log('Resized SVG to', (bbox.x + bbox.width + bbox.x).toString(), (bbox.y + bbox.height + bbox.y).toString());
} else {
console.error("SVG element not found.");
}
}
// console.log('Resized SVG to', newWidth.toString(), newHeight.toString());
// } else {
// console.error("SVG element not found.");
// }
// }

class Flowchart extends Component<Props, State> {
svgRef: React.RefObject<SVGSVGElement>;
Expand All @@ -64,6 +68,7 @@ class Flowchart extends Component<Props, State> {
this.svgRef = createRef();
this.state = {
selectedCells: [],
allNotebooks: false,
};
}

Expand All @@ -73,16 +78,15 @@ class Flowchart extends Component<Props, State> {
}
}

updateSelectedCells = (newSelectedCells: NotebookCellWithID[]) => {
this.setState({ selectedCells: newSelectedCells });
updateSelectedCells = (newSelectedCells: NotebookCellWithID[], allNotebooks: boolean) => {
this.setState({ selectedCells: newSelectedCells, allNotebooks });
};


drawClassChart = () => {
const { selectedCells } = this.state;
console.log('Drawing chart for selected cells', selectedCells);
if (selectedCells.length === 0) {
return;
return { width: 0, height: 0 };
}

const svg = d3.select(this.svgRef.current)
Expand Down Expand Up @@ -144,24 +148,44 @@ class Flowchart extends Component<Props, State> {
const source = nodes.find(node => node.id === d.source);
const target = nodes.find(node => node.id === d.target);
if (source && target) {
const isDirectNeighbor = nodes.indexOf(source) === nodes.indexOf(target) - 1;
const midX = (source.x + target.x) / 2;
const midY = (source.y + target.y) / 2;
return lineGenerator([
[source.x, source.y + nodeHeight / 2],
[midX - nodeWidth / 2, midY],
[target.x, target.y + nodeHeight / 2]
] as [number, number][]);
const distanceY = Math.abs(target.y - source.y); // Calculate the vertical distance between nodes

if (isDirectNeighbor) {
// Draw the link on the left (curved leftwards)
return lineGenerator([
[source.x, source.y + nodeHeight / 2],
[midX - nodeWidth / 2, midY + nodeHeight / 2],
[target.x, target.y + nodeHeight / 2]
] as [number, number][]);
} else {
// Draw the link on the right (curved rightwards)
return lineGenerator([
[source.x + nodeWidth, source.y + nodeHeight / 2],
[midX + nodeWidth + distanceY / 4, midY], // Shift control point rightwards
[target.x + nodeWidth, target.y + nodeHeight / 2]
] as [number, number][]);
}
}
return '';
})
.attr('stroke', '#999')
.attr('fill', 'none');

// Calculate the required width and height
const width = 300; // Fixed width
const height = nodeCounter * 100 + 50; // Based on number of nodes

return { width, height };
}


drawClusterChart = () => {
const { selectedCells } = this.state;
if (selectedCells.length === 0) {
return;
return { width: 0, height: 0 };
}

const svg = d3.select(this.svgRef.current);
Expand All @@ -170,7 +194,7 @@ class Flowchart extends Component<Props, State> {

const nodes: ClusterNode[] = [];
const links: ClusterLink[] = [];
const circleRadius = 25; // Set the circle radius here
const circleRadius = 15; // Set the circle radius here
const arrowheadSize = 6; // Adjust this to the size of the arrowhead
let yCounter = 0;

Expand All @@ -194,7 +218,7 @@ class Flowchart extends Component<Props, State> {
id: nodes.length + 1,
cluster: cells[i].cluster,
class: cls,
x: 150 + clusterSet.size * 150, // Horizontally position nodes with the same class next to each other
x: -50 + clusterSet.size * 150, // Horizontally position nodes with the same class next to each other
y: 100 + yCounter * 150, // Vertically space classes
cell_id: cells[i].cell_id,
notebook_id: cells[i].notebook_id, // Add notebook_id to the node
Expand Down Expand Up @@ -244,7 +268,7 @@ class Flowchart extends Component<Props, State> {
const words = d.cluster.split(' '); // Split cluster name into words
let tspan = d3.select(this).append('tspan')
.attr('x', d.x)
.attr('y', d.y)
.attr('y', d.y + circleRadius + 10)
.attr('dy', -2); // Start at the correct vertical position

for (let i = 0; i < words.length; i += 3) {
Expand Down Expand Up @@ -312,6 +336,11 @@ class Flowchart extends Component<Props, State> {
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#666')
.style('stroke', 'none');

const width = Math.max(...nodes.map(node => node.x)) + circleRadius * 2 + 50; // Plus padding
const height = yCounter * 150 + circleRadius * 2 + 50; // Based on number of classes

return { width, height };
}

drawChart() {
Expand All @@ -321,17 +350,22 @@ class Flowchart extends Component<Props, State> {
return;
}

if (selectedCells.length > 100) {
this.drawClassChart();
resizeSVG(this.svgRef);
return;
}
let dimensions: { width: number, height: number };

this.drawClusterChart();
resizeSVG(this.svgRef);
if (this.state.allNotebooks) {
dimensions = this.drawClassChart();
} else {
dimensions = this.drawClusterChart();
}

const svg = this.svgRef.current;
if (svg) {
svg.setAttribute("width", dimensions.width.toString());
svg.setAttribute("height", dimensions.height.toString());
}
}


render() {
return (
<svg width="800" height="1000" ref={this.svgRef}></svg>
Expand All @@ -348,8 +382,8 @@ export class FlowchartWidget extends ReactWidget {
this.graph = createRef();
}

public updateGraph(selectedCells: NotebookCellWithID[]): void {
this.graph.current?.updateSelectedCells(selectedCells);
public updateGraph(selectedCells: NotebookCellWithID[], allNotebooks: boolean): void {
this.graph.current?.updateSelectedCells(selectedCells, allNotebooks);
}

render(): JSX.Element {
Expand Down
64 changes: 25 additions & 39 deletions src/NotebookSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,45 @@
// NotebookSelector.tsx
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import '../style/notebookSelector.css';

interface NotebookSelectorProps {
notebookIds: number[];
notebookNames: string[];
onSelectionChange: (selectedIds: number[]) => void;
selectedNotebooks: number[];
}

const NotebookSelector: React.FC<NotebookSelectorProps> = ({ notebookIds, onSelectionChange }) => {
const [shownNotebooks, setShownNotebooks] = useState<Set<number>>(new Set());
const NotebookSelector: React.FC<NotebookSelectorProps> = ({ notebookIds, notebookNames, onSelectionChange, selectedNotebooks }) => {
const [selectedValue, setSelectedValue] = useState<string>('');

useEffect(() => {
// Trigger the callback when shownNotebooks changes
onSelectionChange(Array.from(shownNotebooks));
}, [shownNotebooks, onSelectionChange]);


const handleRemoveNotebook = (notebookId: number) => {
const newShownNotebooks = new Set(shownNotebooks);
if(newShownNotebooks.has(notebookId)){
newShownNotebooks.delete(notebookId);
setShownNotebooks(newShownNotebooks);
const newSelectedNotebooks = selectedNotebooks.filter(id => id !== notebookId);
if (newSelectedNotebooks.length === 0) {
onSelectionChange([-2]); // Add "ALL" if no notebooks are selected
} else {
onSelectionChange(newSelectedNotebooks);
}
};
};

const handleAddNotebook = () => {
const notebookId = selectedValue === "ALL" ? -2 : Number(selectedValue);

const notebookId = selectedValue === "All Notebooks" ? -2 : parseInt(selectedValue.match(/\d+/)?.[0] || '', 10);
console.log('Current notebook IDs:', selectedNotebooks);
if (notebookId === -2) {
// Remove all selected notebooks before adding ALL
const newShownNotebooks = new Set<number>();
newShownNotebooks.add(-2);
setShownNotebooks(newShownNotebooks);
} else if (!shownNotebooks.has(notebookId) && notebookIds.includes(notebookId)) {
if (shownNotebooks.has(-2)) {
console.log("nxdjwkn");
handleRemoveNotebook(-2); // Remove ALL (-2) before adding a specific notebook
console.log("COME ON")
}
console.log("notebooks")
console.log(shownNotebooks)
const newShownNotebooks = new Set(shownNotebooks);
newShownNotebooks.add(notebookId);
setShownNotebooks(newShownNotebooks);
onSelectionChange([-2]); // If "ALL" is selected, clear other selections and set "ALL"
} else if (!selectedNotebooks.includes(notebookId) && notebookIds.includes(notebookId)) {
const newSelectedNotebooks = [...selectedNotebooks, notebookId].filter(id => id !== -2); // Remove "ALL" (-2) before adding a specific notebook
onSelectionChange(newSelectedNotebooks);
}
setSelectedValue(''); // Clear the input after adding
};
return (
};

return (
<div className="selector-container">
<div className="current-selection-text">Current selection:</div>
<div className="selected-elements" id="selected-elements">
{Array.from(shownNotebooks).map(notebookId => (
{selectedNotebooks.map(notebookId => (
<div key={notebookId} className="element">
{notebookId === -2 ? "ALL" : notebookId}
{notebookId === -2 ? "All Notebooks" : "Notebook " + notebookId + ": " + notebookNames[notebookIds.indexOf(notebookId)]}
<button className="remove-button" onClick={() => handleRemoveNotebook(notebookId)}>Remove</button>
</div>
))}
Expand All @@ -65,12 +51,12 @@ const NotebookSelector: React.FC<NotebookSelectorProps> = ({ notebookIds, onSele
className="element-selector"
value={selectedValue}
onChange={(e) => setSelectedValue(e.target.value)}
placeholder="Select notebook ID"
placeholder="Select notebook"
/>
<datalist id="elements">
{notebookIds.map(id => (
<option key={id} value={id === -2 ? "ALL" : id}>
{id === -2 ? "ALL" : id}
<option key={id} value={id === -2 ? "All Notebooks" : "Notebook " + id}>
{id === -2 ? "ALL" : notebookNames[notebookIds.indexOf(id)]}
</option>
))}
</datalist>
Expand Down
Loading

0 comments on commit 000bf3c

Please sign in to comment.