Skip to content
Open
Show file tree
Hide file tree
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
188 changes: 94 additions & 94 deletions docs/assets/js/widget.js

Large diffs are not rendered by default.

84 changes: 66 additions & 18 deletions js/deck-gl/core/calc_viewport.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { rotate_point, rotate_point_inverse } from '../../utils/rotation';
import { visibleTiles } from '../../vector_tile/visibleTiles';
import { update_path_layer_data } from '../layers/path_layer';
import { update_trx_layer_data } from '../layers/trx_layer';
Expand Down Expand Up @@ -27,11 +28,34 @@ export const calc_viewport = async (
return;
}

const tile_bounds = (() => {
if (!viz_state.rotation?.hasRotation) {
return viz_state.bounds;
}

const corners = [
[viz_state.bounds.min_x, viz_state.bounds.min_y],
[viz_state.bounds.min_x, viz_state.bounds.max_y],
[viz_state.bounds.max_x, viz_state.bounds.min_y],
[viz_state.bounds.max_x, viz_state.bounds.max_y],
].map(([x, y]) => rotate_point_inverse(x, y, viz_state.rotation));

const xs = corners.map(([x]) => x);
const ys = corners.map(([, y]) => y);

return {
min_x: Math.min(...xs),
max_x: Math.max(...xs),
min_y: Math.min(...ys),
max_y: Math.max(...ys),
};
})();

const tiles_in_view = visibleTiles(
viz_state.bounds.min_x,
viz_state.bounds.max_x,
viz_state.bounds.min_y,
viz_state.bounds.max_y,
tile_bounds.min_x,
tile_bounds.max_x,
tile_bounds.min_y,
tile_bounds.max_y,
tile_size
);

Expand Down Expand Up @@ -59,13 +83,25 @@ export const calc_viewport = async (
);

// gene bar graph update
const filtered_transcripts = viz_state.combo_data.trx.filter(
(pos) =>
pos.x >= viz_state.bounds.min_x &&
pos.x <= viz_state.bounds.max_x &&
pos.y >= viz_state.bounds.min_y &&
pos.y <= viz_state.bounds.max_y
);
const filtered_transcripts = viz_state.combo_data.trx.filter((pos) => {
if (!viz_state.rotation?.hasRotation) {
return (
pos.x >= viz_state.bounds.min_x &&
pos.x <= viz_state.bounds.max_x &&
pos.y >= viz_state.bounds.min_y &&
pos.y <= viz_state.bounds.max_y
);
}

const [rotX, rotY] = rotate_point(pos.x, pos.y, viz_state.rotation);

return (
rotX >= viz_state.bounds.min_x &&
rotX <= viz_state.bounds.max_x &&
rotY >= viz_state.bounds.min_y &&
rotY <= viz_state.bounds.max_y
);
});

