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
2 changes: 2 additions & 0 deletions js/celldega.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const render_landscape_ist = async ({ model, el }) => {
const nbhd = model.get('nbhd_geojson');
const max_tiles_to_view = model.get('max_tiles_to_view');
const nbhd_edit = model.get('nbhd_edit');
const cell_centroid_size = model.get('cell_centroid_size') ?? 5;
const scale_bar_microns_per_pixel = model.get('scale_bar_microns_per_pixel');

let meta_cell_data = { result: {}, attr: [] };
Expand Down Expand Up @@ -90,6 +91,7 @@ const render_landscape_ist = async ({ model, el }) => {
rotation_x,
rotate,
max_tiles_to_view,
cell_centroid_size,
scale_bar_microns_per_pixel,
base_urls,
cell_name_prefix
Expand Down
3 changes: 3 additions & 0 deletions js/deck-gl/core/deck_ist.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const ini_deck = (root, width, height, technology = '') => {
getCursor,
width,
height,
glOptions: {
preserveDrawingBuffer: true,
},
});

return deck_ist;
Expand Down
8 changes: 6 additions & 2 deletions js/deck-gl/layers/cell_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,10 +391,11 @@ export const ini_cell_layer = async (base_url, viz_state) => {
...getModelMatrixProps(viz_state.rotation),
});
} else {
const initial_cell_radius = viz_state.genes?.cell_ini_radius ?? 5;
cell_layer = new ScatterplotLayer({
id: 'cell-layer',
radiusMinPixels: 1,
getRadius: 5.0,
radiusMinPixels: Math.max(1, initial_cell_radius),
getRadius: initial_cell_radius,
pickable: true,
getFillColor: (i, d) =>
get_cell_color(viz_state.cats, viz_state.highlighted_cells, i, d),
Expand Down Expand Up @@ -435,6 +436,9 @@ export const update_cell_layer_radius = (layers_obj, radius, viz_state) => {
});
} else {
layers_obj.cell_layer = layers_obj.cell_layer.clone({
// Keep marker size adjustable when zoomed out by tying the
// minimum on-screen pixel size to the slider radius.
radiusMinPixels: Math.max(1, radius),
getRadius: radius,
});
}
Expand Down
3 changes: 3 additions & 0 deletions js/deck-gl/matrix/deck_mat.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export const ini_deck = (root, width, height) => {
getCursor,
width,
height,
glOptions: {
preserveDrawingBuffer: true,
},
});

return deck_ist;
Expand Down
2 changes: 1 addition & 1 deletion js/deck-gl/matrix/views.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const ini_views = (viz_state) => {
id: 'col_attr_labels',
x: `${viz_state.viz.row_region + viz_state.viz.label_buffer + viz_state.viz.mat_width}px`,
y: '0px',
width: `${viz_state.viz.dendrogram_width + 60}px`,
width: `${viz_state.viz.dendrogram_width + viz_state.viz.col_attr_label_width}px`,
height: `${viz_state.viz.col_region}px`,
controller: false,
}),
Expand Down
17 changes: 16 additions & 1 deletion js/matrix/set_constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ const parseEntitySpec = (value) => {
return { entity: 'custom', attr: 'name' };
};

const estimate_row_label_width = (row_nodes = []) => {
const min_width = 75;
const max_width = 260;
const avg_char_width_px = 7;

const max_label_length = row_nodes.reduce((max_len, node) => {
const name = String(node?.name ?? '');
return Math.max(max_len, name.length);
}, 0);

const estimated_width = Math.ceil(max_label_length * avg_char_width_px);
return Math.max(min_width, Math.min(max_width, estimated_width));
};

