openms-insight 0.1.4__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {openms_insight-0.1.4 → openms_insight-0.1.6}/.gitignore +1 -0
  2. {openms_insight-0.1.4 → openms_insight-0.1.6}/PKG-INFO +101 -2
  3. {openms_insight-0.1.4 → openms_insight-0.1.6}/README.md +100 -1
  4. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/__init__.py +3 -1
  5. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/components/__init__.py +2 -0
  6. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/components/heatmap.py +69 -15
  7. openms_insight-0.1.6/openms_insight/components/volcanoplot.py +374 -0
  8. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/base.py +10 -1
  9. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/index.css +1 -1
  10. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/index.js +148 -139
  11. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/preprocessing/__init__.py +6 -0
  12. openms_insight-0.1.6/openms_insight/preprocessing/scatter.py +136 -0
  13. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/rendering/bridge.py +32 -6
  14. {openms_insight-0.1.4 → openms_insight-0.1.6}/pyproject.toml +1 -1
  15. {openms_insight-0.1.4 → openms_insight-0.1.6}/LICENSE +0 -0
  16. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/components/lineplot.py +0 -0
  17. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/components/sequenceview.py +0 -0
  18. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/components/table.py +0 -0
  19. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/__init__.py +0 -0
  20. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/cache.py +0 -0
  21. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/registry.py +0 -0
  22. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/state.py +0 -0
  23. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/core/subprocess_preprocess.py +0 -0
  24. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/materialdesignicons-webfont.eot +0 -0
  25. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/materialdesignicons-webfont.ttf +0 -0
  26. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff +0 -0
  27. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff2 +0 -0
  28. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/js-component/dist/index.html +0 -0
  29. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/preprocessing/compression.py +0 -0
  30. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/preprocessing/filtering.py +0 -0
  31. {openms_insight-0.1.4 → openms_insight-0.1.6}/openms_insight/rendering/__init__.py +0 -0
@@ -61,3 +61,4 @@ Thumbs.db
61
61
 
62
62
  # Logs
63
63
  *.log
64
+ CLAUDE.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openms-insight
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Interactive visualization components for mass spectrometry data in Streamlit
5
5
  Project-URL: Homepage, https://github.com/t0mdavid-m/OpenMS-Insight
6
6
  Project-URL: Documentation, https://github.com/t0mdavid-m/OpenMS-Insight#readme
@@ -50,6 +50,7 @@ Interactive visualization components for mass spectrometry data in Streamlit, ba
50
50
  - **Table component** (Tabulator.js) with filtering, sorting, go-to, pagination, CSV export
51
51
  - **Line plot component** (Plotly.js) with highlighting, annotations, zoom
52
52
  - **Heatmap component** (Plotly scattergl) with multi-resolution downsampling for millions of points
53
+ - **Volcano plot component** for differential expression visualization with significance thresholds
53
54
  - **Sequence view component** for peptide visualization with fragment ion matching and auto-zoom
54
55
 
55
56
  ## Installation
@@ -62,7 +63,7 @@ pip install openms-insight
62
63
 