const filtered_gene_names = filtered_transcripts.map(
(transcript) => transcript.name
Expand All @@ -88,13 +124,25 @@ export const calc_viewport = async (
viz_state.obs_store.new_gene_bar_data.set(new_bar_data);

// cell bar graph update
const filtered_cells = viz_state.combo_data.cell.filter(
(pos) =>
pos.x >= viz_state.bounds.min_x &&
pos.x <= viz_state.bounds.max_x &&
pos.y >= viz_state.bounds.min_y &&
pos.y <= viz_state.bounds.max_y
);
const filtered_cells = viz_state.combo_data.cell.filter((pos) => {
if (!viz_state.rotation?.hasRotation) {
return (
pos.x >= viz_state.bounds.min_x &&
pos.x <= viz_state.bounds.max_x &&
pos.y >= viz_state.bounds.min_y &&
pos.y <= viz_state.bounds.max_y
);
}

const [rotX, rotY] = rotate_point(pos.x, pos.y, viz_state.rotation);

return (
rotX >= viz_state.bounds.min_x &&
rotX <= viz_state.bounds.max_x &&
rotY >= viz_state.bounds.min_y &&
rotY <= viz_state.bounds.max_y
);
});

const filtered_cell_names = filtered_cells.map((cell) => cell.cat);

Expand Down
3 changes: 3 additions & 0 deletions js/deck-gl/layers/background_layer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { SolidPolygonLayer } from 'deck.gl';

import { getModelMatrixProps } from '../../utils/rotation';

// Function to create a background layer
export const ini_background_layer = (viz_state) => {
const background_color = [0, 0, 0, 255];
Expand All @@ -21,6 +23,7 @@ export const ini_background_layer = (viz_state) => {
getPolygon: (d) => d.polygon,
getFillColor: background_color,
pickable: false,
...getModelMatrixProps(viz_state.rotation),
});

return background_layer;
Expand Down
3 changes: 3 additions & 0 deletions js/deck-gl/layers/cell_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { update_selected_genes } from '../../global_variables/selected_genes';
import { get_arrow_table } from '../../read_parquet/get_arrow_table';
import { get_scatter_data } from '../../read_parquet/get_scatter_data';
import { scale_umap_data } from '../../umap/scale_umap_data';
import { getModelMatrixProps } from '../../utils/rotation';

const cell_layer_onclick = async (info, d, deck_ist, layers_obj, viz_state) => {
// Check if the device is a touch device
Expand Down Expand Up @@ -305,6 +306,7 @@ export const ini_cell_layer = async (base_url, viz_state) => {
getPosition: [viz_state.obs_store.umap_state.get()],
},
opacity: 1,
...getModelMatrixProps(viz_state.rotation),
});
} else {
cell_layer = new ScatterplotLayer({
Expand All @@ -320,6 +322,7 @@ export const ini_cell_layer = async (base_url, viz_state) => {
updateTriggers: {
getPosition: [viz_state.obs_store.umap_state.get()],
},
...getModelMatrixProps(viz_state.rotation),
});
}

Expand Down
2 changes: 2 additions & 0 deletions js/deck-gl/layers/edit_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as d3 from 'd3';

import { handleValidationWarning } from '../../temp_utils/errorHandler';
import { make_bar_graph, bar_callback_nbhd } from '../../ui/bar_plot';
import { getModelMatrixProps } from '../../utils/rotation';
import { get_layers_list } from '../utils/layers_ist';

import { update_cell_pickable_state } from './cell_layer';
Expand Down Expand Up @@ -228,6 +229,7 @@ export const ini_edit_layer = (viz_state) => {
},
visible: false,
opacity: viz_state.edit.rgn_opacity,
...getModelMatrixProps(viz_state.rotation),
});

return edit_layer;
Expand Down
2 changes: 2 additions & 0 deletions js/deck-gl/layers/image_layers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TileLayer } from 'deck.gl';

import { options } from '../../global_variables/fetch_options';
import { getModelMatrixProps } from '../../utils/rotation';
import {
create_get_tile_data,
create_render_tile_sublayers,
Expand Down Expand Up @@ -34,6 +35,7 @@ const make_image_layer = (viz_state, info) => {
info.color,
opacity
),
...getModelMatrixProps(viz_state.rotation),
});
return image_layer;
};
Expand Down
2 changes: 2 additions & 0 deletions js/deck-gl/layers/nbhd_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GeoJsonLayer } from 'deck.gl';

import { hexToRgb } from '../../utils/hexToRgb';
import { refresh_layer } from '../../utils/refresh_layer';
import { getModelMatrixProps } from '../../utils/rotation';

const get_nbhd_color = (d, viz_state) => {
const inst_color = hexToRgb(d.properties.color);
Expand Down Expand Up @@ -40,6 +41,7 @@ export const ini_nbhd_layer = (viz_state, visible) => {
getFillColor: (d) => get_nbhd_color(d, viz_state),
opacity: 0.5,
visible,
...getModelMatrixProps(viz_state.rotation),
});

return nbhd_layer;
Expand Down
2 changes: 2 additions & 0 deletions js/deck-gl/layers/path_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PathLayer } from 'deck.gl';

