Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 249 additions & 35 deletions js/deck-gl/matrix/dendro_layers.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,163 @@
import { PolygonLayer } from 'deck.gl';
import { LinearInterpolator, PolygonLayer } from 'deck.gl';

import { sync_selected_genes } from '../../global_variables/selected_genes';

import { curate_pan_x, curate_pan_y } from './curate_pan';
import { get_mat_layers_list } from './matrix_layers';
import { redefine_global_view_state } from './redefine_global_view_state';
import { update_zoom_data } from './zoom';

const DENDRO_AXES = ['row', 'col'];
const DEFAULT_FILL_COLOR = [0, 0, 0, 90];
const FOCUSED_FILL_COLOR = [0, 0, 0, 180];
const DOUBLE_CLICK_DELAY = 250;
const DENDRO_FOCUS_TRANSITION = 650;
const MAX_FOCUS_ZOOM = 7;

const getCurrentFocus = (viz_state) => {
const storeFocus =
const ease_out_cubic = (t) => 1 - Math.pow(1 - t, 3);

const ensure_click_tracking = (viz_state) => {
if (!viz_state.dendro.clicks) {
viz_state.dendro.clicks = { row: 0, col: 0 };
}

if (!viz_state.dendro.click_timeouts) {
viz_state.dendro.click_timeouts = { row: null, col: null };
}

if (!viz_state.dendro.double_click_state) {
viz_state.dendro.double_click_state = { row: null, col: null };
}
};

const apply_view_state_transition = (view_state) => {
const transition_props = () => ({
transitionDuration: DENDRO_FOCUS_TRANSITION,
transitionEasing: ease_out_cubic,
transitionInterpolator: new LinearInterpolator(['target', 'zoom']),
});

return Object.fromEntries(
Object.entries(view_state).map(([key, value]) => [
key,
{ ...value, ...transition_props() },
])
);
};

const compute_cluster_zoom = (matrix_span, cluster_span, min_unit) => {
const effective_span = Math.min(matrix_span, Math.max(cluster_span, min_unit));

const zoom = Math.log2(matrix_span / Math.max(effective_span, 1e-6));
Comment thread
cornhundred marked this conversation as resolved.

return Math.min(Math.max(zoom, 0), MAX_FOCUS_ZOOM);
};

const animate_focus_to_cluster = (
deck_mat,
layers_mat,
viz_state,
axis,
polygonProps
) => {
if (!polygonProps) {
return;
}

const is_row_axis = axis === 'row';
const matrix_span = is_row_axis
? viz_state.viz.mat_height
: viz_state.viz.mat_width;
const min_unit = is_row_axis
? viz_state.viz.row_offset
: viz_state.viz.col_offset;

const current_zoom_x = viz_state.zoom.zoom_data.matrix.zoom_x;
const current_zoom_y = viz_state.zoom.zoom_data.matrix.zoom_y;

const cluster_span = Math.max(
Math.abs((polygonProps.pos_bot ?? 0) - (polygonProps.pos_top ?? 0)),
min_unit
);

const target_zoom = compute_cluster_zoom(matrix_span, cluster_span, min_unit);

const zoom_curated = [
is_row_axis ? current_zoom_x : target_zoom,
is_row_axis ? target_zoom : current_zoom_y,
];

const cluster_center =
((polygonProps.pos_top ?? 0) + (polygonProps.pos_bot ?? 0)) / 2;

const existing_pan = viz_state.zoom.zoom_data.matrix;

const desired_pan = {
x: is_row_axis ? existing_pan.pan_x : cluster_center,
y: is_row_axis ? cluster_center : existing_pan.pan_y,
};

const pan_curated = [
curate_pan_x(desired_pan.x, zoom_curated[0], viz_state),
curate_pan_y(desired_pan.y, zoom_curated[1], viz_state),
];

const view_id = is_row_axis ? 'dendro_rows' : 'dendro_cols';

const global_view_state = redefine_global_view_state(
viz_state,
view_id,
zoom_curated,
pan_curated
);

update_zoom_data(viz_state, view_id, zoom_curated, pan_curated);

viz_state.zoom.zoom_data.total_zoom.x = zoom_curated[0];
viz_state.zoom.zoom_data.total_zoom.y = zoom_curated[1];

deck_mat.setProps({
viewState: apply_view_state_transition(global_view_state),
layers: get_mat_layers_list(layers_mat),
});
};

const animate_focus_reset = (deck_mat, layers_mat, viz_state) => {
const default_zoom = [viz_state.zoom.ini_zoom_x, viz_state.zoom.ini_zoom_y];
const default_pan = [viz_state.zoom.ini_pan_x, viz_state.zoom.ini_pan_y];

const global_view_state = redefine_global_view_state(
viz_state,
'matrix',
default_zoom,
default_pan
);

update_zoom_data(viz_state, 'matrix', default_zoom, default_pan);

viz_state.zoom.zoom_data.total_zoom.x = default_zoom[0];
viz_state.zoom.zoom_data.total_zoom.y = default_zoom[1];

deck_mat.setProps({
viewState: apply_view_state_transition(global_view_state),
layers: get_mat_layers_list(layers_mat),
});
};

const get_current_focus = (viz_state) => {
const store_focus =
viz_state?.obs_store?.focused_dendro &&
typeof viz_state.obs_store.focused_dendro.get === 'function'
? viz_state.obs_store.focused_dendro.get()
: null;

return storeFocus ?? viz_state.dendro?.active_polygon ?? null;
return store_focus ?? viz_state.dendro?.active_polygon ?? null;
};

const applyDendroFocus = (deck_mat, layers_mat, viz_state, focus) => {
const normalizedFocus = focus ? { axis: focus.axis, name: focus.name } : null;
const apply_dendro_focus = (deck_mat, layers_mat, viz_state, focus) => {
const normalized_focus = focus
? { axis: focus.axis, name: focus.name }
: null;

let didUpdate = false;

Expand All @@ -30,12 +168,12 @@ const applyDendroFocus = (deck_mat, layers_mat, viz_state, focus) => {

const updatedPolygons = viz_state.dendro.polygons[targetAxis].map(
(polygon) => {
const isFocused =
!!normalizedFocus &&
polygon.properties.axis === normalizedFocus.axis &&
polygon.properties.name === normalizedFocus.name;
const is_focused =
!!normalized_focus &&
polygon.properties.axis === normalized_focus.axis &&
polygon.properties.name === normalized_focus.name;

if (polygon.properties.is_focused === isFocused) {
if (polygon.properties.is_focused === is_focused) {
return polygon;
}

Expand All @@ -45,7 +183,7 @@ const applyDendroFocus = (deck_mat, layers_mat, viz_state, focus) => {
...polygon,
properties: {
...polygon.properties,
is_focused: isFocused,
is_focused,
},
};
}
Expand All @@ -62,11 +200,11 @@ const applyDendroFocus = (deck_mat, layers_mat, viz_state, focus) => {
}
});

viz_state.dendro.active_polygon = normalizedFocus;
viz_state.dendro.active_polygon = normalized_focus;

if (viz_state.obs_store?.focused_dendro) {
const focusValue = normalizedFocus ? { ...normalizedFocus } : null;
viz_state.obs_store.focused_dendro.set(focusValue);
const focus_value = normalized_focus ? { ...normalized_focus } : null;
viz_state.obs_store.focused_dendro.set(focus_value);
}

if (didUpdate && typeof deck_mat?.setProps === 'function') {
Expand Down Expand Up @@ -131,41 +269,56 @@ const focus_dendro_polygon = (
layers_mat,
viz_state,
axis,
polygonName
polygon_name,
options = {}
) => {
const previousFocus = getCurrentFocus(viz_state);
const { allow_toggle = true } = options;

const previous_focus = get_current_focus(viz_state);

if (
previousFocus &&
previousFocus.axis === axis &&
previousFocus.name === polygonName
allow_toggle &&
previous_focus &&
previous_focus.axis === axis &&
previous_focus.name === polygon_name
) {
applyDendroFocus(deck_mat, layers_mat, viz_state, null);
return;
apply_dendro_focus(deck_mat, layers_mat, viz_state, null);
return false;
}

applyDendroFocus(deck_mat, layers_mat, viz_state, {
apply_dendro_focus(deck_mat, layers_mat, viz_state, {
axis,
name: polygonName,
name: polygon_name,
});

return true;
};

const dendro_layer_onclick = (event, deck_mat, layers_mat, viz_state, axis) => {
const dendro_layer_on_click = (
event,
deck_mat,
layers_mat,
viz_state,
axis
) => {
ensure_click_tracking(viz_state);

const clicked_name = event.object.properties.name;
const current_focus = get_current_focus(viz_state);
const was_active_before_click =
current_focus &&
current_focus.axis === axis &&
current_focus.name === clicked_name;

viz_state.dendro.clicks[axis] += 1;

viz_state.click.type = `${axis}_dendro`;

viz_state.click.value = {
name: event.object.properties.name,
name: clicked_name,
selected_names: event.object.properties.all_names,
};

focus_dendro_polygon(
deck_mat,
layers_mat,
viz_state,
axis,
event.object.properties.name
);

if (Object.keys(viz_state.model).length > 0) {
viz_state.model.set('click_info', null);
viz_state.model.set('click_info', viz_state.click);
Expand All @@ -181,6 +334,67 @@ const dendro_layer_onclick = (event, deck_mat, layers_mat, viz_state, axis) => {
event.object.properties.all_names
);
}

const double_click_state = {
name: clicked_name,
was_active_before_click,
};

if (viz_state.dendro.clicks[axis] === 1) {
viz_state.dendro.double_click_state[axis] = double_click_state;

focus_dendro_polygon(
deck_mat,
layers_mat,
viz_state,
axis,
clicked_name
);

viz_state.dendro.click_timeouts[axis] = setTimeout(() => {
viz_state.dendro.clicks[axis] = 0;
viz_state.dendro.click_timeouts[axis] = null;
viz_state.dendro.double_click_state[axis] = null;
}, DOUBLE_CLICK_DELAY);
} else if (viz_state.dendro.clicks[axis] === 2) {
if (viz_state.dendro.click_timeouts[axis]) {
clearTimeout(viz_state.dendro.click_timeouts[axis]);
viz_state.dendro.click_timeouts[axis] = null;
}
Comment thread
cornhundred marked this conversation as resolved.

viz_state.dendro.clicks[axis] = 0;

const prior_state = viz_state.dendro.double_click_state[axis];
viz_state.dendro.double_click_state[axis] = null;

const initiated_from_active =
prior_state &&
prior_state.name === clicked_name &&
prior_state.was_active_before_click;

if (initiated_from_active) {
apply_dendro_focus(deck_mat, layers_mat, viz_state, null);
animate_focus_reset(deck_mat, layers_mat, viz_state);
return;
}

focus_dendro_polygon(
deck_mat,
layers_mat,
viz_state,
axis,
clicked_name,
{ allow_toggle: false }
);

animate_focus_to_cluster(
deck_mat,
layers_mat,
viz_state,
axis,
event.object.properties
);
}
};

export const set_dendro_layer_onclick = (
Expand All @@ -192,7 +406,7 @@ export const set_dendro_layer_onclick = (
layers_mat[`${axis}_dendro_layer`] = layers_mat[`${axis}_dendro_layer`].clone(
{
onClick: (event) =>
dendro_layer_onclick(event, deck_mat, layers_mat, viz_state, axis),
dendro_layer_on_click(event, deck_mat, layers_mat, viz_state, axis),
}
);
};
Loading