63
64
  ```python
64
65
  import streamlit as st
65
- from openms_insight import Table, LinePlot, StateManager
66
+ from openms_insight import Table, LinePlot, Heatmap, VolcanoPlot, StateManager
66
67
 
67
68
  # Create state manager for cross-component linking
68
69
  state_manager = StateManager()
@@ -169,6 +170,21 @@ Table(
169
170
  - `pagination`: Enable pagination for large tables (default: True)
170
171
  - `page_size`: Rows per page (default: 100)
171
172
 
173
+ **Custom formatters:**
174
+ In addition to Tabulator's built-in formatters, these custom formatters are available:
175
+ - `scientific`: Exponential notation (e.g., "1.23e-05") - use `formatterParams: {precision: 3}`
176
+ - `signed`: Explicit +/- prefix (e.g., "+1.234") - use `formatterParams: {precision: 3, showPositive: true}`
177
+ - `badge`: Colored pill/badge for categorical values - use `formatterParams: {colorMap: {"Up": "#FF0000"}, defaultColor: "#888"}`
178
+
179
+ ```python
180
+ column_definitions=[
181
+ {'field': 'pvalue', 'title': 'P-value', 'formatter': 'scientific', 'formatterParams': {'precision': 2}},
182
+ {'field': 'log2fc', 'title': 'Log2 FC', 'formatter': 'signed', 'formatterParams': {'precision': 3}},
183
+ {'field': 'regulation', 'title': 'Status', 'formatter': 'badge',
184
+ 'formatterParams': {'colorMap': {'Up': '#d62728', 'Down': '#1f77b4', 'NS': '#888888'}}},
185
+ ]
186
+ ```
187
+
172
188
  ### LinePlot
173
189
 
174
190
  Stick-style line plot using Plotly.js for mass spectra visualization.
@@ -227,6 +243,80 @@ Heatmap(
227
243
  - `min_points`: Target size for downsampling (default: 20000)
228
244
  - `x_bins`, `y_bins`: Grid resolution for spatial binning
229
245
  - `colorscale`: Plotly colorscale name (default: 'Portland')
246
+ - `log_scale`: Use log10 color mapping (default: True). Set to False for linear.
247
+ - `intensity_label`: Custom colorbar label (default: 'Intensity')
248
+
249
+ **Linear scale example:**
250
+ ```python
251
+ Heatmap(
252
+ cache_id="psm_scores",
253
+ data_path="psm_data.parquet",
254
+ x_column='rt',
255
+ y_column='mz',
256
+ intensity_column='score',
257
+ log_scale=False, # Linear color mapping
258
+ intensity_label='Score', # Custom colorbar label
259
+ colorscale='Blues',
260
+ )
261
+ ```
262
+
263
+ **Categorical mode:**
264
+ Use `category_column` for discrete coloring by category instead of continuous intensity colorscale:
265
+
266
+ ```python
267
+ Heatmap(
268
+ cache_id="samples_heatmap",
269
+ data_path="samples.parquet",
270
+ x_column='retention_time',
271
+ y_column='mass',
272
+ intensity_column='intensity',
273
+ category_column='sample_group', # Color by category instead of intensity
274
+ category_colors={ # Optional custom colors
275
+ 'Control': '#1f77b4',
276
+ 'Treatment_A': '#ff7f0e',
277
+ 'Treatment_B': '#2ca02c',
278
+ },
279
+ )
280
+ ```
281
+
282
+ ### VolcanoPlot
283
+
284
+ Interactive volcano plot for differential expression analysis with significance thresholds.
285
+
286
+ ```python
287
+ from openms_insight import VolcanoPlot
288
+
289
+ VolcanoPlot(
290
+ cache_id="de_volcano",
291
+ data_path="differential_expression.parquet",
292
+ log2fc_column='log2FC',
293
+ pvalue_column='pvalue',
294
+ label_column='protein_name', # Optional: labels for significant points
295
+ filters={'comparison': 'comparison_id'},
296
+ interactivity={'protein': 'protein_id'},
297
+ title="Differential Expression",
298
+ x_label="Log2 Fold Change",
299
+ y_label="-log10(p-value)",
300
+ up_color='#d62728', # Color for up-regulated
301
+ down_color='#1f77b4', # Color for down-regulated
302
+ ns_color='#888888', # Color for not significant
303
+ )(
304
+ state_manager=state_manager,
305
+ fc_threshold=1.0, # Fold change threshold (render-time)
306
+ p_threshold=0.05, # P-value threshold (render-time)
307
+ max_labels=20, # Max labels to show
308
+ )
309
+ ```
310
+
311
+ **Key parameters:**
312
+ - `log2fc_column`: Column with log2 fold change values
313
+ - `pvalue_column`: Column with p-values (automatically converted to -log10)
314
+ - `label_column`: Optional column for point labels
315
+ - `up_color`, `down_color`, `ns_color`: Colors for significance categories
316
+ - `fc_threshold`, `p_threshold`: Significance thresholds (passed at render time, not cached)
317
+ - `max_labels`: Maximum number of labels to display on significant points
318
+
319
+ **Render-time thresholds:** The `fc_threshold` and `p_threshold` are passed via `__call__()`, not `__init__()`. This allows instant threshold adjustment without cache invalidation.
230
320
 
231
321
  ### SequenceView
232
322
 
@@ -290,6 +380,7 @@ All components accept these common arguments:
290
380
  | `interactivity` | `Dict[str, str]` | `None` | Map identifier -> column for click actions |
291
381
  | `cache_path` | `str` | `"."` | Base directory for cache storage |
292
382
  | `regenerate_cache` | `bool` | `False` | Force cache regeneration |
383
+ | `height` | `int` | `400` | Component height in pixels (render-time parameter) |
293
384
 
294
385
  ## Memory-Efficient Preprocessing
295
386
 
@@ -373,6 +464,14 @@ npm run dev
373
464
  SVC_DEV_MODE=true SVC_DEV_URL=http://localhost:5173 streamlit run app.py
374
465
  ```
