Skip to content

Commit

Permalink
Highlight weights on hover
Browse files Browse the repository at this point in the history
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
  • Loading branch information
johnbradley committed Feb 1, 2024
1 parent 620050b commit 98e3d3c
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 17 deletions.
11 changes: 10 additions & 1 deletion andromeda-ui/components/DataExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface DataExplorerProps {
weights: any | undefined;
datasetID: string | undefined;
columnSettings: any;
columnDetails: any;
drFunc: any;
rdrFunc: any;
onClickBack: any;
Expand All @@ -27,14 +28,15 @@ 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<number>(DEFAULT_IMAGE_SIZE);
const [showLabel, setShowLabel] = useState<boolean>(true);
const [showImage, setShowImage] = useState<boolean>(true);
const [sliderWeights, setSliderWeights] = useState<any>(weights);
const [working, setWorking] = useState<boolean>(false);
const stageRef = useRef<any>(null);
const [highlightImageKey, setHighlightImageKey] = useState<string>();

function onImageMoved(x: number, y: number, label: string) {
if (images) {
Expand All @@ -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 => <WeightSlider
key={key}
id={key}
highlight={highlightValues[key]}
weights={sliderWeights}
onChange={(evt: any) => onChangeWeight(key, parseFloat(evt.target.value))} />);
}
Expand Down Expand Up @@ -128,6 +136,7 @@ export default function DataExplorer(props: DataExplorerProps) {
showImage={showImage}
onImageMoved={onImageMoved}
images={images}
setHighlightImageKey={setHighlightImageKey}
/>
<div className="flex gap-2 my-2 mr-2">
<ColoredButton
Expand Down
4 changes: 4 additions & 0 deletions andromeda-ui/components/ImageGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface ImageGridProps {
showLabel: boolean;
showImage: boolean;
onImageMoved: any;
setHighlightImageKey: any;
images: any[];
}

Expand Down Expand Up @@ -55,10 +56,13 @@ export default function ImageGrid(props: ImageGridProps) {
const { stageRef, size, imageSize, pointScaling, showLabel, showImage, images } = props;
function onMouseEnter(evt: any) {
stageRef.current.container().style.cursor = 'move';
const dataLabel = evt.currentTarget.attrs["data-label"];
props.setHighlightImageKey(dataLabel);
}

function onMouseLeave(evt: any) {
stageRef.current.container().style.cursor = 'grab';
props.setHighlightImageKey(undefined);
}

const imageControls = images.map(item => createImage(
Expand Down
39 changes: 26 additions & 13 deletions andromeda-ui/components/WeightSlider.tsx
Original file line number Diff line number Diff line change
@@ -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 = <div className="absolute left-2 top-1 inline w-1 bg-cyan-600 h-3" style={{"left": hintPosition + "px"}}>
&nbsp;
</div>;
}
return <div key={id} className="flex flex-nowrap">
<input
type="range"
id={id}
name={id}
value={value}
onChange={onChange}
min="0.001"
max="0.999"
step="0.001"
className="w-60"
></input>
<div className="relative">
<input
type="range"
id={id}
name={id}
value={value}
onChange={onChange}
min="0.001"
max="0.999"
step="0.001"
style={{"width": SLIDER_WIDTH + "px"}}
></input>
{highlightDiv}
</div>
&nbsp;
<label htmlFor="volume">{id}</label>
</div>
Expand Down
17 changes: 15 additions & 2 deletions andromeda/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pandas as pd
from flask import abort
import andromeda
from sklearn.preprocessing import MinMaxScaler


class DatasetStore(object):
Expand Down Expand Up @@ -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]))
Expand All @@ -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):
Expand Down
4 changes: 3 additions & 1 deletion andromeda/tests/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down

0 comments on commit 98e3d3c

Please sign in to comment.