import { update_selected_cats, update_cat } from '../../global_variables/cat';
import { update_selected_genes } from '../../global_variables/selected_genes';
import { getModelMatrixProps } from '../../utils/rotation';
import { grab_cell_tiles_in_view } from '../../vector_tile/polygons/grab_cell_tiles_in_view';

export const get_path_color = (cats, i, d) => {
Expand Down Expand Up @@ -36,6 +37,7 @@ export const ini_path_layer = (viz_state) => {
getPath: (d) => d,
getColor: (i, d) => get_path_color(viz_state.cats, i, d),
widthUnits: 'pixels',
...getModelMatrixProps(viz_state.rotation),
});

return path_layer;
Expand Down
2 changes: 2 additions & 0 deletions js/deck-gl/layers/simple_image_layer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TileLayer } from 'deck.gl';

import { options } from '../../global_variables/fetch_options';
import { getModelMatrixProps } from '../../utils/rotation';
import {
create_simple_render_tile_sublayers,
create_get_tile_data,
Expand Down Expand Up @@ -30,6 +31,7 @@ export const make_simple_image_layer = async (viz_state, info) => {
),
renderSubLayers: create_simple_render_tile_sublayers(dimensions),
visible: true,
...getModelMatrixProps(viz_state.rotation),
});

return simple_image_layer;
Expand Down
8 changes: 6 additions & 2 deletions js/deck-gl/layers/trx_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ScatterplotLayer } from 'deck.gl';
import { update_cat, update_selected_cats } from '../../global_variables/cat';
import { update_cell_exp_array } from '../../global_variables/cell_exp_array';
import { update_selected_genes } from '../../global_variables/selected_genes';
import { getModelMatrixProps } from '../../utils/rotation';
import { grab_trx_tiles_in_view } from '../../vector_tile/transcripts/grab_trx_tiles_in_view';

const trx_layer_callback = async (
Expand Down Expand Up @@ -46,7 +47,9 @@ const trx_layer_callback = async (
);
};