375
466
 
467
+ ### Debug Mode
468
+
469
+ Enable hash tracking logs to debug data synchronization issues:
470
+
471
+ ```bash
472
+ SVC_DEBUG_HASH=true streamlit run app.py
473
+ ```
474
+
376
475
  ### Running Tests
377
476
 
378
477
  ```bash
@@ -15,6 +15,7 @@ Interactive visualization components for mass spectrometry data in Streamlit, ba
15
15
  - **Table component** (Tabulator.js) with filtering, sorting, go-to, pagination, CSV export
16
16
  - **Line plot component** (Plotly.js) with highlighting, annotations, zoom
17
17
  - **Heatmap component** (Plotly scattergl) with multi-resolution downsampling for millions of points
18
+ - **Volcano plot component** for differential expression visualization with significance thresholds
18
19
  - **Sequence view component** for peptide visualization with fragment ion matching and auto-zoom
19
20
 
20
21
  ## Installation
@@ -27,7 +28,7 @@ pip install openms-insight
27
28
 
28
29
  ```python
29
30
  import streamlit as st
30
- from openms_insight import Table, LinePlot, StateManager
31
+ from openms_insight import Table, LinePlot, Heatmap, VolcanoPlot, StateManager
31
32
 
32
33
  # Create state manager for cross-component linking
33
34
  state_manager = StateManager()