export const set_mat_constants = (
model,
network,
Expand Down Expand Up @@ -141,7 +155,7 @@ export const set_mat_constants = (
viz_state.viz.base_font_size = 125;

viz_state.viz.col_label = 75; // 40
viz_state.viz.row_label = 75; // 35
viz_state.viz.row_label = estimate_row_label_width(network.row_nodes);

viz_state.viz.extra_space = {};
viz_state.viz.extra_space.row = 5; // 10;
Expand All @@ -164,6 +178,7 @@ export const set_mat_constants = (
viz_state.viz.cat_shift_row = 30;

viz_state.viz.label_buffer = 1;
viz_state.viz.col_attr_label_width = 100;

viz_state.animate = {};
viz_state.animate.duration = 2500;
Expand Down
79 changes: 53 additions & 26 deletions js/ui/bar_plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export const bar_callback_nbhd = (
_layers_obj,
_viz_state
) => {
const is_multi_select =
_event?.type === 'contextmenu' || _event?.button === 2;

if (_viz_state.nbhd.edit) {
_viz_state.obs_store.viz_edit_layer.set(true);

Expand All @@ -143,37 +146,46 @@ export const bar_callback_nbhd = (
toggle_slider(_viz_state.sliders.nbhd, true);

const prev_selected_nbhds = _viz_state.obs_store.selected_nbhds.get();
if (
prev_selected_nbhds[0] === _d.name &&
prev_selected_nbhds.length === 1
let next_selected_nbhds;
if (is_multi_select) {
if (prev_selected_nbhds.includes(_d.name)) {
next_selected_nbhds = prev_selected_nbhds.filter((n) => n !== _d.name);
} else {
next_selected_nbhds = [...prev_selected_nbhds, _d.name];
}
} else if (
prev_selected_nbhds.length === 1 &&
prev_selected_nbhds[0] === _d.name
) {
_viz_state.obs_store.selected_nbhds.set([]);
_layers_obj.edit_layer = _layers_obj.edit_layer.clone({
selectedFeatureIndexes: [],
});
next_selected_nbhds = [];
} else {
_viz_state.obs_store.selected_nbhds.set([_d.name]);
// Use edit.feature_collection for edit mode
const features = _viz_state.edit?.feature_collection?.features || [];
const featureIndex = features.findIndex(
(f) => f.properties.name === _d.name || f.properties.cat === _d.name
);
if (featureIndex >= 0) {
_layers_obj.edit_layer = _layers_obj.edit_layer.clone({
selectedFeatureIndexes: [featureIndex],
});
}
next_selected_nbhds = [_d.name];
}
_viz_state.obs_store.selected_nbhds.set(next_selected_nbhds);

// Use edit.feature_collection for edit mode
const features = _viz_state.edit?.feature_collection?.features || [];
const selectedFeatureIndexes = features
.map((f, idx) =>
next_selected_nbhds.includes(f.properties.name) ||
next_selected_nbhds.includes(f.properties.cat)
? idx
: -1
)
.filter((idx) => idx >= 0);
_layers_obj.edit_layer = _layers_obj.edit_layer.clone({
selectedFeatureIndexes,
});

refresh_layer(_viz_state, _layers_obj, 'edit_layer');

if (_viz_state.obs_store.selected_nbhds.get().length > 0) {
const selected_nbhds = _viz_state.obs_store.selected_nbhds.get();
_viz_state.nbhd.svg_bar_nbhd.selectAll('rect').style('opacity', (d) => {
if (d.name === _d.name) {
if (selected_nbhds.includes(d.name)) {
return 1.0;
} else {
return 0.2;
}
return 0.2;
});
} else {
_viz_state.nbhd.svg_bar_nbhd.selectAll('rect').style('opacity', 1.0);
Expand All @@ -194,7 +206,15 @@ export const bar_callback_nbhd = (
}

const prev_selected_nbhds = _viz_state.obs_store.selected_nbhds.get();
if (
if (is_multi_select) {
if (prev_selected_nbhds.includes(_d.name)) {
_viz_state.obs_store.selected_nbhds.set(
prev_selected_nbhds.filter((n) => n !== _d.name)
);
} else {
_viz_state.obs_store.selected_nbhds.set([...prev_selected_nbhds, _d.name]);
}
} else if (
prev_selected_nbhds[0] === _d.name &&
prev_selected_nbhds.length === 1
) {
Expand All @@ -206,12 +226,12 @@ export const bar_callback_nbhd = (
refresh_layer(_viz_state, _layers_obj, 'nbhd_layer');

if (_viz_state.obs_store.selected_nbhds.get().length > 0) {
const selected_nbhds = _viz_state.obs_store.selected_nbhds.get();
_viz_state.nbhd.svg_bar_nbhd.selectAll('rect').style('opacity', (d) => {
if (d.name === _d.name) {
if (selected_nbhds.includes(d.name)) {
return 1.0;
} else {
return 0.2;
}
return 0.2;
});
} else {
_viz_state.nbhd.svg_bar_nbhd.selectAll('rect').style('opacity', 1.0);
Expand Down Expand Up @@ -279,7 +299,14 @@ export const make_bar_graph = (
.attr('transform', (d, i) => `translate(2,${y_new(i) + 2})`)
.on('click', (event, d) =>
click_callback(event, d, deck_ist, layers_obj, viz_state)
);
)
.on('contextmenu', (event, d) => {
// Right-click multi-select for neighborhood bar controls.
if (click_callback === bar_callback_nbhd) {
event.preventDefault();
click_callback(event, d, deck_ist, layers_obj, viz_state);
}
});

bar
.append('rect')
Expand Down
13 changes: 6 additions & 7 deletions js/ui/sliders.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { square_scatter_layer_opacity } from '../deck-gl/layers/square_scatter_l
import { update_trx_layer_radius } from '../deck-gl/layers/trx_layer';
import { refresh_layer } from '../utils/refresh_layer';

const CELL_RADIUS_SLIDER_SCALE = 5;
const TRX_RADIUS_SLIDER_SCALE = 100;

const clamp_to_byte = (value) => {
return Math.max(0, Math.min(255, Math.round(value)));
};
Expand Down Expand Up @@ -208,23 +211,19 @@ const tile_slider_callback = async (deck_sst, viz_state, layers_sst) => {
};

const cell_slider_callback = async (deck_ist, layers_obj, viz_state) => {
const scale_down_cell_radius = 5;

update_cell_layer_radius(
layers_obj,
viz_state.sliders.cell.value / scale_down_cell_radius,
viz_state.sliders.cell.value / CELL_RADIUS_SLIDER_SCALE,
viz_state
);

refresh_layer(viz_state, layers_obj, 'cell_layer');
};

const trx_slider_callback = async (deck_ist, layers_obj, viz_state) => {
const scale_down_trx_radius = 100;

update_trx_layer_radius(
layers_obj,
viz_state.sliders.trx.value / scale_down_trx_radius
viz_state.sliders.trx.value / TRX_RADIUS_SLIDER_SCALE
);

refresh_layer(viz_state, layers_obj, 'trx_layer');
Expand Down Expand Up @@ -302,7 +301,7 @@ export const ini_slider = (slider_type, inst_deck, layers_obj, viz_state) => {
callback = () => tile_slider_callback(inst_deck, viz_state, layers_obj);
break;
case 'cell':
ini_value = viz_state.genes.trx_ini_raidus * 100;
ini_value = viz_state.genes.cell_ini_radius * CELL_RADIUS_SLIDER_SCALE;
callback = () => cell_slider_callback(inst_deck, layers_obj, viz_state);
break;
case 'trx':
Expand Down
24 changes: 18 additions & 6 deletions js/ui/text_buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,8 @@ const cell_button_callback = async (event, deck_ist, layers_obj, viz_state) => {
toggle_path_layer_visibility(layers_obj, is_visible);

if (is_visible) {
toggle_nbhd_layer_visibility(layers_obj, false);
viz_state.obs_store.viz_nbhd_layer.set(false);
viz_state.obs_store.viz_edit_layer.set(false);

if (viz_state.nbhd.is_nbhd) {
viz_state.buttons?.buttons?.nbhd?.style?.('color', 'gray');
}

viz_state.cats.svg_bar_cluster.selectAll('rect').style('opacity', 1.0);
} else {
viz_state.cats.svg_bar_cluster.selectAll('rect').style('opacity', 0.2);
Expand Down Expand Up @@ -286,6 +280,24 @@ const nbhd_button_callback = async (event, deck_ist, layers_obj, viz_state) => {

toggle_slider(viz_state.sliders.nbhd, is_visible);

if (is_visible) {
const selected_nbhds = viz_state.obs_store.selected_nbhds.get();
const available_nbhds = new Set(
(viz_state.nbhd?.feature_collection?.features || []).map(
(feature) => feature.properties.cat
)
);
const has_matching_selection = selected_nbhds.some((nbhd) =>
available_nbhds.has(nbhd)
);

// If a stale neighborhood selection is active, clear it so polygons render.
if (selected_nbhds.length > 0 && !has_matching_selection) {
viz_state.obs_store.selected_nbhds.set([]);
viz_state.nbhd?.svg_bar_nbhd?.selectAll?.('rect')?.style?.('opacity', 1.0);
}
}

toggle_nbhd_layer_visibility(layers_obj, is_visible);

viz_state.obs_store.viz_nbhd_layer.set(is_visible);
Expand Down
Loading
Loading