export const ini_trx_layer = (genes) => {
export const ini_trx_layer = (viz_state) => {
const { genes } = viz_state;

const trx_layer = new ScatterplotLayer({
id: 'trx-layer',
data: genes.trx_data,
Expand All @@ -58,10 +61,11 @@ export const ini_trx_layer = (genes) => {
genes.selected_genes.length === 0 ||
genes.selected_genes.includes(inst_gene)
? 255
: 5;
: 5;

return [...inst_color, inst_opacity];
},
...getModelMatrixProps(viz_state.rotation),
});

return trx_layer;
Expand Down
57 changes: 57 additions & 0 deletions js/utils/rotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Matrix4 } from '@math.gl/core';

const EPSILON = 1e-6;

export const build_rotation_state = (angleDegrees = 0, center = [0, 0]) => {
const angleRadians = (angleDegrees * Math.PI) / 180;
const hasRotation = Math.abs(angleRadians) > EPSILON;
const sin = Math.sin(angleRadians);
const cos = Math.cos(angleRadians);

const rotationState = {
angleDegrees,
angleRadians,
hasRotation,
sin,
cos,
center,
modelMatrix: null,
};

if (hasRotation) {
const matrix = new Matrix4()
.translate([center[0], center[1], 0])
.rotateZ(angleRadians)
.translate([-center[0], -center[1], 0]);
rotationState.modelMatrix = Array.from(matrix);
}

return rotationState;
};

export const getModelMatrixProps = (rotationState) => {
if (rotationState?.hasRotation && rotationState.modelMatrix) {
return { modelMatrix: rotationState.modelMatrix };
}
return {};
};

export const rotate_point = (x, y, rotationState) => {
if (!rotationState?.hasRotation) {
return [x, y];
}
const { center, sin, cos } = rotationState;
const dx = x - center[0];
const dy = y - center[1];
return [cos * dx - sin * dy + center[0], sin * dx + cos * dy + center[1]];
};

export const rotate_point_inverse = (x, y, rotationState) => {
if (!rotationState?.hasRotation) {
return [x, y];
}
const { center, sin, cos } = rotationState;
const dx = x - center[0];
const dy = y - center[1];
return [cos * dx + sin * dy + center[0], -sin * dx + cos * dy + center[1]];
};
12 changes: 11 additions & 1 deletion js/viz/landscape_ist.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { toggle_slider, set_image_layer_sliders } from '../ui/sliders';
import { get_img_layer_visible } from '../ui/text_buttons';
import { make_ist_ui_container } from '../ui/ui_containers';
import { refresh_layer } from '../utils/refresh_layer';
import { build_rotation_state } from '../utils/rotation';
import { update_cell_clusters } from '../widget_interactions/update_cell_clusters';
import { update_ist_landscape_from_cgm } from '../widget_interactions/update_ist_landscape_from_cgm';

Expand Down Expand Up @@ -96,6 +97,7 @@ export const landscape_ist = async (
view_change_custom_callback = null,
rotation_orbit = 0,
rotation_x = 0,
rotate = 0,
max_tiles_to_view = 50
) => {
if (width === 0) {
Expand Down Expand Up @@ -350,6 +352,14 @@ export const landscape_ist = async (
await set_dimensions(viz_state, base_url, image_name_for_dim);
}

const centerX = viz_state.dimensions?.width
? viz_state.dimensions.width / 2
: 0;
const centerY = viz_state.dimensions?.height
? viz_state.dimensions.height / 2
: 0;
viz_state.rotation = build_rotation_state(rotate, [centerX, centerY]);

await set_meta_gene(
viz_state.genes,
base_url,
Expand Down Expand Up @@ -406,7 +416,7 @@ export const landscape_ist = async (
const image_layers = await make_image_layers(viz_state);
const cell_layer = await ini_cell_layer(base_url, viz_state);
const path_layer = await ini_path_layer(viz_state);
const trx_layer = ini_trx_layer(viz_state.genes);
const trx_layer = ini_trx_layer(viz_state);
const edit_layer = ini_edit_layer(viz_state);
const nbhd_layer = ini_nbhd_layer(viz_state, true);

Expand Down
2 changes: 2 additions & 0 deletions js/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const render_landscape_ist = async ({ model, el }) => {
const height = model.get('height');
const rotation_orbit = model.get('rotation_orbit') ?? 0;
const rotation_x = model.get('rotation_x') ?? 0;
const rotate = model.get('rotate') ?? 0;
const nbhd = model.get('nbhd_geojson');
const max_tiles_to_view = model.get('max_tiles_to_view');
const nbhd_edit = model.get('nbhd_edit');
Expand Down Expand Up @@ -84,6 +85,7 @@ const render_landscape_ist = async ({ model, el }) => {
null,
rotation_orbit,
rotation_x,
rotate,
max_tiles_to_view
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/celldega/viz/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Landscape(anywidget.AnyWidget):
rotation_x (float, optional): Rotating angle around X axis for point-cloud views.
token (str): The token traitlet.
base_url (str): The base URL for the widget.
rotate (float, optional): Degrees to rotate the 2D landscape visualization.
AnnData (AnnData, optional): AnnData object to derive metadata from.
dataset_name (str, optional): The name of the dataset to visualize. This will show up in the user interface bar.

Expand Down Expand Up @@ -84,6 +85,7 @@ class Landscape(anywidget.AnyWidget):
ini_zoom = traitlets.Float(0).tag(sync=True)
rotation_orbit = traitlets.Float(0).tag(sync=True)
rotation_x = traitlets.Float(0).tag(sync=True)
rotate = traitlets.Float(0).tag(sync=True)
square_tile_size = traitlets.Float(1.4).tag(sync=True)
dataset_name = traitlets.Unicode("").tag(sync=True)
region = traitlets.Dict({}).tag(sync=True)
Expand Down
Loading