@@ -134,6 +135,21 @@ Table(
134
135
  - `pagination`: Enable pagination for large tables (default: True)
135
136
  - `page_size`: Rows per page (default: 100)
136
137
 
138
+ **Custom formatters:**
139
+ In addition to Tabulator's built-in formatters, these custom formatters are available:
140
+ - `scientific`: Exponential notation (e.g., "1.23e-05") - use `formatterParams: {precision: 3}`
141
+ - `signed`: Explicit +/- prefix (e.g., "+1.234") - use `formatterParams: {precision: 3, showPositive: true}`
142
+ - `badge`: Colored pill/badge for categorical values - use `formatterParams: {colorMap: {"Up": "#FF0000"}, defaultColor: "#888"}`
143
+
144
+ ```python
145
+ column_definitions=[
146
+ {'field': 'pvalue', 'title': 'P-value', 'formatter': 'scientific', 'formatterParams': {'precision': 2}},
147
+ {'field': 'log2fc', 'title': 'Log2 FC', 'formatter': 'signed', 'formatterParams': {'precision': 3}},
148
+ {'field': 'regulation', 'title': 'Status', 'formatter': 'badge',
149
+ 'formatterParams': {'colorMap': {'Up': '#d62728', 'Down': '#1f77b4', 'NS': '#888888'}}},
150
+ ]
151
+ ```
152
+
137
153
  ### LinePlot
138
154
 
139
155
  Stick-style line plot using Plotly.js for mass spectra visualization.
@@ -192,6 +208,80 @@ Heatmap(
192
208
  - `min_points`: Target size for downsampling (default: 20000)
193
209
  - `x_bins`, `y_bins`: Grid resolution for spatial binning
194
210
  - `colorscale`: Plotly colorscale name (default: 'Portland')
211
+ - `log_scale`: Use log10 color mapping (default: True). Set to False for linear.
212
+ - `intensity_label`: Custom colorbar label (default: 'Intensity')
213
+
214
+ **Linear scale example:**
215
+ ```python
216
+ Heatmap(
217
+ cache_id="psm_scores",
218
+ data_path="psm_data.parquet",
219
+ x_column='rt',
220
+ y_column='mz',
221
+ intensity_column='score',
222
+ log_scale=False, # Linear color mapping
223
+ intensity_label='Score', # Custom colorbar label
224
+ colorscale='Blues',
225
+ )
226
+ ```
227
+
228
+ **Categorical mode:**
229
+ Use `category_column` for discrete coloring by category instead of continuous intensity colorscale:
230
+
231
+ ```python
232
+ Heatmap(
233
+ cache_id="samples_heatmap",
234
+ data_path="samples.parquet",
235
+ x_column='retention_time',
236
+ y_column='mass',
237
+ intensity_column='intensity',
238
+ category_column='sample_group', # Color by category instead of intensity
239
+ category_colors={ # Optional custom colors
240
+ 'Control': '#1f77b4',
241
+ 'Treatment_A': '#ff7f0e',
242
+ 'Treatment_B': '#2ca02c',
243
+ },
244
+ )
245
+ ```
246
+
247
+ ### VolcanoPlot
248
+
249
+ Interactive volcano plot for differential expression analysis with significance thresholds.
250
+
251
+ ```python
252
+ from openms_insight import VolcanoPlot
253
+
254
+ VolcanoPlot(
255
+ cache_id="de_volcano",
256
+ data_path="differential_expression.parquet",
257
+ log2fc_column='log2FC',
258
+ pvalue_column='pvalue',
259
+ label_column='protein_name', # Optional: labels for significant points
260
+ filters={'comparison': 'comparison_id'},
261
+ interactivity={'protein': 'protein_id'},
262
+ title="Differential Expression",
263
+ x_label="Log2 Fold Change",
264
+ y_label="-log10(p-value)",
265
+ up_color='#d62728', # Color for up-regulated
266
+ down_color='#1f77b4', # Color for down-regulated
267
+ ns_color='#888888', # Color for not significant
268
+ )(
269
+ state_manager=state_manager,
270
+ fc_threshold=1.0, # Fold change threshold (render-time)
271
+ p_threshold=0.05, # P-value threshold (render-time)
272
+ max_labels=20, # Max labels to show
273
+ )
274
+ ```
275
+
276
+ **Key parameters:**
277
+ - `log2fc_column`: Column with log2 fold change values
278
+ - `pvalue_column`: Column with p-values (automatically converted to -log10)
279
+ - `label_column`: Optional column for point labels
280
+ - `up_color`, `down_color`, `ns_color`: Colors for significance categories
281
+ - `fc_threshold`, `p_threshold`: Significance thresholds (passed at render time, not cached)
282
+ - `max_labels`: Maximum number of labels to display on significant points
283
+
284
+ **Render-time thresholds:** The `fc_threshold` and `p_threshold` are passed via `__call__()`, not `__init__()`. This allows instant threshold adjustment without cache invalidation.
195
285
 
196
286
  ### SequenceView
197
287
 
@@ -255,6 +345,7 @@ All components accept these common arguments:
255
345
  | `interactivity` | `Dict[str, str]` | `None` | Map identifier -> column for click actions |
256
346
  | `cache_path` | `str` | `"."` | Base directory for cache storage |
257
347
  | `regenerate_cache` | `bool` | `False` | Force cache regeneration |
348
+ | `height` | `int` | `400` | Component height in pixels (render-time parameter) |
258
349
 
259
350
  ## Memory-Efficient Preprocessing
260
351
 
@@ -338,6 +429,14 @@ npm run dev
338
429
  SVC_DEV_MODE=true SVC_DEV_URL=http://localhost:5173 streamlit run app.py
339
430
  ```
340
431
 
432
+ ### Debug Mode
433
+
434
+ Enable hash tracking logs to debug data synchronization issues:
435
+
436
+ ```bash
437
+ SVC_DEBUG_HASH=true streamlit run app.py
438
+ ```
439
+
341
440
  ### Running Tests
342
441
 
343
442
  ```bash
@@ -9,13 +9,14 @@ from .components.heatmap import Heatmap
9
9
  from .components.lineplot import LinePlot
10
10
  from .components.sequenceview import SequenceView, SequenceViewResult
11
11
  from .components.table import Table
12
+ from .components.volcanoplot import VolcanoPlot
12
13
  from .core.base import BaseComponent
13
14
  from .core.cache import CacheMissError
14
15
  from .core.registry import get_component_class, register_component
15
16
  from .core.state import StateManager
16
17
  from .rendering.bridge import clear_component_annotations, get_component_annotations
17
18
 
18
- __version__ = "0.1.0"
19
+ __version__ = "0.1.6"
19
20
 
20
21
  __all__ = [
21
22
  # Core
@@ -28,6 +29,7 @@ __all__ = [
28
29
  "Table",
29
30
  "LinePlot",
30
31
  "Heatmap",
32
+ "VolcanoPlot",
31
33
  "SequenceView",
32
34
  "SequenceViewResult",
33
35
  # Utilities
@@ -3,9 +3,11 @@
3
3
  from .heatmap import Heatmap
4
4
  from .lineplot import LinePlot
5
5
  from .table import Table
6
+ from .volcanoplot import VolcanoPlot
6
7
 
7
8
  __all__ = [
8
9
  "Table",
9
10
  "LinePlot",
10
11
  "Heatmap",
12
+ "VolcanoPlot",
11
13
  ]
@@ -86,9 +86,14 @@ class Heatmap(BaseComponent):
86
86
  x_label: Optional[str] = None,
87
87
  y_label: Optional[str] = None,
88
88
  colorscale: str = "Portland",
89
+ reversescale: bool = False,
89
90
  use_simple_downsample: bool = False,
90
91
  use_streaming: bool = True,
91
92
  categorical_filters: Optional[List[str]] = None,
93
+ category_column: Optional[str] = None,
94
+ category_colors: Optional[Dict[str, str]] = None,
95
+ log_scale: bool = True,
96
+ intensity_label: Optional[str] = None,
92
97
  **kwargs,
93
98
  ):
94
99
  """
@@ -133,6 +138,18 @@ class Heatmap(BaseComponent):
133
138
  are sent to the client regardless of filter selection. Should be
134
139
  used for filters with a small number of unique values (<20).
135
140
  Example: ['im_dimension'] for ion mobility filtering.
141
+ category_column: Optional column name for categorical coloring.
142
+ When provided, points are colored by discrete category values
143
+ instead of the continuous intensity colorscale. Useful for
144
+ condition-based heatmaps (e.g., coloring by sample group).
145
+ category_colors: Optional mapping of category values to colors.
146
+ Keys should match values in category_column.
147
+ Values should be CSS color strings (e.g., '#FF0000', 'red').
148
+ If not provided, default Plotly colors will be used.
149
+ log_scale: If True (default), apply log10 transformation to intensity
150
+ values for color mapping. Set to False for linear color mapping.
151
+ intensity_label: Custom label for the colorbar. Default is "Intensity".
152
+ Useful when displaying non-intensity values like scores or counts.
136
153
  **kwargs: Additional configuration options
137
154
  """
138
155
  self._x_column = x_column
@@ -147,7 +164,12 @@ class Heatmap(BaseComponent):
147
164
  self._x_label = x_label or x_column
148
165
  self._y_label = y_label or y_column
149
166
  self._colorscale = colorscale
167
+ self._reversescale = reversescale
150
168
  self._use_simple_downsample = use_simple_downsample
169
+ self._category_column = category_column
170
+ self._category_colors = category_colors or {}
171
+ self._log_scale = log_scale
172
+ self._intensity_label = intensity_label
151
173
  self._use_streaming = use_streaming
152
174
  self._categorical_filters = categorical_filters or []
153
175
 
@@ -176,6 +198,8 @@ class Heatmap(BaseComponent):
176
198
  use_simple_downsample=use_simple_downsample,
177
199
  use_streaming=use_streaming,
178
200
  categorical_filters=categorical_filters,
201
+ category_column=category_column,
202
+ category_colors=category_colors,
179
203
  **kwargs,
180
204
  )
181
205
 
@@ -202,6 +226,10 @@ class Heatmap(BaseComponent):
202
226
  "x_label": self._x_label,
203
227
  "y_label": self._y_label,
204
228
  "colorscale": self._colorscale,
229
+ "category_column": self._category_column,
230
+ "log_scale": self._log_scale,
231
+ "intensity_label": self._intensity_label,
232
+ # Note: category_colors is render-time styling, doesn't affect cache
205
233
  }
206
234
 
207
235
  def _restore_cache_config(self, config: Dict[str, Any]) -> None:
@@ -223,6 +251,10 @@ class Heatmap(BaseComponent):
223
251
  self._x_label = config.get("x_label", self._x_column)
224
252
  self._y_label = config.get("y_label", self._y_column)
225
253
  self._colorscale = config.get("colorscale", "Portland")
254
+ self._category_column = config.get("category_column")
255
+ self._log_scale = config.get("log_scale", True)
256
+ self._intensity_label = config.get("intensity_label")
257
+ # category_colors is not stored in cache (render-time styling)
226
258
 
227
259
  def get_state_dependencies(self) -> list:
228
260
  """
@@ -457,9 +489,7 @@ class Heatmap(BaseComponent):
457
489
  filtered_total = filtered_data.select(pl.len()).collect().item()
458
490
 
459
491
  # Compute level sizes for this filtered subset (2× for cache buffer)
460
- level_sizes = compute_compression_levels(
461
- cache_target, filtered_total
462
- )
492
+ level_sizes = compute_compression_levels(cache_target, filtered_total)
463
493
 
464
494
  print(
465
495
  f"[HEATMAP] Value {filter_value}: {filtered_total:,} pts → levels {level_sizes}",
@@ -937,12 +967,15 @@ class Heatmap(BaseComponent):
937
967
 
938
968
  zoom = state.get(self._zoom_identifier)
939
969
 
940
- # Build columns to select
970
+ # Build columns to select (filter out None values)
941
971
  columns_to_select = [
942
- self._x_column,
943
- self._y_column,
944
- self._intensity_column,
972
+ col
973
+ for col in [self._x_column, self._y_column, self._intensity_column]
974
+ if col is not None
945
975
  ]
976
+ # Include category column if specified
977
+ if self._category_column and self._category_column not in columns_to_select:
978
+ columns_to_select.append(self._category_column)
946
979
  # Include columns needed for interactivity
947
980
  if self._interactivity:
948
981
  for col in self._interactivity.values():
@@ -995,17 +1028,25 @@ class Heatmap(BaseComponent):
995
1028
  columns=columns_to_select,
996
1029
  filter_defaults=self._filter_defaults,
997
1030
  )
998
- # Sort by intensity ascending so high-intensity points are drawn on top
999
- df_pandas = df_pandas.sort_values(self._intensity_column).reset_index(
1000
- drop=True
1001
- )
1031
+ # Sort by intensity ascending so high-intensity points are drawn on top (scattergl)
1032
+ if (
1033
+ self._intensity_column
1034
+ and self._intensity_column in df_pandas.columns
1035
+ ):
1036
+ df_pandas = df_pandas.sort_values(
1037
+ self._intensity_column, ascending=True
1038
+ ).reset_index(drop=True)
1002
1039
  else:
1003
1040
  # No filters to apply - levels already filtered by categorical filter
1004
1041
  schema_names = data.collect_schema().names()
1005
1042
  available_cols = [c for c in columns_to_select if c in schema_names]
1006
1043
  df_polars = data.select(available_cols).collect()
1007
- # Sort by intensity ascending so high-intensity points are drawn on top
1008
- df_polars = df_polars.sort(self._intensity_column)
1044
+ # Sort by intensity ascending so high-intensity points are drawn on top (scattergl)
1045
+ if (
1046
+ self._intensity_column
1047
+ and self._intensity_column in df_polars.columns
1048
+ ):
1049
+ df_polars = df_polars.sort(self._intensity_column)
1009
1050
  data_hash = compute_dataframe_hash(df_polars)
1010
1051
  df_pandas = df_polars.to_pandas()
1011
1052
  else:
@@ -1017,8 +1058,9 @@ class Heatmap(BaseComponent):
1017
1058
  # Select only needed columns
1018
1059
  available_cols = [c for c in columns_to_select if c in df_polars.columns]
1019
1060
  df_polars = df_polars.select(available_cols)
1020
- # Sort by intensity ascending so high-intensity points are drawn on top
1021
- df_polars = df_polars.sort(self._intensity_column)
1061
+ # Sort by intensity ascending so high-intensity points are drawn on top (scattergl)
1062
+ if self._intensity_column and self._intensity_column in df_polars.columns:
1063
+ df_polars = df_polars.sort(self._intensity_column)
1022
1064
  print(
1023
1065
  f"[HEATMAP] Selected {len(df_polars)} pts for zoom, levels={level_sizes}",
1024
1066
  file=sys.stderr,
@@ -1046,6 +1088,7 @@ class Heatmap(BaseComponent):
1046
1088
  "xLabel": self._x_label,
1047
1089
  "yLabel": self._y_label,
1048
1090
  "colorscale": self._colorscale,
1091
+ "reversescale": self._reversescale,
1049
1092
  "zoomIdentifier": self._zoom_identifier,
1050
1093
  "interactivity": self._interactivity,
1051
1094
  }
@@ -1053,6 +1096,17 @@ class Heatmap(BaseComponent):
1053
1096
  if self._title:
1054
1097
  args["title"] = self._title
1055
1098
 
1099
+ # Add category column configuration for categorical coloring mode
1100
+ if self._category_column:
1101
+ args["categoryColumn"] = self._category_column
1102
+ if self._category_colors:
1103
+ args["categoryColors"] = self._category_colors
1104
+
1105
+ # Add log scale and intensity label configuration
1106
+ args["logScale"] = self._log_scale
1107
+ if self._intensity_label:
1108
+ args["intensityLabel"] = self._intensity_label
1109
+
1056
1110
  # Add any extra config options
1057
1111
  args.update(self._config)
1058
1112