From 98e3d3ce24a10ebd99d4fadb68f732aceba7b9f1 Mon Sep 17 00:00:00 2001 From: John Bradley Date: Mon, 20 Nov 2023 11:28:28 -0500 Subject: [PATCH] Highlight weights on hover Adds logic to show values for an observation when the user's mouse is over it. The values will be shown by a small square on the weight slider following an earlier convention. The backend API now returns values for each observation scaled to a value between 0 and 1. Fixes #62 --- andromeda-ui/components/DataExplorer.tsx | 11 ++++++- andromeda-ui/components/ImageGrid.tsx | 4 +++ andromeda-ui/components/WeightSlider.tsx | 39 ++++++++++++++++-------- andromeda/dataset.py | 17 +++++++++-- andromeda/tests/test_dataset.py | 4 ++- 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/andromeda-ui/components/DataExplorer.tsx b/andromeda-ui/components/DataExplorer.tsx index ad64b38..f294ca8 100644 --- a/andromeda-ui/components/DataExplorer.tsx +++ b/andromeda-ui/components/DataExplorer.tsx @@ -19,6 +19,7 @@ interface DataExplorerProps { weights: any | undefined; datasetID: string | undefined; columnSettings: any; + columnDetails: any; drFunc: any; rdrFunc: any; onClickBack: any; @@ -27,7 +28,7 @@ interface DataExplorerProps { export default function DataExplorer(props: DataExplorerProps) { const { images, setImageData, pointScaling, setPointScaling, - weights, datasetID, columnSettings, + weights, datasetID, columnSettings, columnDetails, drFunc, rdrFunc, onClickBack } = props; const [imageSize, setImageSize] = useState(DEFAULT_IMAGE_SIZE); const [showLabel, setShowLabel] = useState(true); @@ -35,6 +36,7 @@ export default function DataExplorer(props: DataExplorerProps) { const [sliderWeights, setSliderWeights] = useState(weights); const [working, setWorking] = useState(false); const stageRef = useRef(null); + const [highlightImageKey, setHighlightImageKey] = useState(); function onImageMoved(x: number, y: number, label: string) { if (images) { @@ -55,9 +57,15 @@ export default function DataExplorer(props: DataExplorerProps) { } if (weights) { + let highlightValues: {[key:string]: number} = {} + if (highlightImageKey) { + highlightValues = images.find((x) => x.label == highlightImageKey).values; + } + weightControls = Object.keys(weights).map(key => onChangeWeight(key, parseFloat(evt.target.value))} />); } @@ -128,6 +136,7 @@ export default function DataExplorer(props: DataExplorerProps) { showImage={showImage} onImageMoved={onImageMoved} images={images} + setHighlightImageKey={setHighlightImageKey} />
createImage( diff --git a/andromeda-ui/components/WeightSlider.tsx b/andromeda-ui/components/WeightSlider.tsx index 0725fa2..2f394a4 100644 --- a/andromeda-ui/components/WeightSlider.tsx +++ b/andromeda-ui/components/WeightSlider.tsx @@ -1,24 +1,37 @@ interface WeightSliderProps { id: string; weights: any; + highlight: number | undefined; onChange: any; } -export default function WeightSlider({ id, weights, onChange }: WeightSliderProps) { - const value = weights[id]; +const SLIDER_WIDTH = 200; + +export default function WeightSlider({ id, weights, highlight, onChange }: WeightSliderProps) { + const value = weights[id]; + let highlightDiv = null; + if (highlight !== undefined) { + const hintPosition = highlight * (SLIDER_WIDTH - 4); + highlightDiv =
+   +
; + } return
- +
+ + {highlightDiv} +
 
diff --git a/andromeda/dataset.py b/andromeda/dataset.py index 43a52d6..f4759bc 100644 --- a/andromeda/dataset.py +++ b/andromeda/dataset.py @@ -3,6 +3,7 @@ import pandas as pd from flask import abort import andromeda +from sklearn.preprocessing import MinMaxScaler class DatasetStore(object): @@ -75,6 +76,14 @@ def get_normalized_dataframe(self): return df[self.selected_columns] + def get_min_max_scaled_dataframe(self): + df = self.read_dataframe() + df.set_index(self.label_column_name, inplace=True) + df = df.select_dtypes('number') + scaler = MinMaxScaler() + df[df.columns] = scaler.fit_transform(df[df.columns]) + return df + def get_label_to_url(self): df = self.read_dataframe() return dict(zip(df[self.label_column_name], df[self.url_column_name])) @@ -92,9 +101,13 @@ def dimensional_reduction(self, weights): image_coordinate_df = andromeda.dimension_reduction( normalized_df, weight_series ) - self.add_label_and_url_columns(image_coordinate_df) - return weight_series.to_dict(), image_coordinate_df.to_dict("records") + min_max_dataframe = self.get_min_max_scaled_dataframe() + coordinate_dict_ary = image_coordinate_df.to_dict("records") + for item in coordinate_dict_ary: + series = min_max_dataframe.loc[item['label']] + item['values'] = series.to_dict() + return weight_series.to_dict(), coordinate_dict_ary @staticmethod def create_weight_series(weights, columns): diff --git a/andromeda/tests/test_dataset.py b/andromeda/tests/test_dataset.py index 14b31e1..079ee1f 100644 --- a/andromeda/tests/test_dataset.py +++ b/andromeda/tests/test_dataset.py @@ -53,9 +53,11 @@ def fake_get_path(): self.assertEqual(weights.keys(), set(["R1", "B1", "G1"])) self.assertEqual(len(image_coordinates), 3) - self.assertEqual(image_coordinates[0].keys(), set(["x", "y", "label", "url"])) + self.assertEqual(image_coordinates[0].keys(), set(["x", "y", "label", "url", "values"])) self.assertEqual(image_coordinates[0]["label"], "p1") self.assertEqual(image_coordinates[0]["url"], "https://example.com/p1.jpg") + self.assertEqual(image_coordinates[0]["values"].keys(), set(["R1", "B1", "G1"])) + self.assertEqual(image_coordinates[0]["values"]["B1"], 0.2) self.assertEqual(image_coordinates[1]["url"], "") @patch('dataset.os')