plexora 0.0.1__py3-none-any.whl
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.
- plexora/__init__.py +129 -0
- plexora/__main__.py +15 -0
- plexora/_url.py +105 -0
- plexora/api/__init__.py +55 -0
- plexora/api/dataset.py +398 -0
- plexora/api/http.py +19 -0
- plexora/api/plugin.py +541 -0
- plexora/api/store.py +221 -0
- plexora/cli.py +777 -0
- plexora/client/dist/354_bundle.js +2 -0
- plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
- plexora/client/dist/418_bundle.js +2 -0
- plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
- plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
- plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
- plexora/client/dist/770_bundle.js +1 -0
- plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
- plexora/client/dist/vendor_bundle.js +2 -0
- plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
- plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
- plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
- plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
- plexora/client/src/css/import.css +474 -0
- plexora/client/src/css/main.css +903 -0
- plexora/client/src/css/openProject.css +363 -0
- plexora/client/src/css/quickView.css +166 -0
- plexora/client/src/css/tokens.css +51 -0
- plexora/client/src/css/viewer.css +1655 -0
- plexora/client/src/img/apple-touch-icon.png +0 -0
- plexora/client/src/img/favicon.ico +0 -0
- plexora/client/src/img/logo.ai +8892 -33
- plexora/client/src/img/logo.svg +1 -0
- plexora/client/src/img/logo_with_text.ai +9011 -34
- plexora/client/src/img/logo_with_text.svg +1 -0
- plexora/client/src/js/main.js +704 -0
- plexora/client/src/js/pluginRegistry.js +108 -0
- plexora/client/src/js/services/appStatus.js +286 -0
- plexora/client/src/js/services/browsePicker.js +73 -0
- plexora/client/src/js/services/dataLayer.js +400 -0
- plexora/client/src/js/services/datasetContext.js +143 -0
- plexora/client/src/js/services/glRenderer.js +143 -0
- plexora/client/src/js/services/importFormValidation.js +257 -0
- plexora/client/src/js/services/numericData.js +100 -0
- plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
- plexora/client/src/js/services/simpleEventHandler.js +25 -0
- plexora/client/src/js/vendor.js +29 -0
- plexora/client/src/js/views/channelList.js +663 -0
- plexora/client/src/js/views/colorSwatchPicker.js +202 -0
- plexora/client/src/js/views/columnClassifier.js +146 -0
- plexora/client/src/js/views/coordinateField.js +133 -0
- plexora/client/src/js/views/dataSourceField.js +276 -0
- plexora/client/src/js/views/imageViewer.js +3306 -0
- plexora/client/src/js/views/miniMap.js +787 -0
- plexora/client/src/js/views/navbarControls.js +178 -0
- plexora/client/src/js/views/openProjectPage.js +239 -0
- plexora/client/src/js/views/projectEdit.js +335 -0
- plexora/client/src/js/views/quickViewLanding.js +136 -0
- plexora/client/src/js/views/rainbow.js +326 -0
- plexora/client/src/js/views/requirementsModal.js +572 -0
- plexora/client/src/js/views/rgbImageViewer.js +115 -0
- plexora/client/src/js/views/searchableSelect.js +411 -0
- plexora/client/src/js/views/segmentationProgress.js +122 -0
- plexora/client/src/js/views/toolLoader.js +747 -0
- plexora/client/src/js/views/viewerControls.js +689 -0
- plexora/client/src/js/views/viewerManager.js +434 -0
- plexora/client/src/js/views/viewerSidebar.js +1117 -0
- plexora/client/src/js/workers/tileDecoder.js +130 -0
- plexora/client/src/shaders/frag.glsl +528 -0
- plexora/client/src/shaders/vert.glsl +12 -0
- plexora/client/templates/base.html +212 -0
- plexora/client/templates/index.html +238 -0
- plexora/client/templates/open_project.html +96 -0
- plexora/client/templates/project_columns.html +86 -0
- plexora/client/templates/project_edit.html +196 -0
- plexora/client/templates/upload.html +118 -0
- plexora/connect.py +528 -0
- plexora/datasource.py +782 -0
- plexora/jupyter.py +406 -0
- plexora/notebook_env.py +232 -0
- plexora/paths.py +486 -0
- plexora/plugins/__init__.py +12 -0
- plexora/plugins/cell_explorer/__init__.py +100 -0
- plexora/plugins/cell_explorer/server/__init__.py +11 -0
- plexora/plugins/cell_explorer/server/routes.py +167 -0
- plexora/plugins/cell_explorer/server/state.py +281 -0
- plexora/plugins/cell_explorer/server/values.py +145 -0
- plexora/plugins/cell_explorer/server/variables.py +367 -0
- plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
- plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
- plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
- plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
- plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
- plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
- plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
- plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
- plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
- plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
- plexora/plugins/figure_builder/__init__.py +125 -0
- plexora/plugins/figure_builder/server/__init__.py +7 -0
- plexora/plugins/figure_builder/server/compose.py +365 -0
- plexora/plugins/figure_builder/server/export.py +784 -0
- plexora/plugins/figure_builder/server/export_jobs.py +148 -0
- plexora/plugins/figure_builder/server/operations.py +556 -0
- plexora/plugins/figure_builder/server/pixels.py +142 -0
- plexora/plugins/figure_builder/server/provenance.py +174 -0
- plexora/plugins/figure_builder/server/render.py +278 -0
- plexora/plugins/figure_builder/server/repository.py +701 -0
- plexora/plugins/figure_builder/server/routes.py +594 -0
- plexora/plugins/figure_builder/server/schema.py +1023 -0
- plexora/plugins/figure_builder/server/sources.py +120 -0
- plexora/plugins/figure_builder/server/textmetrics.py +155 -0
- plexora/plugins/figure_builder/static/figureActions.js +363 -0
- plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
- plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
- plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
- plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
- plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
- plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
- plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
- plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
- plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
- plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
- plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
- plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
- plexora/plugins/figure_builder/static/figureRichText.js +634 -0
- plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
- plexora/plugins/figure_builder/static/figureSchema.js +218 -0
- plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
- plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
- plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
- plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
- plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
- plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
- plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
- plexora/plugins/gating/__init__.py +76 -0
- plexora/plugins/gating/server/__init__.py +0 -0
- plexora/plugins/gating/server/anndata_gates.py +367 -0
- plexora/plugins/gating/server/database.py +16 -0
- plexora/plugins/gating/server/model.py +320 -0
- plexora/plugins/gating/server/routes.py +235 -0
- plexora/plugins/gating/static/csvGatingList.js +947 -0
- plexora/plugins/gating/static/gating.css +289 -0
- plexora/plugins/gating/static/gatingApi.js +221 -0
- plexora/plugins/gating/static/gatingSidebarController.js +513 -0
- plexora/plugins/gating/templates/gating/legacy.html +26 -0
- plexora/plugins/gating/templates/gating/panel.html +69 -0
- plexora/plugins/roi/__init__.py +95 -0
- plexora/plugins/roi/server/__init__.py +8 -0
- plexora/plugins/roi/server/adapters.py +655 -0
- plexora/plugins/roi/server/geojson.py +267 -0
- plexora/plugins/roi/server/geometry.py +181 -0
- plexora/plugins/roi/server/mapping.py +169 -0
- plexora/plugins/roi/server/operations.py +293 -0
- plexora/plugins/roi/server/repository.py +215 -0
- plexora/plugins/roi/server/routes.py +409 -0
- plexora/plugins/roi/server/schema.py +323 -0
- plexora/plugins/roi/static/roi.css +583 -0
- plexora/plugins/roi/static/roiApi.js +177 -0
- plexora/plugins/roi/static/roiFigureBridge.js +107 -0
- plexora/plugins/roi/static/roiGeometry.js +350 -0
- plexora/plugins/roi/static/roiRenderer.js +278 -0
- plexora/plugins/roi/static/roiSidebarController.js +1038 -0
- plexora/plugins/roi/static/roiState.js +518 -0
- plexora/plugins/roi/static/roiTools.js +993 -0
- plexora/plugins/roi/templates/roi/panel.html +240 -0
- plexora/proxy.py +57 -0
- plexora/server/models/adapters/__init__.py +94 -0
- plexora/server/models/adapters/anndata_adapter.py +388 -0
- plexora/server/models/adapters/base.py +93 -0
- plexora/server/models/adapters/classify.py +192 -0
- plexora/server/models/adapters/csv_adapter.py +105 -0
- plexora/server/models/adapters/inspection.py +265 -0
- plexora/server/models/adapters/spatialdata_adapter.py +248 -0
- plexora/server/models/centroid_tiles.py +305 -0
- plexora/server/models/data_model.py +1659 -0
- plexora/server/models/database_model.py +169 -0
- plexora/server/models/project.py +1270 -0
- plexora/server/plugins.py +199 -0
- plexora/server/routes/browse_routes.py +41 -0
- plexora/server/routes/data_routes.py +294 -0
- plexora/server/routes/import_routes.py +603 -0
- plexora/server/routes/page_routes.py +139 -0
- plexora/server/routes/project_routes.py +438 -0
- plexora/server/routes/quick_view_routes.py +62 -0
- plexora/server/routes/system_routes.py +39 -0
- plexora/server/routes/tool_routes.py +418 -0
- plexora/server/utils/addHEColumns.py +11 -0
- plexora/server/utils/fast_png.py +97 -0
- plexora/server/utils/fullConversion.py +50 -0
- plexora/server/utils/native_dialog.py +168 -0
- plexora/server/utils/pre_normalization.py +47 -0
- plexora/server/utils/segmentation_pyramid.py +598 -0
- plexora/server/utils/smallestenclosingcircle.py +126 -0
- plexora/server/utils/tiffsurgeon.py +372 -0
- plexora/server_cli.py +48 -0
- plexora/test.py +0 -0
- plexora-0.0.1.dist-info/METADATA +341 -0
- plexora-0.0.1.dist-info/RECORD +248 -0
- plexora-0.0.1.dist-info/WHEEL +5 -0
- plexora-0.0.1.dist-info/entry_points.txt +12 -0
- plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
- plexora-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cellExplorerContinuous.js - the numeric controls: ramp, range, palette.
|
|
3
|
+
*
|
|
4
|
+
* One line: the colour bar, the palette button, and Auto. The range is set on
|
|
5
|
+
* the bar rather than beside it -- two handles over the data's own extent --
|
|
6
|
+
* which is the arrangement that makes the picture literally true. The bar is
|
|
7
|
+
* painted as the mapping across the whole extent: flat low colour up to the
|
|
8
|
+
* bottom handle, the ramp between the handles, flat high colour above the top
|
|
9
|
+
* one. That IS what the image does with a value past either end, so the control
|
|
10
|
+
* and the legend are the same object and cannot disagree.
|
|
11
|
+
*
|
|
12
|
+
* This replaced a pair of typed number fields on a line of their own. They were
|
|
13
|
+
* exact, which a slider is not, and they cost a line plus a caption plus the
|
|
14
|
+
* whole "draft text is not committed state" apparatus -- a half-typed "0." is
|
|
15
|
+
* not a number, so the text had to live in the input and the numbers in the
|
|
16
|
+
* state and the two only met on Enter or blur. Two handles over a bar need none
|
|
17
|
+
* of that, and what the numbers are is still readable underneath.
|
|
18
|
+
*
|
|
19
|
+
* Two events, two costs, the same split the opacity slider uses: `input` fires
|
|
20
|
+
* per pixel of drag and only repaints this bar, while `change` fires once on
|
|
21
|
+
* release and is the only one that recolours the image and saves. Recolouring
|
|
22
|
+
* per pixel would be a lookup table per cell rebuilt tens of times a second,
|
|
23
|
+
* and it would re-render this panel out from under the handle being dragged.
|
|
24
|
+
*
|
|
25
|
+
* Clipping is display-only. A value past either end takes the end colour;
|
|
26
|
+
* nothing in the table is touched, and Auto always gets back to the robust
|
|
27
|
+
* percentiles the server computed.
|
|
28
|
+
*
|
|
29
|
+
* The eye at the end of the row is the legend's per-category eye, for a kind of
|
|
30
|
+
* column that has no rows to put one on. Without it a numeric column had no way
|
|
31
|
+
* to get the colours off the tissue at all except dragging opacity to zero,
|
|
32
|
+
* which loses whatever opacity was set to get back to.
|
|
33
|
+
*
|
|
34
|
+
* The palettes live behind a button beside the ramp rather than in a row of
|
|
35
|
+
* their own. Choosing one is a thing people do once per column at most, and it
|
|
36
|
+
* was taking permanent space in a sidebar that also has to hold a legend.
|
|
37
|
+
*/
|
|
38
|
+
class CellExplorerContinuous {
|
|
39
|
+
|
|
40
|
+
/** How finely the handles divide the data's extent. */
|
|
41
|
+
static STEPS = 1000;
|
|
42
|
+
|
|
43
|
+
constructor(container, handlers) {
|
|
44
|
+
this.container = container;
|
|
45
|
+
this.handlers = handlers;
|
|
46
|
+
this.nodes = null;
|
|
47
|
+
//: Whether the palette chooser is open. On the instance, not in the
|
|
48
|
+
//: DOM, because choosing a palette re-renders this whole panel -- so
|
|
49
|
+
//: anything held in the markup would slam shut on the first click.
|
|
50
|
+
this.paletteOpen = false;
|
|
51
|
+
//: The last thing rendered, so opening or closing the palettes can
|
|
52
|
+
//: redraw without asking the controller for state it already handed us.
|
|
53
|
+
this._last = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param descriptor the column's server descriptor
|
|
58
|
+
* @param entry { palette, custom, range } for this column
|
|
59
|
+
* @param domain [low, high] currently drawn between
|
|
60
|
+
* @param auto whether that domain is the automatic one
|
|
61
|
+
*/
|
|
62
|
+
render(descriptor, entry, domain, auto) {
|
|
63
|
+
const container = this.container;
|
|
64
|
+
if (!container) return;
|
|
65
|
+
this._last = { descriptor, entry, domain, auto };
|
|
66
|
+
container.textContent = "";
|
|
67
|
+
this.nodes = null;
|
|
68
|
+
if (!descriptor) return;
|
|
69
|
+
|
|
70
|
+
const stats = descriptor.stats || {};
|
|
71
|
+
if (!Number.isFinite(stats.min)) {
|
|
72
|
+
container.appendChild(CellExplorerContinuous.note(
|
|
73
|
+
`No valid values found for "${descriptor.name}".`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A zero-width scale. Said in words, because a uniformly coloured image
|
|
78
|
+
// is not something a user should have to deduce -- and with nothing to
|
|
79
|
+
// spread across, neither the handles nor the palette chooser have
|
|
80
|
+
// anything to change.
|
|
81
|
+
if (stats.constant) {
|
|
82
|
+
container.appendChild(this.buildRamp(entry, domain, stats, false, false));
|
|
83
|
+
container.appendChild(CellExplorerContinuous.note(
|
|
84
|
+
`Every cell has the same value: ${CellExplorerContinuous.format(stats.min)}.`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
container.appendChild(this.buildRamp(entry, domain, stats, true, auto));
|
|
89
|
+
if (this.paletteOpen) container.appendChild(this.buildPalettes(entry));
|
|
90
|
+
|
|
91
|
+
if (descriptor.n_missing > 0) {
|
|
92
|
+
container.appendChild(CellExplorerContinuous.note(
|
|
93
|
+
`${descriptor.n_missing.toLocaleString()} cells have no value and are not drawn.`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Redraw from what was last handed in. Used by the palette disclosure,
|
|
98
|
+
* which changes what this panel shows and nothing about the state. */
|
|
99
|
+
refresh() {
|
|
100
|
+
if (!this._last) return;
|
|
101
|
+
const { descriptor, entry, domain, auto } = this._last;
|
|
102
|
+
this.render(descriptor, entry, domain, auto);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The colour bar, its two handles, the palette button and Auto -- one line,
|
|
107
|
+
* with the numbers the handles are currently at underneath.
|
|
108
|
+
*
|
|
109
|
+
* @param choosable false for a constant column, where there is a bar to
|
|
110
|
+
* look at and nothing to set on it.
|
|
111
|
+
*/
|
|
112
|
+
buildRamp(entry, [low, high], stats, choosable, auto) {
|
|
113
|
+
const wrapper = document.createElement("div");
|
|
114
|
+
// Dimmed while the overlay is off, so the panel says which of the two
|
|
115
|
+
// states it is in without anybody having to look at the image.
|
|
116
|
+
wrapper.className = entry.hidden
|
|
117
|
+
? "cex-ramp-wrapper is-hidden" : "cex-ramp-wrapper";
|
|
118
|
+
|
|
119
|
+
const row = document.createElement("div");
|
|
120
|
+
row.className = "cex-ramp-row";
|
|
121
|
+
|
|
122
|
+
const track = document.createElement("div");
|
|
123
|
+
track.className = "cex-ramp-track";
|
|
124
|
+
|
|
125
|
+
const strip = document.createElement("div");
|
|
126
|
+
strip.className = "cex-ramp";
|
|
127
|
+
track.appendChild(strip);
|
|
128
|
+
row.appendChild(track);
|
|
129
|
+
|
|
130
|
+
const scale = document.createElement("div");
|
|
131
|
+
scale.className = "cex-ramp-scale";
|
|
132
|
+
const lowLabel = document.createElement("span");
|
|
133
|
+
const highLabel = document.createElement("span");
|
|
134
|
+
scale.append(lowLabel, highLabel);
|
|
135
|
+
|
|
136
|
+
this.nodes = {
|
|
137
|
+
strip, lowLabel, highLabel, entry, stats,
|
|
138
|
+
low, high, minInput: null, maxInput: null,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (choosable) {
|
|
142
|
+
this.nodes.minInput = this.buildHandle("min", low, stats);
|
|
143
|
+
this.nodes.maxInput = this.buildHandle("max", high, stats);
|
|
144
|
+
// Read back what the inputs actually hold. A range input snaps its
|
|
145
|
+
// value onto the step grid and clamps it to the ends, so taking
|
|
146
|
+
// the numbers from the handles rather than from the domain is what
|
|
147
|
+
// keeps the readout equal to what releasing would commit.
|
|
148
|
+
this.nodes.low = Number(this.nodes.minInput.value);
|
|
149
|
+
this.nodes.high = Number(this.nodes.maxInput.value);
|
|
150
|
+
track.append(this.nodes.minInput, this.nodes.maxInput);
|
|
151
|
+
row.appendChild(this.buildPaletteButton(entry));
|
|
152
|
+
row.appendChild(this.buildAuto(auto));
|
|
153
|
+
}
|
|
154
|
+
// Offered for a constant column too: there is nothing to set on that
|
|
155
|
+
// bar, and taking the overlay off to look at the tissue underneath is
|
|
156
|
+
// exactly as reasonable there as anywhere else.
|
|
157
|
+
row.appendChild(this.buildVisibility(entry));
|
|
158
|
+
|
|
159
|
+
this.paint();
|
|
160
|
+
wrapper.append(row, scale);
|
|
161
|
+
return wrapper;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* One end of the range, as a real <input type="range">.
|
|
166
|
+
*
|
|
167
|
+
* Two of them stacked over the same track, which is what gives a
|
|
168
|
+
* two-handled slider arrow keys, Home/End and a tab stop per end without
|
|
169
|
+
* any of that being written here. The track ignores pointer events and the
|
|
170
|
+
* thumbs take them back (see the stylesheet), or the upper input would
|
|
171
|
+
* swallow every click meant for the lower one.
|
|
172
|
+
*/
|
|
173
|
+
buildHandle(which, value, stats) {
|
|
174
|
+
const input = document.createElement("input");
|
|
175
|
+
input.type = "range";
|
|
176
|
+
input.className = `cex-ramp-handle cex-ramp-handle-${which}`;
|
|
177
|
+
input.min = String(stats.min);
|
|
178
|
+
input.max = String(stats.max);
|
|
179
|
+
input.step = String((stats.max - stats.min) / CellExplorerContinuous.STEPS || "any");
|
|
180
|
+
input.value = String(value);
|
|
181
|
+
input.setAttribute("aria-label", which === "min" ? "Range minimum" : "Range maximum");
|
|
182
|
+
input.addEventListener("input", () => this.drag(which));
|
|
183
|
+
// Release is the commit. Everything before it is this bar redrawing
|
|
184
|
+
// itself, which costs nothing; this is the one that rebuilds the
|
|
185
|
+
// lookup table, repaints the image and saves.
|
|
186
|
+
input.addEventListener("change", () => {
|
|
187
|
+
this.handlers.onRange?.(this.nodes.low, this.nodes.high);
|
|
188
|
+
});
|
|
189
|
+
return input;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A handle moved. Keeps the two from crossing and repaints the bar.
|
|
194
|
+
*
|
|
195
|
+
* The ends are held one step apart rather than allowed to meet: a
|
|
196
|
+
* zero-width domain has no ramp to draw and divides by zero on the way to
|
|
197
|
+
* the lookup table.
|
|
198
|
+
*/
|
|
199
|
+
drag(which) {
|
|
200
|
+
const { minInput, maxInput, stats } = this.nodes;
|
|
201
|
+
const step = (stats.max - stats.min) / CellExplorerContinuous.STEPS;
|
|
202
|
+
let low = Number(minInput.value);
|
|
203
|
+
let high = Number(maxInput.value);
|
|
204
|
+
if (which === "min" && low > high - step) {
|
|
205
|
+
low = Math.max(stats.min, high - step);
|
|
206
|
+
minInput.value = String(low);
|
|
207
|
+
} else if (which === "max" && high < low + step) {
|
|
208
|
+
high = Math.min(stats.max, low + step);
|
|
209
|
+
maxInput.value = String(high);
|
|
210
|
+
}
|
|
211
|
+
this.nodes.low = low;
|
|
212
|
+
this.nodes.high = high;
|
|
213
|
+
this.paint();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Repaint the bar and its numbers from the handles' current positions. */
|
|
217
|
+
paint() {
|
|
218
|
+
const { strip, lowLabel, highLabel, entry, stats, low, high } = this.nodes;
|
|
219
|
+
strip.style.background = CellExplorerContinuous.gradient(entry, low, high, stats);
|
|
220
|
+
lowLabel.textContent = CellExplorerContinuous.format(low);
|
|
221
|
+
highLabel.textContent = CellExplorerContinuous.format(high);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The bar's paint: what the image does to every value in the data's extent.
|
|
226
|
+
*
|
|
227
|
+
* Sampled from the same ramp the lookup table is built from, so the legend
|
|
228
|
+
* cannot claim a mapping the image does not use -- and stopped flat outside
|
|
229
|
+
* the chosen range, because that is what clipping is.
|
|
230
|
+
*/
|
|
231
|
+
static gradient(entry, low, high, stats) {
|
|
232
|
+
const span = stats.max - stats.min;
|
|
233
|
+
// A column with no extent has no positions to place stops at. Drawn as
|
|
234
|
+
// the plain ramp, which is the honest picture of a palette nothing is
|
|
235
|
+
// being spread across.
|
|
236
|
+
if (!(span > 0)) {
|
|
237
|
+
return "linear-gradient(to right, " + [0, 0.25, 0.5, 0.75, 1].map(
|
|
238
|
+
(fraction) => CellExplorerColors.rampStop(
|
|
239
|
+
entry.palette, entry.custom, fraction)).join(", ") + ")";
|
|
240
|
+
}
|
|
241
|
+
const at = (value) =>
|
|
242
|
+
Math.min(100, Math.max(0, ((value - stats.min) / span) * 100));
|
|
243
|
+
const stops = [0, 0.25, 0.5, 0.75, 1].map((fraction) =>
|
|
244
|
+
`${CellExplorerColors.rampStop(entry.palette, entry.custom, fraction)}`
|
|
245
|
+
+ ` ${at(low + fraction * (high - low)).toFixed(2)}%`);
|
|
246
|
+
const first = CellExplorerColors.rampStop(entry.palette, entry.custom, 0);
|
|
247
|
+
const last = CellExplorerColors.rampStop(entry.palette, entry.custom, 1);
|
|
248
|
+
return `linear-gradient(to right, ${first} 0%, ${stops.join(", ")}, ${last} 100%)`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Auto, lit rather than disabled when the range IS automatic: that is the
|
|
253
|
+
* state it reports, and it is also how the row says which of the two it is
|
|
254
|
+
* in without spending a caption on "(auto)".
|
|
255
|
+
*/
|
|
256
|
+
buildAuto(auto) {
|
|
257
|
+
const button = document.createElement("button");
|
|
258
|
+
button.type = "button";
|
|
259
|
+
button.className = auto ? "cex-toggle is-active" : "cex-toggle";
|
|
260
|
+
button.textContent = "Auto";
|
|
261
|
+
button.setAttribute("aria-pressed", auto ? "true" : "false");
|
|
262
|
+
button.title = auto
|
|
263
|
+
? "Drawn between the 1st and 99th percentiles"
|
|
264
|
+
: "Go back to the automatic range";
|
|
265
|
+
button.addEventListener("click", () => this.handlers.onRange?.(null, null));
|
|
266
|
+
return button;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Draw this column's overlay, or take it off.
|
|
271
|
+
*
|
|
272
|
+
* The same eye the legend puts on every category row, for a kind of column
|
|
273
|
+
* that has no rows -- so a numeric column had no way at all to get the
|
|
274
|
+
* colours off the tissue short of dragging opacity to zero and back, which
|
|
275
|
+
* loses whatever opacity was set. Nothing about the column is forgotten
|
|
276
|
+
* while it is off; the lookup table is simply built transparent.
|
|
277
|
+
*/
|
|
278
|
+
buildVisibility(entry) {
|
|
279
|
+
const button = document.createElement("button");
|
|
280
|
+
button.type = "button";
|
|
281
|
+
button.className = "cex-visibility cex-ramp-visibility";
|
|
282
|
+
button.setAttribute("aria-pressed", entry.hidden ? "false" : "true");
|
|
283
|
+
button.title = entry.hidden ? "Show these colours" : "Hide these colours";
|
|
284
|
+
button.setAttribute("aria-label", button.title);
|
|
285
|
+
const icon = document.createElement("span");
|
|
286
|
+
// The icon carries the state as well as the dimming does, so it is
|
|
287
|
+
// never conveyed by contrast alone.
|
|
288
|
+
icon.className = entry.hidden ? "fas fa-eye-slash" : "fas fa-eye";
|
|
289
|
+
button.appendChild(icon);
|
|
290
|
+
button.addEventListener("click", () => {
|
|
291
|
+
this.handlers.onHidden?.(!entry.hidden);
|
|
292
|
+
});
|
|
293
|
+
return button;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The disclosure that opens the palette list, sitting against the ramp it
|
|
298
|
+
* changes. Named as well as drawn: the current palette is in the title, so
|
|
299
|
+
* what this button would change is answerable without opening it.
|
|
300
|
+
*/
|
|
301
|
+
buildPaletteButton(entry) {
|
|
302
|
+
const button = document.createElement("button");
|
|
303
|
+
button.type = "button";
|
|
304
|
+
button.className = this.paletteOpen
|
|
305
|
+
? "cex-palette-button is-open" : "cex-palette-button";
|
|
306
|
+
button.setAttribute("aria-expanded", this.paletteOpen ? "true" : "false");
|
|
307
|
+
const name = CellExplorerColors.PALETTE_LABELS[entry.palette] || entry.palette;
|
|
308
|
+
button.title = `Palette: ${name}`;
|
|
309
|
+
button.setAttribute("aria-label", `Choose a palette (currently ${name})`);
|
|
310
|
+
const icon = document.createElement("span");
|
|
311
|
+
icon.className = "fas fa-sliders";
|
|
312
|
+
button.appendChild(icon);
|
|
313
|
+
button.addEventListener("click", () => {
|
|
314
|
+
this.paletteOpen = !this.paletteOpen;
|
|
315
|
+
this.refresh();
|
|
316
|
+
});
|
|
317
|
+
return button;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
buildPalettes(entry) {
|
|
321
|
+
const section = document.createElement("div");
|
|
322
|
+
// A disclosure panel, not a permanent row: it is here because the
|
|
323
|
+
// button next to the ramp is open. No heading -- the button that opened
|
|
324
|
+
// it is the heading.
|
|
325
|
+
section.className = "cex-palettes";
|
|
326
|
+
|
|
327
|
+
const row = document.createElement("div");
|
|
328
|
+
row.className = "cex-palette-row";
|
|
329
|
+
row.setAttribute("role", "radiogroup");
|
|
330
|
+
row.setAttribute("aria-label", "Palette");
|
|
331
|
+
|
|
332
|
+
Object.keys(CellExplorerColors.PALETTE_LABELS).forEach((palette) => {
|
|
333
|
+
const button = document.createElement("button");
|
|
334
|
+
button.type = "button";
|
|
335
|
+
button.className = palette === entry.palette
|
|
336
|
+
? "cex-palette is-active" : "cex-palette";
|
|
337
|
+
button.setAttribute("role", "radio");
|
|
338
|
+
button.setAttribute("aria-checked", palette === entry.palette ? "true" : "false");
|
|
339
|
+
// Named as well as shown: a swatch alone is not reachable by anyone
|
|
340
|
+
// who cannot distinguish the ramps, which is the group these
|
|
341
|
+
// palettes exist for.
|
|
342
|
+
button.title = CellExplorerColors.PALETTE_LABELS[palette];
|
|
343
|
+
button.setAttribute("aria-label", CellExplorerColors.PALETTE_LABELS[palette]);
|
|
344
|
+
const stops = [0, 0.5, 1].map((fraction) =>
|
|
345
|
+
CellExplorerColors.rampStop(palette, entry.custom, fraction));
|
|
346
|
+
button.style.background = `linear-gradient(to right, ${stops.join(", ")})`;
|
|
347
|
+
button.addEventListener("click", () => this.handlers.onPalette?.(palette));
|
|
348
|
+
row.appendChild(button);
|
|
349
|
+
});
|
|
350
|
+
section.appendChild(row);
|
|
351
|
+
|
|
352
|
+
if (entry.palette === "custom") {
|
|
353
|
+
section.appendChild(this.buildCustomEnds(entry));
|
|
354
|
+
}
|
|
355
|
+
return section;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
buildCustomEnds(entry) {
|
|
359
|
+
const row = document.createElement("div");
|
|
360
|
+
row.className = "cex-custom-row";
|
|
361
|
+
[["low", "Low"], ["high", "High"]].forEach(([end, label]) => {
|
|
362
|
+
const wrapper = document.createElement("label");
|
|
363
|
+
wrapper.className = "cex-custom-end";
|
|
364
|
+
const input = document.createElement("input");
|
|
365
|
+
input.type = "color";
|
|
366
|
+
input.value = entry.custom?.[end]
|
|
367
|
+
|| (end === "low" ? CellExplorerColors.CUSTOM_LOW : CellExplorerColors.CUSTOM_HIGH);
|
|
368
|
+
input.addEventListener("input", (event) => {
|
|
369
|
+
this.handlers.onCustomColor?.(end, event.target.value);
|
|
370
|
+
});
|
|
371
|
+
const text = document.createElement("span");
|
|
372
|
+
text.textContent = label;
|
|
373
|
+
wrapper.append(input, text);
|
|
374
|
+
row.appendChild(wrapper);
|
|
375
|
+
});
|
|
376
|
+
return row;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
static note(text) {
|
|
380
|
+
const paragraph = document.createElement("p");
|
|
381
|
+
paragraph.className = "cex-hint";
|
|
382
|
+
paragraph.textContent = text;
|
|
383
|
+
return paragraph;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Readable in a narrow sidebar without losing what the number is.
|
|
388
|
+
*
|
|
389
|
+
* Both ends of the magnitude range turn up in real metadata -- a
|
|
390
|
+
* probability of 0.0000042 and an area of 1,245,322 -- and the default
|
|
391
|
+
* `toString` gives "4.2e-6" for one and an unreadable run of digits for the
|
|
392
|
+
* other.
|
|
393
|
+
*/
|
|
394
|
+
static format(value) {
|
|
395
|
+
if (!Number.isFinite(value)) return "--";
|
|
396
|
+
const magnitude = Math.abs(value);
|
|
397
|
+
if (magnitude === 0) return "0";
|
|
398
|
+
if (magnitude >= 100_000 || magnitude < 0.001) return value.toExponential(2);
|
|
399
|
+
if (magnitude >= 1000) return Math.round(value).toLocaleString();
|
|
400
|
+
if (magnitude >= 10) return value.toFixed(1);
|
|
401
|
+
return value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cellExplorerFigureBridge.js - what Cell Explorer tells a figure about itself.
|
|
3
|
+
*
|
|
4
|
+
* Two DOM events and no import in either direction, exactly as
|
|
5
|
+
* cellExplorerRoiBridge.js already does between this plugin and ROI: a build
|
|
6
|
+
* with one and not the other has to work, and a listener nobody dispatches to
|
|
7
|
+
* simply never fires.
|
|
8
|
+
*
|
|
9
|
+
* plexora:figure-capture-state "describe what you are drawing"
|
|
10
|
+
* plexora:figure-restore-state "here is what you were drawing"
|
|
11
|
+
*
|
|
12
|
+
* ## What is restored, and what deliberately is not
|
|
13
|
+
*
|
|
14
|
+
* Which COLUMN is showing can be restored without persisting -- `select()`
|
|
15
|
+
* takes `{persist: false}`, which exists precisely so a column can be shown
|
|
16
|
+
* without becoming the project's remembered choice. That is the part that
|
|
17
|
+
* matters: a panel captured on a phenotype map reopens on the phenotype map.
|
|
18
|
+
*
|
|
19
|
+
* The palette, the hidden rows and the manual range are PERSISTED preferences.
|
|
20
|
+
* Applying them would be an edit to the project's own Cell Explorer settings,
|
|
21
|
+
* made because the user looked at a figure panel -- which is the thing a figure
|
|
22
|
+
* must never do. So they are reported rather than applied, and the figure says
|
|
23
|
+
* which part the live viewer is not reproducing.
|
|
24
|
+
*
|
|
25
|
+
* The legend is computed at capture time and stored with the panel, so an
|
|
26
|
+
* export on a machine where this plugin is not installed still carries the
|
|
27
|
+
* right labels and colours. Recomputing it later from a palette that has since
|
|
28
|
+
* changed would produce a legend that disagrees with the panel above it.
|
|
29
|
+
*/
|
|
30
|
+
(function () {
|
|
31
|
+
const PLUGIN_NAME = "cell_explorer";
|
|
32
|
+
|
|
33
|
+
function record() {
|
|
34
|
+
return window.__plexora?.plugins?.get(PLUGIN_NAME) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function controller() {
|
|
38
|
+
return record()?.sidebarController || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The legend rows for whatever is showing: one per category, or a ramp. */
|
|
42
|
+
function legendFor(state) {
|
|
43
|
+
const column = state.column;
|
|
44
|
+
if (!column) return [];
|
|
45
|
+
const descriptor = state.descriptor(column);
|
|
46
|
+
if (!descriptor) return [];
|
|
47
|
+
|
|
48
|
+
if (state.kindFor(column) === "continuous") {
|
|
49
|
+
const entry = state.continuous(column);
|
|
50
|
+
const ramp = CellExplorerColors.ramp(entry.palette, entry.custom) || [];
|
|
51
|
+
return [{
|
|
52
|
+
kind: "continuous",
|
|
53
|
+
label: column,
|
|
54
|
+
// A handful of stops rather than all 256: this is drawn as a
|
|
55
|
+
// gradient, and a document that carried a quarter of a kilobyte
|
|
56
|
+
// of hex per continuous panel would be paying for precision
|
|
57
|
+
// nobody can see.
|
|
58
|
+
ramp: sampleRamp(ramp, 16),
|
|
59
|
+
domain: state.domainFor(column),
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const hidden = state.hiddenSet(column);
|
|
64
|
+
const colors = state.categorical(column).colors || {};
|
|
65
|
+
return state.allLabels(column)
|
|
66
|
+
.filter((label) => !hidden.has(label))
|
|
67
|
+
.map((label, index) => ({
|
|
68
|
+
kind: "categorical",
|
|
69
|
+
label: String(label),
|
|
70
|
+
color: colors[label] || CellExplorerColors.defaultCategoryColor(index),
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sampleRamp(ramp, count) {
|
|
75
|
+
if (!ramp.length) return [];
|
|
76
|
+
const out = [];
|
|
77
|
+
for (let i = 0; i < count; i += 1) {
|
|
78
|
+
const stop = ramp[Math.round((i / (count - 1)) * (ramp.length - 1))];
|
|
79
|
+
out.push(Array.isArray(stop) ? CellExplorerColors.toHex(stop) : String(stop));
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
window.addEventListener("plexora:figure-capture-state", (event) => {
|
|
85
|
+
const cell = controller();
|
|
86
|
+
const state = cell?.state;
|
|
87
|
+
if (!state || !state.column) return; // nothing showing: contribute nothing
|
|
88
|
+
|
|
89
|
+
const column = state.column;
|
|
90
|
+
const kind = state.kindFor(column);
|
|
91
|
+
event.detail?.contribute?.(PLUGIN_NAME, {
|
|
92
|
+
version: record()?.definition?.version || "",
|
|
93
|
+
state: {
|
|
94
|
+
column: column,
|
|
95
|
+
kind: kind,
|
|
96
|
+
display: { ...state.settings.display },
|
|
97
|
+
// The per-column preferences, carried so a future build can
|
|
98
|
+
// apply them somewhere they are not the project's own state --
|
|
99
|
+
// a server-side export render, for one.
|
|
100
|
+
categorical: kind === "categorical" ? { ...state.categorical(column) } : null,
|
|
101
|
+
continuous: kind === "continuous" ? { ...state.continuous(column) } : null,
|
|
102
|
+
},
|
|
103
|
+
legend: legendFor(state),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
window.addEventListener("plexora:figure-restore-state", (event) => {
|
|
108
|
+
const wanted = event.detail?.plugins?.[PLUGIN_NAME];
|
|
109
|
+
const report = event.detail?.report;
|
|
110
|
+
if (!wanted) return;
|
|
111
|
+
|
|
112
|
+
const cell = controller();
|
|
113
|
+
if (!cell) return; // not open; Figure Builder records "skipped" for us
|
|
114
|
+
|
|
115
|
+
const state = wanted.state || {};
|
|
116
|
+
if (!state.column) {
|
|
117
|
+
report?.(PLUGIN_NAME, "ok");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Without persisting: showing a column for a figure panel is not the
|
|
122
|
+
// same as choosing it for the project.
|
|
123
|
+
const alreadyShowing = cell.state?.column === state.column;
|
|
124
|
+
if (!alreadyShowing) cell.select(state.column, { persist: false });
|
|
125
|
+
|
|
126
|
+
// The palette and the hidden rows are the project's own settings and
|
|
127
|
+
// are left alone. "partial" is the honest answer whenever they differ.
|
|
128
|
+
const current = cell.state?.categorical?.(state.column);
|
|
129
|
+
const wantedHidden = (state.categorical?.hidden || []).join("|");
|
|
130
|
+
const currentHidden = (current?.hidden || []).join("|");
|
|
131
|
+
report?.(PLUGIN_NAME, wantedHidden === currentHidden ? "ok" : "partial");
|
|
132
|
+
});
|
|
133
|
+
})();
|