From b80fc311bdc07e5f1ba6bfe1ecb7896f86168107 Mon Sep 17 00:00:00 2001 From: Nicolas Fernandez Date: Thu, 26 Jun 2025 10:51:26 -0400 Subject: [PATCH] feat(matrix): support attribute labels and adjust matrix size --- docs/javascript/api.md | 52 +++++++++++++++-------- js/deck-gl/matrix/attr_label_layers.js | 59 ++++++++++++++++++++++++++ js/deck-gl/matrix/matrix_layers.js | 6 +++ js/matrix/attr_label_data.js | 32 ++++++++++++++ js/matrix/set_constants.js | 15 +++++++ js/viz/matrix_viz.js | 20 ++++++++- 6 files changed, 165 insertions(+), 19 deletions(-) create mode 100644 js/deck-gl/matrix/attr_label_layers.js create mode 100644 js/matrix/attr_label_data.js diff --git a/docs/javascript/api.md b/docs/javascript/api.md index 677ee57e..1403ddca 100644 --- a/docs/javascript/api.md +++ b/docs/javascript/api.md @@ -2,7 +2,6 @@ The JavaScript component of Celldega is used within the Jupyter Widgets framework to provide interactive visualization in the context of a Jupyter notebook but can also be used as a standalone JavaScript library. - ## `landscape_ist` API Documentation The `landscape_ist` function initializes and renders an interactive spatial transcriptomics (IST) landscape visualization. This API is designed to work with Deck.gl and includes customizable visualization options, dynamic data updates, and UI interactions. @@ -32,9 +31,11 @@ The `landscape_ist` function returns an object (`landscape`) with several method Updates the visualization to highlight data for a specific gene. ##### Parameters + - **`inst_gene`** (`string`): The gene to highlight. ##### Behavior + - Updates the transcript layer to show data for the specified gene. - Scrolls the bar graph to bring the selected gene into view. - Toggles visibility of image layers and controls based on the selected gene. @@ -46,9 +47,11 @@ Updates the visualization to highlight data for a specific gene. Updates the visualization to highlight data for a specific column (e.g., cluster). ##### Parameters + - **`inst_col`** (`string`): The column to highlight. ##### Behavior + - Highlights the bar graph corresponding to the selected column. - Updates cell and path layers to reflect the selected column. - Toggles visibility of layers based on the column selection. @@ -60,9 +63,11 @@ Updates the visualization to highlight data for a specific column (e.g., cluster Updates the visualization based on a dendrogram selection of columns. ##### Parameters + - **`selected_cols`** (`Array`): The list of selected column names. ##### Behavior + - Highlights the selected columns in the bar graph. - Updates layers to reflect the selection. @@ -73,11 +78,13 @@ Updates the visualization based on a dendrogram selection of columns. Updates the view state of the Deck.gl visualization. ##### Parameters + - **`new_view_state`** (`Object`): The new view state configuration. - **`close_up`** (`boolean`): Whether the view should zoom in closely. - **`trx_layer`** (`Object`): The transcript layer to update. ##### Behavior + - Adjusts the viewport and reconfigures layers based on the new view state. --- @@ -87,6 +94,7 @@ Updates the view state of the Deck.gl visualization. Updates all visualization layers. ##### Behavior + - Refreshes the Deck.gl layers with the current visualization state. --- @@ -96,6 +104,7 @@ Updates all visualization layers. Finalizes the Deck.gl instance and cleans up resources. ##### Behavior + - Disposes of all Deck.gl resources and event listeners to prevent memory leaks. --- @@ -155,26 +164,29 @@ The `matrix_viz` function initializes and renders a matrix visualization. This A ### Internal Behavior The function performs the following setup: + 1. **Deck.gl Integration**: + - Initializes a Deck.gl instance for the matrix visualization. - Sets properties for interactivity, including tooltips, view state changes, and layer filtering. 2. **Matrix Data Setup**: + - Parses and structures the matrix data from the `network` object. - - Configures labels, categories, and dendrograms for both rows and columns. + - Configures labels, attributes, and dendrograms for both rows and columns. 3. **Layer Initialization**: + - Creates layers for: - Matrix cells. - Row and column labels. - - Row and column categories. + - Row and column attributes. - Row and column dendrograms. - Attaches interactions (e.g., click events) to these layers. 4. **UI Setup**: - Creates a container for the visualization and appends it to the root DOM element. - --- ### Example Usage @@ -183,31 +195,35 @@ The function performs the following setup: import { matrix_viz } from 'path/to/matrix_viz'; const rootElement = document.getElementById('matrix-container'); -const model = { /* Model containing visualization data */ }; -const network = { /* Network object representing the matrix data */ }; +const model = { + /* Model containing visualization data */ +}; +const network = { + /* Network object representing the matrix data */ +}; // Callback functions const rowLabelCallback = (row) => { - console.log('Row label clicked:', row); + console.log('Row label clicked:', row); }; const colLabelCallback = (col) => { - console.log('Column label clicked:', col); + console.log('Column label clicked:', col); }; const colDendroCallback = (dendro) => { - console.log('Column dendrogram clicked:', dendro); + console.log('Column dendrogram clicked:', dendro); }; // Initialize the matrix visualization await matrix_viz( - model, - rootElement, - network, - 800, - 800, - rowLabelCallback, - colLabelCallback, - colDendroCallback + model, + rootElement, + network, + 800, + 800, + rowLabelCallback, + colLabelCallback, + colDendroCallback ); -``` \ No newline at end of file +``` diff --git a/js/deck-gl/matrix/attr_label_layers.js b/js/deck-gl/matrix/attr_label_layers.js new file mode 100644 index 00000000..6a41a83d --- /dev/null +++ b/js/deck-gl/matrix/attr_label_layers.js @@ -0,0 +1,59 @@ +import * as d3 from 'd3'; +import { TextLayer } from 'deck.gl'; + +export const ini_row_attr_label_layer = (viz_state) => { + const transitions = { + getPosition: { duration: viz_state.animate.duration, easing: d3.easeCubic }, + }; + + const layer = new TextLayer({ + id: 'row-attr-label-layer', + data: viz_state.cats.row_attr_label_data, + getPosition: (d) => [ + viz_state.viz.row_cat_offset * (d.index + 0.5) + + 20 + + viz_state.viz.cat_shift_row, + 10, + ], + getText: (d) => d.name, + getSize: 12, + getColor: [0, 0, 0], + getTextAnchor: 'start', + getAlignmentBaseline: 'bottom', + fontFamily: 'Arial', + sizeUnits: 'pixels', + sizeScale: 1, + pickable: false, + transitions, + }); + + return layer; +}; + +export const ini_col_attr_label_layer = (viz_state) => { + const transitions = { + getPosition: { duration: viz_state.animate.duration, easing: d3.easeCubic }, + }; + + const layer = new TextLayer({ + id: 'col-attr-label-layer', + data: viz_state.cats.col_attr_label_data, + getPosition: (d) => [ + 10, + viz_state.viz.col_cat_offset * (d.index + 0.5) + + viz_state.viz.cat_shift_col, + ], + getText: (d) => d.name, + getSize: 12, + getColor: [0, 0, 0], + getTextAnchor: 'start', + getAlignmentBaseline: 'middle', + fontFamily: 'Arial', + sizeUnits: 'pixels', + sizeScale: 1, + pickable: false, + transitions, + }); + + return layer; +}; diff --git a/js/deck-gl/matrix/matrix_layers.js b/js/deck-gl/matrix/matrix_layers.js index e5219ce9..433228ae 100644 --- a/js/deck-gl/matrix/matrix_layers.js +++ b/js/deck-gl/matrix/matrix_layers.js @@ -3,6 +3,8 @@ export const get_mat_layers_list = (layers_mat) => { layers_mat.mat_layer, layers_mat.row_cat_layer, layers_mat.col_cat_layer, + layers_mat.row_attr_label_layer, + layers_mat.col_attr_label_layer, layers_mat.row_label_layer, layers_mat.col_label_layer, layers_mat.row_dendro_layer, @@ -21,8 +23,12 @@ export const layer_filter = ({ layer, viewport }) => { return true; } else if (viewport.id === 'rows' && layer.id.includes('row-label-layer')) { return true; + } else if (viewport.id === 'rows' && layer.id === 'row-attr-label-layer') { + return true; } else if (viewport.id === 'cols' && layer.id === 'col-label-layer') { return true; + } else if (viewport.id === 'cols' && layer.id === 'col-attr-label-layer') { + return true; } else if ( (viewport.id === 'dendro_rows') & (layer.id === 'row-dendro-layer') diff --git a/js/matrix/attr_label_data.js b/js/matrix/attr_label_data.js new file mode 100644 index 00000000..7e178e10 --- /dev/null +++ b/js/matrix/attr_label_data.js @@ -0,0 +1,32 @@ +export const set_attr_label_data = (network, viz_state, axis) => { + const isRow = axis === 'row'; + const nodes = isRow ? network.row_nodes : network.col_nodes; + const numAttrs = isRow + ? viz_state.cats.num_cats.row + : viz_state.cats.num_cats.col; + + const titles = []; + for (let i = 0; i < numAttrs; i++) { + const catName = `cat-${i}`; + let example = nodes.find((n) => n[catName]); + let title = `attr-${i}`; + if (example) { + const val = example[catName]; + if (typeof val === 'string' && val.includes(':')) { + title = val.split(':')[0]; + } + } + titles.push({ name: title, index: i }); + } + + if (!viz_state.cats.attr_titles) viz_state.cats.attr_titles = {}; + viz_state.cats.attr_titles[axis] = titles.map((d) => d.name); + + return titles; +}; + +export const set_row_attr_label_data = (network, viz_state) => + set_attr_label_data(network, viz_state, 'row'); + +export const set_col_attr_label_data = (network, viz_state) => + set_attr_label_data(network, viz_state, 'col'); diff --git a/js/matrix/set_constants.js b/js/matrix/set_constants.js index 85d5182d..f9ce904e 100644 --- a/js/matrix/set_constants.js +++ b/js/matrix/set_constants.js @@ -106,6 +106,21 @@ export const set_mat_constants = ( viz_state.cats.num_cats.row + viz_state.viz.row_label; + // reduce matrix size to make room for attribute bars + viz_state.viz.mat_width = + width - + viz_state.viz.row_region - + viz_state.viz.dendrogram_width - + viz_state.viz.label_buffer; + viz_state.viz.mat_height = + height - + viz_state.viz.col_region - + viz_state.viz.dendrogram_width - + viz_state.viz.label_buffer; + + viz_state.viz.mat_width = Math.max(20, viz_state.viz.mat_width); + viz_state.viz.mat_height = Math.max(20, viz_state.viz.mat_height); + viz_state.viz.col_width = viz_state.viz.mat_width / viz_state.mat.num_cols; viz_state.viz.row_offset = viz_state.viz.mat_height / viz_state.mat.num_rows; viz_state.viz.col_offset = viz_state.viz.mat_width / viz_state.mat.num_cols; diff --git a/js/viz/matrix_viz.js b/js/viz/matrix_viz.js index 92acdf91..33df5774 100644 --- a/js/viz/matrix_viz.js +++ b/js/viz/matrix_viz.js @@ -29,6 +29,10 @@ import { ini_row_cat_layer, ini_col_cat_layer, } from '../deck-gl/matrix/cat_layers'; +import { + ini_row_attr_label_layer, + ini_col_attr_label_layer, +} from '../deck-gl/matrix/attr_label_layers'; import { ini_deck } from '../deck-gl/matrix/deck_mat'; import { ini_dendro_layer, @@ -53,6 +57,10 @@ import { on_view_state_change } from '../deck-gl/matrix/on_view_state_change'; import { ini_views, ini_view_state } from '../deck-gl/matrix/views'; import { ini_zoom_data } from '../deck-gl/matrix/zoom'; import { set_row_cat_data, set_col_cat_data } from '../matrix/cat_data'; +import { + set_row_attr_label_data, + set_col_attr_label_data, +} from '../matrix/attr_label_data'; import { calc_dendro_polygons, ini_dendro } from '../matrix/dendro'; import { set_row_label_data, set_col_label_data } from '../matrix/label_data'; import { set_mat_data } from '../matrix/mat_data'; @@ -69,7 +77,6 @@ export const matrix_viz = async ( col_label_callback = null, col_dendro_callback = null ) => { - const root = document.createElement('div'); root.style.border = '1px solid #d3d3d3'; // root.style.width = width @@ -102,6 +109,15 @@ export const matrix_viz = async ( set_row_label_data(network, viz_state); set_col_label_data(network, viz_state); + viz_state.cats.row_attr_label_data = set_row_attr_label_data( + network, + viz_state + ); + viz_state.cats.col_attr_label_data = set_col_attr_label_data( + network, + viz_state + ); + viz_state.cats.row_cat_data = set_row_cat_data(network, viz_state); viz_state.cats.col_cat_data = set_col_cat_data(network, viz_state); @@ -115,6 +131,8 @@ export const matrix_viz = async ( layers_mat.mat_layer = ini_mat_layer(viz_state); layers_mat.row_label_layer = ini_row_label_layer(viz_state); layers_mat.col_label_layer = ini_col_label_layer(viz_state); + layers_mat.row_attr_label_layer = ini_row_attr_label_layer(viz_state); + layers_mat.col_attr_label_layer = ini_col_attr_label_layer(viz_state); layers_mat.row_cat_layer = ini_row_cat_layer(viz_state); layers_mat.col_cat_layer = ini_col_cat_layer(viz_state); layers_mat.row_dendro_layer = ini_dendro_layer(layers_mat, viz_state, 'row');