├── .env ├── .env.development ├── .envrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ ├── setup-pnpm │ │ └── action.yml │ └── setup-poetry │ │ └── action.yml └── workflows │ ├── ci.yml │ └── pr.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── .prettierrc.yaml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README-PyPI.md ├── README.md ├── data ├── README.md ├── audio │ ├── 1.wav │ ├── 2.wav │ ├── 3.wav │ ├── 4.wav │ ├── 5.wav │ ├── 6.wav │ ├── 7.wav │ ├── 8.wav │ ├── 9.wav │ ├── mono │ │ ├── gs-16b-1c-44100hz.aac │ │ ├── gs-16b-1c-44100hz.ac3 │ │ ├── gs-16b-1c-44100hz.aiff │ │ ├── gs-16b-1c-44100hz.flac │ │ ├── gs-16b-1c-44100hz.m4a │ │ ├── gs-16b-1c-44100hz.mp3 │ │ ├── gs-16b-1c-44100hz.ogg │ │ ├── gs-16b-1c-44100hz.ogx │ │ ├── gs-16b-1c-44100hz.wav │ │ └── gs-16b-1c-44100hz.wma │ └── stereo │ │ ├── gs-16b-2c-44100hz.aac │ │ ├── gs-16b-2c-44100hz.ac3 │ │ ├── gs-16b-2c-44100hz.aiff │ │ ├── gs-16b-2c-44100hz.flac │ │ ├── gs-16b-2c-44100hz.m4a │ │ ├── gs-16b-2c-44100hz.mp3 │ │ ├── gs-16b-2c-44100hz.mp4 │ │ ├── gs-16b-2c-44100hz.ogg │ │ ├── gs-16b-2c-44100hz.ogx │ │ ├── gs-16b-2c-44100hz.wav │ │ └── gs-16b-2c-44100hz.wma ├── csv │ └── multimodal-random-small.csv ├── feather │ └── multimodal-random-small.feather ├── image_folder │ ├── simple │ │ ├── 0001.png │ │ └── 0002.png │ ├── split_pattern │ │ ├── test │ │ │ ├── cat │ │ │ │ └── 0001.png │ │ │ └── dog │ │ │ │ └── 0001.png │ │ └── train │ │ │ ├── bird │ │ │ └── 0001.png │ │ │ ├── cat │ │ │ └── 0001.png │ │ │ └── dog │ │ │ └── 0001.png │ └── splits │ │ ├── test │ │ └── 0001.png │ │ └── train │ │ └── 0001.png ├── images │ ├── nature-1080p.jpg │ ├── nature-256p.ico │ ├── nature-360p.bmp │ ├── nature-360p.gif │ ├── nature-360p.jpg │ ├── nature-360p.png │ ├── nature-360p.tif │ ├── nature-360p.webp │ ├── nature-720p.jpg │ ├── sea-360p.apng │ └── sea-360p.gif ├── meshes │ ├── tree.ascii.stl │ ├── tree.glb │ ├── tree.gltf │ ├── tree.obj │ ├── tree.off │ ├── tree.ply │ └── tree.stl ├── orc │ └── multimodal-random-small.orc ├── parquet │ └── multimodal-random-small.parquet ├── tables │ └── tallymarks-small.h5 └── videos │ ├── sea-360p-10s.mp4 │ ├── sea-360p.avi │ ├── sea-360p.mkv │ ├── sea-360p.mov │ ├── sea-360p.mp4 │ ├── sea-360p.mpg │ ├── sea-360p.ogg │ ├── sea-360p.webm │ └── sea-360p.wmv ├── docs ├── templates │ └── text.mako └── whitelist.txt ├── index.d.ts ├── openapitools.json ├── package.json ├── playbook ├── allstar │ └── data_slices_sliceguard.ipynb ├── rookie │ ├── decision_boundary_detection.ipynb │ ├── decision_boundary_layout.json │ ├── decision_boundary_layout_colab.json │ ├── embedding_layout.json │ ├── embedding_layout_colab.json │ ├── huggingface_embedding.ipynb │ ├── huggingface_embedding_audio.ipynb │ └── towhee_embedding.ipynb ├── stories │ ├── cleaning_classification_dataset │ │ ├── clean_classification_dataset.ipynb │ │ ├── img │ │ │ ├── clusters_spotlight.png │ │ │ ├── duplicates_spotlight.png │ │ │ ├── inconsistencies_spotlight.png │ │ │ ├── issues_spotlight.png │ │ │ └── outliers_spotlight.png │ │ ├── spotlight-layout-clusters.json │ │ ├── spotlight-layout-duplicates.json │ │ ├── spotlight-layout-issues.json │ │ ├── spotlight-layout-label-inconsistencies.json │ │ └── spotlight-layout-outliers.json │ ├── explore_object_detection.ipynb │ ├── fastf1_spotlight_demo.ipynb │ ├── fine_tune_img.ipynb │ ├── making_of_embeddings_animation.ipynb │ └── navigating_data_issues.ipynb └── veteran │ ├── cv_issue_detection.ipynb │ ├── cv_issues.json │ ├── cv_issues_cleanlab.json │ ├── detect_leakage.json │ ├── drift_kcore.ipynb │ ├── drift_kcore.json │ ├── drift_kcore_colab.json │ ├── duplicates_annoy.ipynb │ ├── duplicates_annoy.json │ ├── duplicates_annoy_colab.json │ ├── label_errors_cleanlab.ipynb │ ├── label_errors_cleanlab.json │ ├── label_errors_cleanlab_colab.json │ ├── layout-drift-kcore.json │ ├── leakage_annoy.ipynb │ ├── leakage_annoy.json │ ├── leakage_annoy_colab.json │ ├── outlier_cleanlab.ipynb │ ├── outlier_cleanlab.json │ └── outlier_cleanlab_colab.json ├── pnpm-lock.yaml ├── poetry.lock ├── public ├── index.css └── spotlight.svg ├── pyproject.toml ├── renumics ├── spotlight │ ├── __init__.py │ ├── __version__.py │ ├── analysis │ │ ├── __init__.py │ │ ├── analyzers │ │ │ ├── __init__.py │ │ │ ├── cleanlab.py │ │ │ └── cleanvision.py │ │ ├── decorator.py │ │ ├── registry.py │ │ └── typing.py │ ├── app.py │ ├── app_config.py │ ├── appdirs.py │ ├── backend │ │ ├── __init__.py │ │ ├── apis │ │ │ ├── __init__.py │ │ │ ├── plugins.py │ │ │ └── websocket.py │ │ ├── config.py │ │ ├── exceptions.py │ │ ├── middlewares │ │ │ ├── __init__.py │ │ │ └── timing.py │ │ ├── statics │ │ ├── tasks │ │ │ ├── __init__.py │ │ │ ├── exceptions.py │ │ │ ├── reduction.py │ │ │ ├── task.py │ │ │ └── task_manager.py │ │ ├── templates │ │ │ └── index.html │ │ └── websockets.py │ ├── cache.py │ ├── cli.py │ ├── data_source │ │ ├── __init__.py │ │ ├── data_source.py │ │ ├── decorator.py │ │ ├── exceptions.py │ │ └── registry.py │ ├── data_store.py │ ├── dataset │ │ ├── __init__.py │ │ ├── descriptors │ │ │ ├── __init__.py │ │ │ └── data_alignment.py │ │ ├── exceptions.py │ │ ├── pandas.py │ │ └── typing.py │ ├── develop │ │ ├── __init__.py │ │ ├── project.py │ │ └── vite.py │ ├── dtypes │ │ ├── __init__.py │ │ ├── conversion.py │ │ └── legacy.py │ ├── embeddings │ │ ├── __init__.py │ │ ├── decorator.py │ │ ├── embedders │ │ │ ├── __init__.py │ │ │ ├── gte.py │ │ │ ├── vit.py │ │ │ └── wav2vec2.py │ │ ├── preprocessors.py │ │ ├── registry.py │ │ └── typing.py │ ├── environ.py │ ├── io │ │ ├── __init__.py │ │ ├── audio.py │ │ ├── file.py │ │ ├── gltf.py │ │ ├── huggingface.py │ │ └── path.py │ ├── layout │ │ ├── __init__.py │ │ ├── lenses.py │ │ ├── nodes.py │ │ └── widgets.py │ ├── layouts │ │ ├── __init__.py │ │ ├── default.py │ │ ├── model_compare.py │ │ └── model_debug.py │ ├── logging.py │ ├── media │ │ ├── __init__.py │ │ ├── audio.py │ │ ├── base.py │ │ ├── embedding.py │ │ ├── exceptions.py │ │ ├── image.py │ │ ├── mesh.py │ │ ├── sequence_1d.py │ │ └── video.py │ ├── plugin_loader.py │ ├── py.typed │ ├── reporting.py │ ├── requests.py │ ├── server.py │ ├── settings.py │ ├── typing.py │ ├── viewer.py │ └── webbrowser.py └── spotlight_plugins │ └── core │ ├── __init__.py │ ├── api │ ├── __init__.py │ ├── config.py │ ├── filebrowser.py │ ├── issues.py │ ├── layout.py │ └── table.py │ ├── arrow_dataset_source.py │ ├── hdf5_data_source.py │ ├── huggingface_datasource.py │ ├── pandas_data_source.py │ └── py.typed ├── scripts ├── Test-SpotlightStart.ps1 ├── check_dynamic_versioning.py ├── generate_api_spec.py ├── generate_demo_test_data.py ├── generate_demo_test_data_ultra.py ├── generate_multimodal_test_data.py ├── generate_performance_test_data.py └── generate_test_csv.py ├── setupTests.js ├── src ├── App.tsx ├── api │ ├── config.ts │ ├── errors.ts │ └── index.ts ├── application.ts ├── browser.ts ├── client │ ├── .openapi-generator-ignore │ ├── .openapi-generator │ │ ├── FILES │ │ └── VERSION │ ├── apis │ │ ├── ConfigApi.ts │ │ ├── DefaultApi.ts │ │ ├── FilebrowserApi.ts │ │ ├── IssuesApi.ts │ │ ├── LayoutApi.ts │ │ ├── PluginsApi.ts │ │ ├── TableApi.ts │ │ └── index.ts │ ├── index.ts │ ├── models │ │ ├── AnalysisInfo.ts │ │ ├── Column.ts │ │ ├── DataIssue.ts │ │ ├── FileEntry.ts │ │ ├── Folder.ts │ │ ├── HTTPValidationError.ts │ │ ├── LocationInner.ts │ │ ├── Plugin.ts │ │ ├── ResponseGetValue.ts │ │ ├── SetConfigRequest.ts │ │ ├── SetLayoutRequest.ts │ │ ├── Table.ts │ │ ├── ValidationError.ts │ │ ├── Value.ts │ │ └── index.ts │ └── runtime.ts ├── components │ ├── AppBar.tsx │ ├── ColumnSelector │ │ ├── ColumnList.tsx │ │ ├── ColumnListItem.tsx │ │ ├── ColumnListSeparator.tsx │ │ ├── ColumnSelector.tsx │ │ └── index.ts │ ├── DataTypeIcon.tsx │ ├── FileBrowser │ │ ├── AddressBar.tsx │ │ ├── FileBrowser.tsx │ │ ├── FileList.tsx │ │ ├── elements.tsx │ │ └── index.ts │ ├── FilterBar │ │ ├── Button.tsx │ │ ├── FilterBar.tsx │ │ ├── FilterCreator.tsx │ │ ├── FilterEditor.tsx │ │ ├── FilterItem.tsx │ │ ├── FilterList.tsx │ │ ├── FilterSelectedButton.tsx │ │ ├── FilterText.tsx │ │ ├── MergeFiltersButton.tsx │ │ ├── ValueInput.tsx │ │ └── index.tsx │ ├── GltfViewer │ │ ├── CameraControls.tsx │ │ ├── GltfScene.tsx │ │ ├── GltfViewer.tsx │ │ ├── index.ts │ │ ├── loading.ts │ │ └── morphing.tsx │ ├── LineChart │ │ ├── LineChart.tsx │ │ └── index.ts │ ├── LoadingError.tsx │ ├── LoadingIndicator.tsx │ ├── Logo.tsx │ ├── ScalarValue.tsx │ ├── StatusBar.tsx │ ├── ToolBar.tsx │ ├── WebGLDetector │ │ └── index.tsx │ ├── Workspace │ │ ├── AddWidgetButton.tsx │ │ ├── AddWidgetDropdown.tsx │ │ ├── ComponentFactory.tsx │ │ ├── Styles.tsx │ │ ├── Workspace.tsx │ │ ├── icons.tsx │ │ ├── images │ │ │ ├── maximize.svg │ │ │ └── minimize.svg │ │ ├── index.ts │ │ └── layout.ts │ ├── index.ts │ ├── shared │ │ ├── AudioViewer │ │ │ └── index.tsx │ │ └── Plot │ │ │ ├── Brush.tsx │ │ │ ├── Legend │ │ │ ├── CategoricalLegend.tsx │ │ │ ├── ContinuousLegend.tsx │ │ │ └── index.tsx │ │ │ ├── Plot.tsx │ │ │ ├── PlotContext.tsx │ │ │ ├── Points.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── XAxis.tsx │ │ │ ├── YAxis.tsx │ │ │ ├── Zoom.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ ├── ui │ │ ├── Button.tsx │ │ ├── CellBadge.tsx │ │ ├── Center.tsx │ │ ├── Checkbox.tsx │ │ ├── ColorPalette.tsx │ │ ├── ColorPaletteSelect.tsx │ │ ├── ColumnBadge.tsx │ │ ├── ConfirmationDialog.tsx │ │ ├── ContextMenu.tsx │ │ ├── Dialog.tsx │ │ ├── Dot.tsx │ │ ├── Dropdown.tsx │ │ ├── Html.tsx │ │ ├── Info.tsx │ │ ├── LabeledSlider.tsx │ │ ├── Markdown.tsx │ │ ├── Menu │ │ │ ├── ActionInput.tsx │ │ │ ├── ColumnSelect.tsx │ │ │ ├── HorizontalDivider.tsx │ │ │ ├── Input.tsx │ │ │ ├── Item.tsx │ │ │ ├── Menu.tsx │ │ │ ├── MultiColumnSelect.tsx │ │ │ ├── Subtitle.tsx │ │ │ ├── Switch.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── Title.tsx │ │ │ └── index.tsx │ │ ├── NeedsUpgradeButton.tsx │ │ ├── Pill.tsx │ │ ├── Popup.tsx │ │ ├── Select │ │ │ ├── Select.tsx │ │ │ ├── index.tsx │ │ │ ├── styles.ts │ │ │ └── types.ts │ │ ├── Slider.tsx │ │ ├── Spinner.tsx │ │ ├── Tag.tsx │ │ ├── ToggleButton.tsx │ │ ├── Tooltip.tsx │ │ ├── WidgetContainer.tsx │ │ ├── WidgetContent.tsx │ │ ├── WidgetMenu.tsx │ │ └── index.ts │ └── walkthrough │ │ ├── FileBrowserWalkthrough.tsx │ │ ├── MainWalkthrough.tsx │ │ ├── Tour.tsx │ │ └── index.ts ├── dataformat │ └── index.ts ├── datatypes.ts ├── filters.ts ├── globals.ts ├── hooks │ ├── index.ts │ ├── useCell.ts │ ├── useColorTransferFunction.ts │ ├── useKeyPressed.ts │ ├── useMemoCompare.ts │ ├── useMemoWithPrevious.ts │ ├── useOnClickOutside.ts │ ├── usePersistentState.ts │ ├── usePrevious.ts │ ├── useSize.ts │ ├── useTimeout.ts │ └── useWhyDidYouUpdate.ts ├── icons │ ├── Add.tsx │ ├── AddBox.tsx │ ├── AddCell.tsx │ ├── Annotation.tsx │ ├── Array.tsx │ ├── Audio.tsx │ ├── Autoplay.tsx │ ├── Ban.tsx │ ├── BoundingBox.tsx │ ├── Brush.tsx │ ├── Bubbles.tsx │ ├── Calendar.tsx │ ├── Categorical.tsx │ ├── Check.tsx │ ├── Checked.tsx │ ├── ClipboardList.tsx │ ├── ColorPalette.tsx │ ├── Copy.tsx │ ├── Cube.tsx │ ├── Delete.tsx │ ├── Docs.tsx │ ├── Down.tsx │ ├── Edit.tsx │ ├── EditOff.tsx │ ├── Embedding.tsx │ ├── File.tsx │ ├── Filter.tsx │ ├── FilterOff.tsx │ ├── Folder.tsx │ ├── Gauge.tsx │ ├── Github.tsx │ ├── Grid.tsx │ ├── GroupSelection.tsx │ ├── Help.tsx │ ├── Hide.tsx │ ├── Histogram.tsx │ ├── Image.tsx │ ├── Layout.tsx │ ├── Lightbulb.tsx │ ├── Location.tsx │ ├── LockClosed.tsx │ ├── LockOpen.tsx │ ├── Maximize.tsx │ ├── Mesh.tsx │ ├── Minimize.tsx │ ├── Number.tsx │ ├── OpenFolder.tsx │ ├── Question.tsx │ ├── Refresh.tsx │ ├── Repeat.tsx │ ├── Reset.tsx │ ├── ResetLayout.tsx │ ├── Resize.tsx │ ├── Right.tsx │ ├── Save.tsx │ ├── ScatterPlot.tsx │ ├── Selection.tsx │ ├── Sequence.tsx │ ├── Sequence1D.tsx │ ├── Settings.tsx │ ├── Show.tsx │ ├── Soundwave.tsx │ ├── Table.tsx │ ├── Text.tsx │ ├── Tour.tsx │ ├── TriangleDown.tsx │ ├── TriangleRight.tsx │ ├── Unchecked.tsx │ ├── Union.tsx │ ├── Up.tsx │ ├── Video.tsx │ ├── Warning.tsx │ ├── Window.tsx │ ├── WordCloud.tsx │ ├── X.tsx │ ├── XCircle.tsx │ └── index.ts ├── lenses │ ├── ArrayLens.tsx │ ├── AudioLens.tsx │ ├── BLEUScoreLens.tsx │ ├── BoundingBoxLens │ │ ├── BBox.tsx │ │ └── index.tsx │ ├── HtmlLens.tsx │ ├── ImageLens │ │ ├── MenuBar.tsx │ │ └── index.tsx │ ├── LensContext.tsx │ ├── LensFactory.tsx │ ├── MarkdownLens.tsx │ ├── MeshLens │ │ ├── MenuBar.tsx │ │ └── index.tsx │ ├── None.tsx │ ├── RougeScoreLens.tsx │ ├── SafeHtmlLens.tsx │ ├── ScalarLens.tsx │ ├── SequenceLens │ │ ├── MenuBar.tsx │ │ └── index.tsx │ ├── SpectrogramLens │ │ ├── MelScale.tsx │ │ ├── MenuBar.tsx │ │ ├── Spectrogram.tsx │ │ ├── SpectrogramWorker.ts │ │ └── index.tsx │ ├── TextLens.tsx │ ├── VideoLens.tsx │ ├── index.ts │ ├── useCellValues.ts │ ├── useSetting.ts │ └── useSharedState.ts ├── lib.ts ├── main.tsx ├── math │ ├── distances.ts │ ├── downsampling.test.ts │ └── downsampling.ts ├── notify.tsx ├── palettes.ts ├── services │ ├── config.ts │ ├── data.ts │ ├── task.ts │ └── websocket │ │ ├── connection.ts │ │ ├── index.ts │ │ └── types.ts ├── stores │ ├── appSettings.ts │ ├── colors.ts │ ├── components.ts │ ├── dataset │ │ ├── colorTransferFunctionFactory.tsx │ │ ├── columnFactory.ts │ │ ├── dataset.ts │ │ ├── index.ts │ │ ├── relevanceWorker.ts │ │ └── statisticsFactory.tsx │ ├── layout.ts │ ├── messages.ts │ └── pluginStore.ts ├── styles │ └── GlobalStyles.tsx ├── systems │ └── dnd │ │ ├── DragContext.tsx │ │ ├── Draggable.tsx │ │ ├── Droppable.tsx │ │ ├── OverlayFactory.tsx │ │ ├── index.ts │ │ └── types.ts ├── types │ ├── base.ts │ ├── dataset.ts │ ├── filter.ts │ ├── index.ts │ ├── layout.ts │ ├── lenses.ts │ └── problem.ts ├── vite-env.d.ts └── widgets │ ├── ConfusionMatrix │ ├── Matrix.tsx │ ├── hooks.ts │ ├── index.tsx │ └── types.ts │ ├── DataGrid │ ├── Cell │ │ ├── CategoricalCell.tsx │ │ ├── Cell.tsx │ │ ├── CellContextMenu.tsx │ │ ├── CellFactory.tsx │ │ ├── CellPlaceholder.tsx │ │ ├── DefaultCell.tsx │ │ ├── HeaderCell.tsx │ │ ├── HeaderCellContextMenu.tsx │ │ ├── NumberCell.tsx │ │ └── index.ts │ ├── DataGrid.tsx │ ├── GridContextMenu.tsx │ ├── HeaderGrid.tsx │ ├── KeyboardControls.tsx │ ├── MenuBar │ │ ├── AddColumnButton.tsx │ │ ├── MenuBar.tsx │ │ ├── SettingsMenu.tsx │ │ ├── TableViewSelection.tsx │ │ └── index.ts │ ├── MouseControls.tsx │ ├── RelevanceIndicator.tsx │ ├── TableGrid.tsx │ ├── columnWidthByType.ts │ ├── context │ │ ├── columnContext.tsx │ │ ├── resizeContext.tsx │ │ ├── sortingContext.tsx │ │ └── tableViewContext.tsx │ ├── getRowHeight.ts │ ├── hooks │ │ ├── useCellValue.ts │ │ ├── useHighlight.ts │ │ ├── useRowCount.ts │ │ └── useSort.ts │ ├── index.ts │ └── useRowHighlight.ts │ ├── Histogram │ ├── Bars.tsx │ ├── Histogram.tsx │ ├── Tooltip.tsx │ ├── XAxis.tsx │ ├── index.ts │ ├── types.ts │ └── useHistogram.ts │ ├── Inspector │ ├── AddViewButton.tsx │ ├── DetailCell.tsx │ ├── DetailsGrid.tsx │ ├── Header │ │ ├── Header.tsx │ │ ├── Row.tsx │ │ └── index.ts │ ├── Inspector.tsx │ ├── MenuBar │ │ ├── ColumnCountSelect.tsx │ │ ├── MenuBar.tsx │ │ └── index.tsx │ ├── ViewConfigurator │ │ ├── ColumnList.tsx │ │ ├── ColumnListItem.tsx │ │ ├── ColumnListSeparator.tsx │ │ ├── ViewConfigurator.tsx │ │ └── index.ts │ ├── ViewGrid.tsx │ ├── index.ts │ ├── rowHeightContext.tsx │ ├── store.tsx │ └── types.tsx │ ├── IssuesWidget.tsx │ ├── MetricsWidget │ ├── confusion.ts │ ├── index.tsx │ ├── metrics.ts │ └── types.ts │ ├── ScatterplotView │ ├── MenuBar.tsx │ ├── ScatterplotView.tsx │ └── index.tsx │ ├── SimilarityMap │ ├── MenuBar.tsx │ ├── PCAParameterMenu.tsx │ ├── SimilarityMap.tsx │ ├── TooltipContent.tsx │ ├── UmapParameterMenu.tsx │ ├── index.ts │ └── types.ts │ ├── WidgetContext.ts │ ├── WidgetFactory.tsx │ ├── WordCloudView │ ├── Cloud.tsx │ ├── MenuBar.tsx │ ├── WordCloudView.tsx │ ├── index.tsx │ └── stopwords.json │ ├── index.ts │ ├── types.ts │ └── useWidgetConfig.ts ├── static └── img │ ├── dataframe_head_sample.png │ ├── spotlight.svg │ ├── spotlight_features.gif │ └── spotlight_video.gif ├── tailwind.config.cjs ├── tests ├── __init__.py ├── conftest.py ├── integration │ ├── __init__.py │ ├── backend │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_app_csv_df.py │ │ ├── test_app_tally_df.py │ │ ├── test_cache.py │ │ ├── test_double_app_tally_df.py │ │ └── test_reporting.py │ ├── data_source │ │ └── test_pandas_data_source.py │ ├── dataset │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── data.py │ │ ├── helpers.py │ │ ├── test_bounding_box_column.py │ │ ├── test_categorical.py │ │ ├── test_dataset.py │ │ ├── test_descriptors.py │ │ ├── test_embedding_column.py │ │ ├── test_fancy_indexing.py │ │ ├── test_h5_file_parsing.py │ │ └── test_prune.py │ ├── formats │ │ ├── __init__.py │ │ └── test_formats.py │ ├── h5 │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── data.py │ │ └── test_h5.py │ ├── helpers.py │ ├── huggingface │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── dataset.py │ │ └── test_hf.py │ ├── layout │ │ ├── __init__.py │ │ └── test_setting.py │ ├── media │ │ ├── __init__.py │ │ ├── data.py │ │ ├── test_audio.py │ │ ├── test_image.py │ │ ├── test_mesh.py │ │ └── test_video.py │ └── pandas │ │ └── test_pandas.py ├── ui │ ├── __init__.py │ ├── basic_actions.py │ ├── conftest.py │ ├── generate_ui_test_elements.py │ ├── helpers.py │ ├── test_basics.py │ ├── test_load_perfomance.py │ ├── test_similarity_map.py │ └── test_startpage.py └── unit │ ├── __init__.py │ ├── dtypes │ ├── __init__.py │ └── test_conversion.py │ ├── io │ ├── __init__.py │ └── test_audio.py │ └── media │ ├── __init__.py │ ├── data.py │ ├── test_audio.py │ ├── test_embedding.py │ ├── test_image.py │ ├── test_mesh.py │ ├── test_sequence_1d.py │ └── test_video.py ├── tsconfig.json ├── tsconfig.node.json ├── types ├── chroma-js.d.ts ├── env.d.ts ├── globals.d.ts ├── rougeScore.d.ts ├── twin.d.ts └── wavesurfer.d.ts └── vite.config.ts /.env: -------------------------------------------------------------------------------- 1 | TAG=dev 2 | BUILD_ENV=dev 3 | VITE_PUBLIC_URL="./" 4 | VITE_PREVIEW_USER="Volker Vorschau" 5 | VITE_DOCS_URL="https://docs.renumics.com/spotlight/" 6 | VITE_VERSION=$VERSION 7 | VITE_API_BASE_PATH="." 8 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/.env.development -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | layout_poetry() { 2 | if [[ ! -f pyproject.toml ]]; then 3 | log_error 'No pyproject.toml found. Use `poetry new` or `poetry init` to create one first.' 4 | exit 2 5 | fi 6 | 7 | # create venv if it doesn't exist 8 | poetry run true 9 | 10 | export VIRTUAL_ENV=$(poetry env info --path) 11 | export POETRY_ACTIVE=1 12 | PATH_add "$VIRTUAL_ENV/bin" 13 | } 14 | 15 | export VERSION=$(poetry version -s) 16 | 17 | dotenv 18 | use asdf 19 | 20 | export PIP_IGNORE_INSTALLED=1 21 | layout poetry 22 | 23 | pre-commit install --hook-type pre-commit 24 | pre-commit install --hook-type pre-push 25 | 26 | export SPOTLIGHT_DEV=True 27 | export SPOTLIGHT_VERBOSE=True 28 | export SPOTLIGHT_OPT_OUT=True 29 | 30 | # source local envrc for any additional local defines and overrides (e.g. a different BUILD_VARIANT) 31 | [[ -f .envrc.local ]] && source_env .envrc.local 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help us improve by submitting a detailed bug report 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | Please provide a clear and concise description of the bug you've encountered. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. Observe the error 19 | 20 | **Expected behavior** 21 | Describe what you expected to happen instead of the bug. 22 | 23 | **Screenshots** 24 | If possible, include screenshots or screen recordings to better illustrate the issue. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Browser Version [e.g. 22] 31 | - Spotlight Version [e.g. 1.0.0] 32 | 33 | **Additional context** 34 | Provide any additional context or information that could be relevant to understanding the issue. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Propose a new feature or enhancement for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | Please describe the problem or motivation behind your feature request. For example, "I find it difficult to [...]" 11 | 12 | **Describe the solution you'd like** 13 | Provide a clear and concise description of the feature or enhancement you would like to see implemented. 14 | 15 | **Describe alternatives you've considered** 16 | Discuss any alternative solutions or features you have considered, explaining the pros and cons of each. 17 | 18 | **Additional context** 19 | Include any additional context, examples, or screenshots that can help illustrate your feature request. 20 | This can assist in providing a better understanding of your proposal. 21 | -------------------------------------------------------------------------------- /.github/actions/setup-pnpm/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup pnpm 2 | description: Prepare Node.js and pnpm environment, cache/restore dependencies 3 | inputs: 4 | node-version: 5 | description: Node.js version to use 6 | default: '19' 7 | pnpm-version: 8 | description: pnpm version to use 9 | default: latest 10 | workdir: 11 | description: directory to find pnpm-lock.yaml in 12 | default: '.' 13 | runs: 14 | using: composite 15 | steps: 16 | - name: Install pnpm ${{ inputs.pnpm-version }} 17 | uses: pnpm/action-setup@v3 18 | with: 19 | version: ${{ inputs.pnpm-version }} 20 | - name: Set up Node.js ${{ inputs.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ inputs.node-version }} 24 | cache: pnpm 25 | cache-dependency-path: ${{ inputs.workdir }}/pnpm-lock.yaml 26 | - name: Install pnpm dependencies 27 | run: cd ${{ inputs.workdir }} && pnpm install --frozen-lockfile 28 | shell: bash 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs 2 | .vscode/ 3 | .idea/ 4 | 5 | # Build 6 | dist/ 7 | build/ 8 | 9 | # Dev 10 | dev/ 11 | /scratch/ 12 | test_install/ 13 | .tool-versions 14 | .envrc.local 15 | .DS_Store 16 | *.log 17 | 18 | # Cache 19 | __pycache__/ 20 | .mypy_cache/ 21 | .pytest_cache/ 22 | .ruff_cache/ 23 | .ipynb_checkpoints/ 24 | 25 | # Spotlight licenses 26 | *.key 27 | 28 | # Datasets 29 | *.h5 30 | *.csv 31 | /data/tables/ 32 | /data/coco/ 33 | !/data/tables/tallymarks-small.h5 34 | !/data/csv/multimodal-random-small.csv 35 | 36 | #venv 37 | .venv/ 38 | 39 | # dependencies 40 | /node_modules 41 | /.pnp 42 | .pnp.js 43 | 44 | # testing 45 | /coverage 46 | geckodriver.log 47 | /tests/ui/_autogenerated_ui_elements.py 48 | 49 | # misc 50 | .DS_Store 51 | .env*local 52 | .env*local 53 | 54 | # Logs 55 | logs 56 | npm-debug.log* 57 | yarn-debug.log* 58 | yarn-error.log* 59 | pnpm-debug.log* 60 | lerna-debug.log* 61 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .venv/ 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | trailingComma: es5 2 | tabWidth: 4 3 | printWidth: 88 4 | singleQuote: true 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to Spotlight 2 | 3 | Everyone is welcome to contribute, and we value everybody's contribution. Code 4 | contributions are not the only way to help the community. 5 | Reporting bugs, suggesting features, contributing design ideas, 6 | or offering feedback is also extremely valuable to us. 7 | 8 | Technical details on how to contribute can be found in our [documentation](https://renumics.com/docs/development). 9 | 10 | ## Ways to contribute 11 | 12 | There are several ways you can contribute to Spotlight: 13 | 14 | - Fix outstanding issues. 15 | - Implement new features. 16 | - Submit issues related to bugs or desired new features. 17 | - Share your use case. 18 | 19 | If you don't know where to start, you might want to take a look at [hacktoberfest issues](https://github.com/Renumics/spotlight/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest) 20 | and our guide on how to create a [new Lens](https://renumics.com/docs/development/lenses). 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2023 Renumics GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Audio 2 | 3 | Original music files: “Furious Freak” and “Galway”, Kevin MacLeod 4 | (incompetech.com), Licensed under Creative Commons: By Attribution 3.0, 5 | http://creativecommons.org/licenses/by/3.0/ 6 | 7 | Audio saved in different file formats taken from 8 | https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/en/latest/design-guide/audio-samples.html. 9 | 10 | # Images 11 | 12 | Photo by [Alexander](https://unsplash.com/@blgnlife) on 13 | [Unsplash](https://unsplash.com/photos/nRuPY_8fvrU), cropped, resized and saved 14 | in different file formats. 15 | 16 | # Meshes 17 | 18 | "Pine Tree - Proto Series - Free" (https://skfb.ly/6QTuG) by BitGem is licensed 19 | under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/). 20 | 21 | Mesh saved in different file formats. 22 | 23 | # Videos 24 | 25 | Video by [Javier Lemus](https://pixabay.com/users/javlemus-12694742/) on 26 | [Pixabay](https://pixabay.com/), shortened and saved in different file formats, 27 | including animation. 28 | -------------------------------------------------------------------------------- /data/audio/1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/1.wav -------------------------------------------------------------------------------- /data/audio/2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/2.wav -------------------------------------------------------------------------------- /data/audio/3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/3.wav -------------------------------------------------------------------------------- /data/audio/4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/4.wav -------------------------------------------------------------------------------- /data/audio/5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/5.wav -------------------------------------------------------------------------------- /data/audio/6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/6.wav -------------------------------------------------------------------------------- /data/audio/7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/7.wav -------------------------------------------------------------------------------- /data/audio/8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/8.wav -------------------------------------------------------------------------------- /data/audio/9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/9.wav -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.aac -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.ac3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.ac3 -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.aiff -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.flac -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.m4a -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.mp3 -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.ogg -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.ogx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.ogx -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.wav -------------------------------------------------------------------------------- /data/audio/mono/gs-16b-1c-44100hz.wma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/mono/gs-16b-1c-44100hz.wma -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.aac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.aac -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.ac3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.ac3 -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.aiff -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.flac -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.m4a -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.mp3 -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.mp4 -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.ogg -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.ogx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.ogx -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.wav -------------------------------------------------------------------------------- /data/audio/stereo/gs-16b-2c-44100hz.wma: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/audio/stereo/gs-16b-2c-44100hz.wma -------------------------------------------------------------------------------- /data/feather/multimodal-random-small.feather: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/feather/multimodal-random-small.feather -------------------------------------------------------------------------------- /data/image_folder/simple/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/simple/0001.png -------------------------------------------------------------------------------- /data/image_folder/simple/0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/simple/0002.png -------------------------------------------------------------------------------- /data/image_folder/split_pattern/test/cat/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/split_pattern/test/cat/0001.png -------------------------------------------------------------------------------- /data/image_folder/split_pattern/test/dog/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/split_pattern/test/dog/0001.png -------------------------------------------------------------------------------- /data/image_folder/split_pattern/train/bird/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/split_pattern/train/bird/0001.png -------------------------------------------------------------------------------- /data/image_folder/split_pattern/train/cat/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/split_pattern/train/cat/0001.png -------------------------------------------------------------------------------- /data/image_folder/split_pattern/train/dog/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/split_pattern/train/dog/0001.png -------------------------------------------------------------------------------- /data/image_folder/splits/test/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/splits/test/0001.png -------------------------------------------------------------------------------- /data/image_folder/splits/train/0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/image_folder/splits/train/0001.png -------------------------------------------------------------------------------- /data/images/nature-1080p.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-1080p.jpg -------------------------------------------------------------------------------- /data/images/nature-256p.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-256p.ico -------------------------------------------------------------------------------- /data/images/nature-360p.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.bmp -------------------------------------------------------------------------------- /data/images/nature-360p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.gif -------------------------------------------------------------------------------- /data/images/nature-360p.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.jpg -------------------------------------------------------------------------------- /data/images/nature-360p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.png -------------------------------------------------------------------------------- /data/images/nature-360p.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.tif -------------------------------------------------------------------------------- /data/images/nature-360p.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-360p.webp -------------------------------------------------------------------------------- /data/images/nature-720p.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/nature-720p.jpg -------------------------------------------------------------------------------- /data/images/sea-360p.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/sea-360p.apng -------------------------------------------------------------------------------- /data/images/sea-360p.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/images/sea-360p.gif -------------------------------------------------------------------------------- /data/meshes/tree.ascii.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/meshes/tree.ascii.stl -------------------------------------------------------------------------------- /data/meshes/tree.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/meshes/tree.glb -------------------------------------------------------------------------------- /data/orc/multimodal-random-small.orc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/orc/multimodal-random-small.orc -------------------------------------------------------------------------------- /data/parquet/multimodal-random-small.parquet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/parquet/multimodal-random-small.parquet -------------------------------------------------------------------------------- /data/tables/tallymarks-small.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/tables/tallymarks-small.h5 -------------------------------------------------------------------------------- /data/videos/sea-360p-10s.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p-10s.mp4 -------------------------------------------------------------------------------- /data/videos/sea-360p.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.avi -------------------------------------------------------------------------------- /data/videos/sea-360p.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.mkv -------------------------------------------------------------------------------- /data/videos/sea-360p.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.mov -------------------------------------------------------------------------------- /data/videos/sea-360p.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.mp4 -------------------------------------------------------------------------------- /data/videos/sea-360p.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.mpg -------------------------------------------------------------------------------- /data/videos/sea-360p.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.ogg -------------------------------------------------------------------------------- /data/videos/sea-360p.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.webm -------------------------------------------------------------------------------- /data/videos/sea-360p.wmv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/data/videos/sea-360p.wmv -------------------------------------------------------------------------------- /docs/whitelist.txt: -------------------------------------------------------------------------------- 1 | index.md 2 | layout/index.md 3 | layout/lenses.md 4 | dataset/index.md 5 | dtypes/index.md 6 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './src/lib'; 2 | export * from './src/icons'; 3 | -------------------------------------------------------------------------------- /openapitools.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", 3 | "spaces": 2, 4 | "generator-cli": { 5 | "version": "7.0.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /playbook/rookie/decision_boundary_layout_colab.json: -------------------------------------------------------------------------------- 1 | {"orientation":"vertical","children":[{"kind":"split","weight":55.410225921522,"orientation":"horizontal","children":[{"kind":"tab","weight":39.81582591147937,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"tab","weight":30.613613682809017,"children":[{"kind":"widget","name":"Scatter Plot","type":"scatterplot","config":{"xAxisColumn":"embx","yAxisColumn":"emby","colorBy":"decision_boundary_score","sizeBy":null,"filter":false}}]},{"kind":"tab","weight":29.570560405711603,"children":[{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":"decision_boundary_score","stackByColumnKey":"","filter":false}}]}]},{"kind":"tab","weight":44.589774078478,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ImageView","columns":["image"],"name":"view","key":"a0f7869f-e311-48ab-96ca-354deca675e0"},{"view":"ScalarView","columns":["fine_label_prediction_str"],"name":"view","key":"0a91ba21-8dd6-44a7-9ad3-51629cad1144"},{"view":"ScalarView","columns":["decision_boundary_alternate_label_str"],"name":"view","key":"5f3f373b-2124-45b7-ab24-aba223647a93"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /playbook/rookie/embedding_layout_colab.json: -------------------------------------------------------------------------------- 1 | {"orientation":"vertical","children":[{"kind":"split","weight":60,"orientation":"horizontal","children":[{"kind":"tab","weight":50.390828556539866,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"tab","weight":49.609171443460134,"children":[{"kind":"widget","name":"Scatter Plot","type":"scatterplot","config":{"xAxisColumn":"embx","yAxisColumn":"emby","colorBy":"fine_label_str","sizeBy":null,"filter":false}},{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":null,"stackByColumnKey":null,"filter":false}}]}]},{"kind":"tab","weight":40,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ImageView","columns":["image"],"name":"view","key":"92810da9-f611-4476-af74-6a6edb1f248f"},{"view":"ScalarView","columns":["fine_label_str"],"name":"view","key":"e29c2cba-858d-45b7-bbb7-010a495f4b80"},{"view":"ScalarView","columns":["fine_label_prediction_str"],"name":"view","key":"b2389909-3408-4cd4-b9c7-295e252c8461"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /playbook/stories/cleaning_classification_dataset/img/clusters_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/playbook/stories/cleaning_classification_dataset/img/clusters_spotlight.png -------------------------------------------------------------------------------- /playbook/stories/cleaning_classification_dataset/img/duplicates_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/playbook/stories/cleaning_classification_dataset/img/duplicates_spotlight.png -------------------------------------------------------------------------------- /playbook/stories/cleaning_classification_dataset/img/inconsistencies_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/playbook/stories/cleaning_classification_dataset/img/inconsistencies_spotlight.png -------------------------------------------------------------------------------- /playbook/stories/cleaning_classification_dataset/img/issues_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/playbook/stories/cleaning_classification_dataset/img/issues_spotlight.png -------------------------------------------------------------------------------- /playbook/stories/cleaning_classification_dataset/img/outliers_spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/playbook/stories/cleaning_classification_dataset/img/outliers_spotlight.png -------------------------------------------------------------------------------- /playbook/veteran/detect_leakage.json: -------------------------------------------------------------------------------- 1 | {"orientation":"vertical","children":[{"kind":"split","weight":44.11473788328388,"orientation":"horizontal","children":[{"kind":"tab","weight":39.052132701421804,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"tab","weight":33.697398630858345,"children":[{"kind":"widget","name":"Similarity Map","type":"similaritymap","config":{"placeBy":null,"reductionMethod":null,"colorBy":"split","sizeBy":null,"filter":false,"umapNNeighbors":20,"umapMetric":null,"umapMinDist":0.15,"pcaNormalization":null,"umapMenuLocalGlobalBalance":null,"umapMenuIsAdvanced":false}}]},{"kind":"tab","weight":27.25046866771985,"children":[{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":null,"stackByColumnKey":null,"filter":true}}]}]},{"kind":"tab","weight":55.88526211671612,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ScalarView","columns":["split"],"name":"view","key":"1b3f53f3-58b7-41ae-8f4b-2b9837baeff5"},{"view":"ImageView","columns":["image"],"name":"view","key":"cf738a3e-ed55-40ab-bdce-1a21519ddd3f"},{"view":"ImageView","columns":["nn_image"],"name":"view","key":"2953e69a-7d90-404e-a416-631a6bfcced0"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /playbook/veteran/drift_kcore_colab.json: -------------------------------------------------------------------------------- 1 | {"orientation":"vertical","children":[{"kind":"split","weight":60,"orientation":"horizontal","children":[{"kind":"tab","weight":38.10641975622466,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"tab","weight":34.56163652308748,"children":[{"kind":"widget","name":"Scatter Plot","type":"scatterplot","config":{"xAxisColumn":"embx","yAxisColumn":"emby","colorBy":"k_core_distance","sizeBy":null,"filter":false}}]},{"kind":"tab","weight":27.331943720687857,"children":[{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":"k_core_distance","stackByColumnKey":null,"filter":false}}]}]},{"kind":"tab","weight":40,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ScalarView","columns":["fine_label_str"],"name":"view","key":"b0f7fa76-ae8d-4701-85b8-46fb016587c1"},{"view":"ImageView","columns":["image"],"name":"view","key":"2333e9e7-15c9-43b6-a4ae-2a512511d359"},{"view":"ScalarView","columns":["k_core_distance"],"name":"view","key":"525a6d61-312d-4c62-90cb-ad59ad67b644"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /playbook/veteran/duplicates_annoy_colab.json: -------------------------------------------------------------------------------- 1 | {"orientation":"horizontal","children":[{"kind":"tab","weight":29.74592833876222,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"split","weight":30.25407166123778,"orientation":"vertical","children":[{"kind":"tab","weight":50,"children":[{"kind":"widget","name":"Scatter Plot","type":"scatterplot","config":{"xAxisColumn":"embx","yAxisColumn":"emby","colorBy":"nn_distance","sizeBy":null,"filter":false}}]},{"kind":"tab","weight":50,"children":[{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":"nn_distance","stackByColumnKey":null,"filter":false}}]}]},{"kind":"tab","weight":25,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ImageView","columns":["image"],"name":"view","key":"c9a000b3-75c7-4f95-ad53-9967d7d333ff"},{"view":"ScalarView","columns":["fine_label_str"],"name":"view","key":"ff04b0be-118c-4ec3-8f6f-be9ef651869b"},{"view":"ImageView","columns":["nn_image"],"name":"view","key":"08f228f2-6452-4110-be90-07478771a667"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /playbook/veteran/layout-drift-kcore.json: -------------------------------------------------------------------------------- 1 | {"orientation":"vertical","children":[{"kind":"split","weight":60,"orientation":"horizontal","children":[{"kind":"tab","weight":27.0979967514889,"children":[{"kind":"widget","name":"Table","type":"table","config":{"tableView":"full","visibleColumns":null,"sorting":null,"orderByRelevance":false}}]},{"kind":"tab","weight":34.310013424763966,"children":[{"kind":"widget","name":"Similarity Map","type":"similaritymap"}]},{"kind":"tab","weight":38.59198982374714,"children":[{"kind":"widget","name":"Histogram","type":"histogram","config":{"columnKey":"k_core_distance","stackByColumnKey":"split","filter":false}}]}]},{"kind":"tab","weight":40,"children":[{"kind":"widget","name":"Inspector","type":"inspector","config":{"views":[{"view":"ImageView","columns":["image"],"name":"view","key":"4f1e059e-74ba-4881-bb59-74a962f3d1dd"},{"view":"ScalarView","columns":["fine_label_str"],"name":"view","key":"1be70703-0bbe-4e40-8d19-9a404254208e"},{"view":"ScalarView","columns":["fine_label_prediction_str"],"name":"view","key":"b72134f9-793c-4f24-ae12-4cd95df38e8a"}],"visibleColumns":4}}]}]} -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | user-select: none; 3 | -webkit-user-select: none; /* Chrome all / Safari all */ 4 | -moz-user-select: none; /* Firefox all */ 5 | -ms-user-select: none; /* IE 10+ */ 6 | } 7 | 8 | body { 9 | overflow: hidden; 10 | } 11 | 12 | div#modalRoot { 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | height: 0; 17 | width: 0; 18 | z-index: 99; 19 | } 20 | 21 | div#popupRoot { 22 | position: fixed; 23 | top: 0; 24 | left: 0; 25 | height: 0; 26 | width: 0; 27 | z-index: 999; 28 | } 29 | 30 | div#selectMenuRoot { 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | height: 0; 35 | width: 0; 36 | z-index: 99999; 37 | } 38 | -------------------------------------------------------------------------------- /public/spotlight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /renumics/spotlight/__version__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package version. 3 | 4 | Replaced at build time. 5 | """ 6 | 7 | __version__ = "0.0.0" 8 | -------------------------------------------------------------------------------- /renumics/spotlight/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dataset Analysis 3 | """ 4 | 5 | import importlib 6 | import pkgutil 7 | from typing import List 8 | 9 | from renumics.spotlight.data_store import DataStore 10 | from renumics.spotlight.logging import logger 11 | 12 | from . import analyzers as analyzers_namespace 13 | from .registry import registered_analyzers 14 | from .typing import DataIssue 15 | 16 | # import all modules in .analyzers 17 | for module_info in pkgutil.iter_modules(analyzers_namespace.__path__): 18 | importlib.import_module(analyzers_namespace.__name__ + "." + module_info.name) 19 | 20 | 21 | def find_issues(data_store: DataStore, columns: List[str]) -> List[DataIssue]: 22 | """ 23 | Find dataset issues in the data source 24 | """ 25 | 26 | logger.info("Analysis started.") 27 | 28 | issues: List[DataIssue] = [] 29 | for analyze in registered_analyzers: 30 | issues.extend(analyze(data_store, columns)) 31 | 32 | logger.info("Analysis done.") 33 | 34 | return issues 35 | -------------------------------------------------------------------------------- /renumics/spotlight/analysis/analyzers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/analysis/analyzers/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight/analysis/decorator.py: -------------------------------------------------------------------------------- 1 | """ 2 | A decorator for data analysis functions 3 | """ 4 | 5 | from .registry import register_analyzer 6 | from .typing import DataAnalyzer 7 | 8 | 9 | def data_analyzer(func: DataAnalyzer) -> DataAnalyzer: 10 | """ 11 | register a data analysis function 12 | """ 13 | register_analyzer(func) 14 | return func 15 | -------------------------------------------------------------------------------- /renumics/spotlight/analysis/registry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage data analyzers available for spotlights automatic dataset analysis. 3 | """ 4 | 5 | from typing import Set 6 | 7 | from .typing import DataAnalyzer 8 | 9 | registered_analyzers: Set[DataAnalyzer] = set() 10 | 11 | 12 | def register_analyzer(analyzer: DataAnalyzer) -> None: 13 | """ 14 | Register a data analyzer 15 | """ 16 | registered_analyzers.add(analyzer) 17 | 18 | 19 | def unregister_analyzer(analyzer: DataAnalyzer) -> None: 20 | """ 21 | Unregister a data analyzer 22 | """ 23 | registered_analyzers.remove(analyzer) 24 | -------------------------------------------------------------------------------- /renumics/spotlight/analysis/typing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Shared types for data analysis 3 | """ 4 | 5 | from typing import Callable, Iterable, List, Literal, Optional 6 | 7 | from pydantic.dataclasses import dataclass 8 | 9 | from renumics.spotlight.data_store import DataStore 10 | 11 | Severity = Literal["low", "medium", "high"] 12 | 13 | 14 | @dataclass 15 | class DataIssue: 16 | """ 17 | An Issue affecting multiple rows of the dataset 18 | """ 19 | 20 | title: str 21 | rows: List[int] 22 | severity: Severity = "medium" 23 | columns: Optional[List[str]] = None 24 | description: str = "" 25 | 26 | 27 | DataAnalyzer = Callable[[DataStore, List[str]], Iterable[DataIssue]] 28 | -------------------------------------------------------------------------------- /renumics/spotlight/app_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Spotlight Application Config 3 | """ 4 | 5 | from dataclasses import dataclass 6 | from pathlib import Path 7 | from typing import Any, List, Optional, Union 8 | 9 | from renumics.spotlight.analysis.typing import DataIssue 10 | from renumics.spotlight.dtypes import DTypeMap 11 | from renumics.spotlight.layout.nodes import Layout 12 | 13 | 14 | @dataclass(frozen=True) 15 | class AppConfig: 16 | """ 17 | Spotlight Application Config 18 | """ 19 | 20 | # dataset 21 | dataset: Any = None 22 | dtypes: Optional[DTypeMap] = None 23 | project_root: Optional[Path] = None 24 | 25 | # data analysis 26 | analyze: Optional[Union[List[str], bool]] = None 27 | custom_issues: Optional[List[DataIssue]] = None 28 | 29 | # embedding 30 | embed: Optional[Union[List[str], bool]] = None 31 | 32 | # frontend 33 | layout: Optional[Layout] = None 34 | filebrowsing_allowed: Optional[bool] = None 35 | -------------------------------------------------------------------------------- /renumics/spotlight/appdirs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module managing different application directories (config, cache, temp,...) 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | import appdirs 8 | 9 | _APP_NAME = "spotlight" 10 | _APP_AUTHOR = "renumics" 11 | 12 | config_dir = Path(appdirs.user_config_dir(_APP_NAME, _APP_AUTHOR)) 13 | cache_dir = Path(appdirs.user_cache_dir(_APP_NAME, _APP_AUTHOR)) 14 | -------------------------------------------------------------------------------- /renumics/spotlight/backend/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides all backend code. 3 | Based on FastAPI 4 | """ 5 | -------------------------------------------------------------------------------- /renumics/spotlight/backend/apis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/backend/apis/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight/backend/apis/websocket.py: -------------------------------------------------------------------------------- 1 | """ 2 | Endpoints for websocket communication. 3 | """ 4 | 5 | from fastapi import APIRouter, WebSocket 6 | 7 | router = APIRouter() 8 | 9 | 10 | @router.websocket("/ws") 11 | async def websocket_endpoint(websocket: WebSocket) -> None: 12 | """ 13 | Handle all websocket connections and dispatch all incoming messages. 14 | """ 15 | connection = websocket.app.websocket_manager.create_connection(websocket) 16 | await connection.listen() 17 | -------------------------------------------------------------------------------- /renumics/spotlight/backend/middlewares/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/backend/middlewares/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight/backend/statics: -------------------------------------------------------------------------------- 1 | ../../../build/frontend -------------------------------------------------------------------------------- /renumics/spotlight/backend/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides long-running tasks. 3 | """ 4 | 5 | from .exceptions import TaskCancelled 6 | from .task_manager import TaskManager 7 | 8 | __all__ = ["TaskManager", "TaskCancelled"] 9 | -------------------------------------------------------------------------------- /renumics/spotlight/backend/tasks/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exceptions for task management 3 | """ 4 | 5 | 6 | class TaskCancelled(Exception): 7 | """ 8 | The Task has been cancelled 9 | """ 10 | -------------------------------------------------------------------------------- /renumics/spotlight/backend/tasks/task.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides a simple dataclass for storing task information 3 | """ 4 | 5 | import dataclasses 6 | from concurrent.futures import Future 7 | from typing import Optional, Union 8 | 9 | 10 | @dataclasses.dataclass 11 | class Task: 12 | """ 13 | An executing task. 14 | """ 15 | 16 | name: Optional[str] 17 | tag: Optional[Union[str, int]] 18 | future: Future 19 | -------------------------------------------------------------------------------- /renumics/spotlight/data_source/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Spotlight Datasources fetch and normalize data. 3 | 4 | Register a new datasource for a file extension or python class 5 | through add_datasource or the @datasource decorator. 6 | """ 7 | 8 | from .data_source import ColumnMetadata, DataSource 9 | from .decorator import datasource 10 | from .registry import add_datasource, create_datasource 11 | 12 | __all__ = [ 13 | "DataSource", 14 | "ColumnMetadata", 15 | "datasource", 16 | "create_datasource", 17 | "add_datasource", 18 | ] 19 | -------------------------------------------------------------------------------- /renumics/spotlight/data_source/decorator.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Type, Union 2 | 3 | from .data_source import DataSource 4 | from .registry import add_datasource 5 | 6 | 7 | def datasource( 8 | source: Union[str, Type] 9 | ) -> Callable[[Type[DataSource]], Type[DataSource]]: 10 | """ 11 | Decorator to add a data source. 12 | See `add_datasource` 13 | """ 14 | 15 | def func(klass: Type[DataSource]) -> Type[DataSource]: 16 | add_datasource(source, klass) 17 | return klass 18 | 19 | return func 20 | -------------------------------------------------------------------------------- /renumics/spotlight/data_source/exceptions.py: -------------------------------------------------------------------------------- 1 | from fastapi import status 2 | 3 | from renumics.spotlight.backend.exceptions import Problem 4 | 5 | 6 | class InvalidDataSource(Problem): 7 | """The data source can't be opened""" 8 | 9 | def __init__(self) -> None: 10 | super().__init__( 11 | "Invalid data source", 12 | "Can't open supplied data source.", 13 | status.HTTP_403_FORBIDDEN, 14 | ) 15 | -------------------------------------------------------------------------------- /renumics/spotlight/data_source/registry.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any, Dict, List, Type, Union 3 | 4 | from renumics.spotlight.typing import is_pathtype 5 | 6 | from .data_source import DataSource 7 | from .exceptions import InvalidDataSource 8 | 9 | data_sources: Dict[Union[str, Type], Type[DataSource]] = {} 10 | 11 | 12 | def add_datasource(source: Union[str, Type], klass: Type[DataSource]) -> None: 13 | """ 14 | Add a datasource for the given file extension or type 15 | """ 16 | data_sources[source] = klass 17 | 18 | 19 | def create_datasource(source: Any) -> DataSource: 20 | """ 21 | open the specified data source 22 | """ 23 | 24 | keys: List[Any] = [] 25 | if is_pathtype(source): 26 | path = Path(source) 27 | if path.exists(): 28 | keys.append(path.suffix) 29 | keys.append(Path) 30 | 31 | keys.append(type(source)) 32 | 33 | for key in keys: 34 | try: 35 | data_source = data_sources[key] 36 | except KeyError: 37 | continue 38 | try: 39 | return data_source(source) 40 | except InvalidDataSource: 41 | continue 42 | else: 43 | raise InvalidDataSource() 44 | -------------------------------------------------------------------------------- /renumics/spotlight/develop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/develop/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight/embeddings/embedders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/embeddings/embedders/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight/embeddings/embedders/vit.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, List, Optional 2 | 3 | import numpy as np 4 | import PIL.Image 5 | import transformers 6 | 7 | from renumics.spotlight.embeddings.decorator import embed 8 | from renumics.spotlight.logging import logger 9 | 10 | try: 11 | import torch 12 | except ImportError: 13 | logger.warning("ViT embedder requires `pytorch` to be installed.") 14 | else: 15 | 16 | @embed("image") 17 | def vit( 18 | batches: Iterable[List[PIL.Image.Image]], 19 | ) -> Iterable[List[Optional[np.ndarray]]]: 20 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 21 | model_name = "google/vit-base-patch16-224" 22 | processor = transformers.AutoImageProcessor.from_pretrained(model_name) 23 | model = transformers.ViTModel.from_pretrained(model_name).to(device) 24 | 25 | for batch in batches: 26 | images = [image.convert("RGB") for image in batch] 27 | inputs = processor(images=images, return_tensors="pt") 28 | with torch.no_grad(): 29 | outputs = model(**inputs.to(device)) 30 | embeddings = outputs.last_hidden_state[:, 0].cpu().numpy() 31 | 32 | yield list(embeddings) 33 | -------------------------------------------------------------------------------- /renumics/spotlight/embeddings/registry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage data analyzers available for spotlights automatic dataset analysis. 3 | """ 4 | 5 | from typing import Any, Dict, Tuple, Type 6 | 7 | from renumics.spotlight import dtypes 8 | 9 | from .typing import Embedder 10 | 11 | registered_embedders: Dict[ 12 | str, Tuple[Type[Embedder], dtypes.DType, tuple, Dict[str, Any]] 13 | ] = {} 14 | 15 | 16 | def register_embedder( 17 | embedder: Type[Embedder], dtype: dtypes.DType, name: str, *args: Any, **kwargs: Any 18 | ) -> None: 19 | """ 20 | Register an embedder 21 | """ 22 | registered_embedders[name] = (embedder, dtype, args, kwargs) 23 | 24 | 25 | def unregister_embedder(embedder: str) -> None: 26 | """ 27 | Unregister an embedder 28 | """ 29 | del registered_embedders[embedder] 30 | -------------------------------------------------------------------------------- /renumics/spotlight/environ.py: -------------------------------------------------------------------------------- 1 | """ 2 | Environment variables' manipulation. 3 | """ 4 | 5 | import os 6 | from contextlib import contextmanager 7 | from typing import Dict, Iterator, Optional 8 | 9 | 10 | @contextmanager 11 | def set_temp_environ(**kwargs: Optional[str]) -> Iterator[None]: 12 | """ 13 | Temporarily set passed environment variables. If value `None` is passed, 14 | temporarily delete respective variable. 15 | """ 16 | old_environ: Dict[str, Optional[str]] = { 17 | key: os.getenv(key, None) for key in kwargs 18 | } 19 | try: 20 | for key, value in kwargs.items(): 21 | if value is None: 22 | if key in os.environ: 23 | del os.environ[key] 24 | else: 25 | os.environ[key] = value 26 | yield 27 | finally: 28 | for key, old_value in old_environ.items(): 29 | if old_value is None: 30 | del os.environ[key] 31 | else: 32 | os.environ[key] = old_value 33 | -------------------------------------------------------------------------------- /renumics/spotlight/io/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reading and writing of different data formats. 3 | """ 4 | 5 | import ast 6 | from contextlib import suppress 7 | from typing import Any 8 | 9 | from .audio import ( 10 | get_format_codec, 11 | get_waveform, 12 | read_audio, 13 | transcode_audio, 14 | write_audio, 15 | ) 16 | from .gltf import ( 17 | GLTF_DTYPES, 18 | GLTF_DTYPES_CONVERSION, 19 | GLTF_DTYPES_LOOKUP, 20 | GLTF_SHAPES, 21 | GLTF_SHAPES_LOOKUP, 22 | check_gltf, 23 | decode_gltf_arrays, 24 | encode_gltf_array, 25 | ) 26 | from .huggingface import prepare_hugging_face_dict 27 | 28 | __all__ = [ 29 | "get_format_codec", 30 | "get_waveform", 31 | "read_audio", 32 | "write_audio", 33 | "transcode_audio", 34 | "GLTF_DTYPES", 35 | "GLTF_DTYPES_CONVERSION", 36 | "GLTF_DTYPES_LOOKUP", 37 | "GLTF_SHAPES", 38 | "GLTF_SHAPES_LOOKUP", 39 | "check_gltf", 40 | "decode_gltf_arrays", 41 | "encode_gltf_array", 42 | "prepare_hugging_face_dict", 43 | "try_literal_eval", 44 | ] 45 | 46 | 47 | def try_literal_eval(x: str) -> Any: 48 | """ 49 | Try to evaluate a literal expression, otherwise return value as is. 50 | """ 51 | with suppress(Exception): 52 | return ast.literal_eval(x) 53 | return x 54 | -------------------------------------------------------------------------------- /renumics/spotlight/io/huggingface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers for HuggingFace formats. 3 | """ 4 | 5 | from typing import Any, Dict 6 | 7 | 8 | def prepare_hugging_face_dict(x: Dict) -> Any: 9 | """ 10 | Prepare HuggingFace format for files to be used in Spotlight. 11 | """ 12 | if x.keys() != {"bytes", "path"}: 13 | return x 14 | blob = x["bytes"] 15 | if blob is not None: 16 | return blob 17 | return x["path"] 18 | -------------------------------------------------------------------------------- /renumics/spotlight/io/path.py: -------------------------------------------------------------------------------- 1 | """ 2 | path helper functions 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | from renumics.spotlight.typing import PathType 8 | 9 | 10 | def is_path_relative_to(path: PathType, parent: PathType) -> bool: 11 | """ 12 | Is the path a subpath of the parent 13 | """ 14 | try: 15 | Path(path).relative_to(parent) 16 | return True 17 | except ValueError: 18 | return False 19 | -------------------------------------------------------------------------------- /renumics/spotlight/layouts/__init__.py: -------------------------------------------------------------------------------- 1 | from .default import default 2 | from .model_compare import compare_classification 3 | from .model_debug import debug_classification 4 | 5 | __all__ = ["default", "debug_classification", "compare_classification"] 6 | -------------------------------------------------------------------------------- /renumics/spotlight/layouts/default.py: -------------------------------------------------------------------------------- 1 | from renumics.spotlight.layout import ( 2 | histogram, 3 | inspector, 4 | layout, 5 | scatterplot, 6 | similaritymap, 7 | split, 8 | tab, 9 | table, 10 | ) 11 | from renumics.spotlight.layout.nodes import Layout 12 | 13 | 14 | def default() -> Layout: 15 | """ 16 | Default layout for spotlight. 17 | """ 18 | 19 | return layout( 20 | split( 21 | tab(table(), weight=60), 22 | tab(similaritymap(), scatterplot(), histogram(), weight=40), 23 | weight=60, 24 | ), 25 | tab(inspector(), weight=40), 26 | ) 27 | -------------------------------------------------------------------------------- /renumics/spotlight/logging.py: -------------------------------------------------------------------------------- 1 | """ 2 | Logging facilities for Spotlight 3 | """ 4 | 5 | from loguru import logger 6 | 7 | 8 | def enable() -> None: 9 | """ 10 | Enable logging for all spotlight modules 11 | """ 12 | logger.enable("renumics.spotlight") 13 | logger.enable("renumics.spotlight_plugins") 14 | 15 | 16 | def disable() -> None: 17 | """ 18 | Disable logging for all spotlight modules 19 | """ 20 | logger.disable("renumics.spotlight") 21 | logger.disable("renumics.spotlight_plugins") 22 | -------------------------------------------------------------------------------- /renumics/spotlight/media/__init__.py: -------------------------------------------------------------------------------- 1 | from .audio import Audio 2 | from .base import Array1dLike, Array2dLike, FileMediaType, ImageLike, MediaType 3 | from .embedding import Embedding 4 | from .image import Image 5 | from .mesh import Mesh 6 | from .sequence_1d import Sequence1D 7 | from .video import Video 8 | 9 | __all__ = [ 10 | "Array1dLike", 11 | "Array2dLike", 12 | "ImageLike", 13 | "MediaType", 14 | "FileMediaType", 15 | "Embedding", 16 | "Sequence1D", 17 | "Audio", 18 | "Image", 19 | "Mesh", 20 | "Video", 21 | ] 22 | -------------------------------------------------------------------------------- /renumics/spotlight/media/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exceptions raised in dtype handling 3 | """ 4 | 5 | 6 | class DTypeException(Exception): 7 | """ 8 | Base data type exception. 9 | """ 10 | 11 | 12 | class NotADType(DTypeException): 13 | """ 14 | Not a Spotlight data type. 15 | """ 16 | 17 | 18 | class UnsupportedDType(DTypeException): 19 | """ 20 | This data type is not supported. 21 | """ 22 | 23 | 24 | class InvalidFile(Exception): 25 | """ 26 | File does not exist or is not readable for the respective data type. 27 | """ 28 | -------------------------------------------------------------------------------- /renumics/spotlight/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight/py.typed -------------------------------------------------------------------------------- /renumics/spotlight/requests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Requests-related helpers. 3 | """ 4 | 5 | from renumics.spotlight.__version__ import __version__ 6 | 7 | headers = { 8 | # https://meta.wikimedia.org/wiki/User-Agent_policy 9 | "User-Agent": f"SpotlightBot/{__version__} (https://spotlight.renumics.com/)" 10 | } 11 | -------------------------------------------------------------------------------- /renumics/spotlight/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | global settings (read from env) 3 | """ 4 | 5 | from typing import Optional 6 | 7 | from pydantic_settings import BaseSettings, SettingsConfigDict 8 | 9 | 10 | class Settings(BaseSettings): 11 | """ 12 | Spotlight settings module 13 | settings will be loaded from env variables or .env file 14 | """ 15 | 16 | dev: bool = False 17 | verbose: bool = False 18 | opt_out: bool = False 19 | opt_in: bool = False 20 | layout: Optional[str] = None 21 | 22 | model_config = SettingsConfigDict(env_prefix="spotlight_") 23 | 24 | 25 | settings = Settings() 26 | -------------------------------------------------------------------------------- /renumics/spotlight/webbrowser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Launch browser. 3 | """ 4 | 5 | import threading 6 | import time 7 | import webbrowser 8 | 9 | import requests 10 | from loguru import logger 11 | 12 | 13 | def wait_for(url: str) -> None: 14 | """Wait until the service at url is reachable.""" 15 | while True: 16 | try: 17 | requests.head(url, timeout=10, verify=False) 18 | break 19 | except requests.exceptions.ConnectionError: 20 | time.sleep(0.5) 21 | 22 | 23 | def launch_browser_in_thread(url: str) -> threading.Thread: 24 | """Open the app in a browser in background once it runs.""" 25 | thread = threading.Thread(target=launch_browser, args=(url,)) 26 | thread.start() 27 | return thread 28 | 29 | 30 | def launch_browser(url: str) -> None: 31 | """Open the app in a browser once it runs.""" 32 | wait_for(url) # wait also for socket? 33 | try: 34 | webbrowser.open(url) 35 | except Exception: 36 | logger.warning( 37 | f"Couldn't launch browser automatically, you can reach Spotlight at {url}." 38 | ) 39 | -------------------------------------------------------------------------------- /renumics/spotlight_plugins/core/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight_plugins/core/api/__init__.py -------------------------------------------------------------------------------- /renumics/spotlight_plugins/core/api/issues.py: -------------------------------------------------------------------------------- 1 | """ 2 | Problems API endpoints 3 | """ 4 | 5 | from typing import List 6 | 7 | from fastapi import APIRouter, Request 8 | from pydantic.dataclasses import dataclass 9 | 10 | from renumics.spotlight.analysis import DataIssue 11 | 12 | router = APIRouter(tags=["issues"]) 13 | 14 | 15 | @dataclass 16 | class AnalysisInfo: 17 | """ 18 | The current analysis status with all issues 19 | """ 20 | 21 | running: bool 22 | issues: List[DataIssue] 23 | 24 | 25 | @router.get("/", response_model=AnalysisInfo, operation_id="get_all") 26 | async def get_all(request: Request) -> AnalysisInfo: 27 | """ 28 | Get all data issues. 29 | """ 30 | 31 | return AnalysisInfo( 32 | running=request.app.issues is None, 33 | issues=request.app.custom_issues + (request.app.issues or []), 34 | ) 35 | -------------------------------------------------------------------------------- /renumics/spotlight_plugins/core/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Renumics/spotlight/2fafe54ba16060d0294784284ad12f91565bf9eb/renumics/spotlight_plugins/core/py.typed -------------------------------------------------------------------------------- /scripts/Test-SpotlightStart.ps1: -------------------------------------------------------------------------------- 1 | $Port = "5000" 2 | $URL = "http://127.0.0.1:${Port}" 3 | 4 | try { 5 | $Process = Start-Process spotlight -PassThru -NoNewWindow ` 6 | -ArgumentList "--host 127.0.0.1 --port ${Port} --no-browser data/tables/tallymarks-small.h5" 7 | 8 | foreach ($i in 0..20) { 9 | try { 10 | $Response = Invoke-WebRequest "${URL}/api/table/" 11 | $GenerationID = ($Response.Content | ConvertFrom-Json).generation_id 12 | } 13 | catch {} 14 | if ($GenerationID -ne $Null) { 15 | break 16 | } 17 | Start-Sleep 0.5 18 | } 19 | if ($GenerationID -eq $Null) { 20 | throw "No connection to Spotlight" 21 | } 22 | try { 23 | Invoke-WebRequest "${URL}/api/table/number/42?generation_id=${GenerationID}" 24 | } 25 | catch { 26 | throw "Connection with generation_id=${GenerationID} failed. Error message: ${_}" 27 | } 28 | } 29 | finally { 30 | if ($Process -ne $Null) { 31 | Stop-Process -InputObject $Process 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/check_dynamic_versioning.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Check if dynamic versioning is enabled 4 | Exit with code 1 otherwise 5 | """ 6 | 7 | import sys 8 | 9 | import toml 10 | 11 | if __name__ == "__main__": 12 | with open("pyproject.toml", encoding="utf-8") as pyproject_file: 13 | pyproject = toml.load(pyproject_file) 14 | 15 | errors = [] 16 | 17 | if pyproject["tool"]["poetry"]["version"] != "0.0.0": 18 | errors.append("Error: tool.poetry.version != 0.0.0") 19 | if not pyproject["tool"]["poetry-dynamic-versioning"]["enable"]: 20 | errors.append("Error: tool.poetry-dymamic-versioning.enable != true") 21 | 22 | for error in errors: 23 | print(error) 24 | if errors: 25 | sys.exit(1) 26 | -------------------------------------------------------------------------------- /scripts/generate_api_spec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | generate the api spec 4 | """ 5 | import json 6 | 7 | import click 8 | 9 | from renumics.spotlight.app import SpotlightApp 10 | 11 | 12 | @click.command() # type: ignore 13 | @click.option( 14 | "--output-path", 15 | "-o", 16 | type=click.Path(file_okay=True, dir_okay=False, writable=True), 17 | help="path to write spec to", 18 | required=True, 19 | ) 20 | def generate_api_spec(output_path: str) -> None: 21 | """ 22 | generate swagger api spec as json 23 | :param output_path: path to output json 24 | :return: 25 | """ 26 | app = SpotlightApp() 27 | app.openapi_version = "3.0.2" 28 | 29 | with open(output_path, "w", encoding="utf8") as out_f: 30 | json.dump(app.openapi(), out_f, indent=4) 31 | 32 | 33 | if __name__ == "__main__": 34 | generate_api_spec() 35 | -------------------------------------------------------------------------------- /setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/api/config.ts: -------------------------------------------------------------------------------- 1 | import application from '../application'; 2 | import { Configuration } from '../client/runtime'; 3 | 4 | export const config = new Configuration({ basePath: application.apiUrl }); 5 | -------------------------------------------------------------------------------- /src/api/errors.ts: -------------------------------------------------------------------------------- 1 | import { Problem } from '../types'; 2 | 3 | const DEFAULT_PROBLEM: Problem = { 4 | type: 'UndefinedApiError', 5 | title: 'Undefined API Error', 6 | }; 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | export async function parseError(error: any): Promise { 10 | if (!error.response?.json) { 11 | return DEFAULT_PROBLEM; 12 | } 13 | 14 | try { 15 | return (await error.response.json()) as Problem; 16 | } catch { 17 | return DEFAULT_PROBLEM; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import application from '../application'; 2 | import { FilebrowserApi, LayoutApi, PluginsApi, TableApi, IssuesApi } from '../client'; 3 | import { Configuration } from '../client/runtime'; 4 | import { parseError } from './errors'; 5 | 6 | const config = new Configuration({ basePath: application.apiUrl }); 7 | 8 | export default { 9 | table: new TableApi(config), 10 | filebrowser: new FilebrowserApi(config), 11 | layout: new LayoutApi(config), 12 | plugin: new PluginsApi(config), 13 | issues: new IssuesApi(config), 14 | parseError, 15 | }; 16 | -------------------------------------------------------------------------------- /src/application.ts: -------------------------------------------------------------------------------- 1 | // a collection of compile time constants 2 | // basically wrapping all our compile time settings 3 | // set through import.meta.env in a typesafe interface 4 | 5 | interface Edition { 6 | name: string; 7 | shorthand: string; 8 | } 9 | 10 | interface Application { 11 | edition: Edition; 12 | version: string; 13 | apiUrl?: string; 14 | publicUrl?: string; 15 | docsUrl: string; 16 | repositoryUrl: string; 17 | filebrowsingAllowed: boolean; 18 | } 19 | 20 | const application: Application = { 21 | edition: { 22 | name: 'Community', 23 | shorthand: 'CE', 24 | }, 25 | version: import.meta.env.VITE_VERSION ?? 'dev', 26 | publicUrl: import.meta.env.VITE_PUBLIC_URL ?? globalThis.location.origin, 27 | apiUrl: import.meta.env.VITE_API_BASE_PATH ?? globalThis.location.origin, 28 | docsUrl: 'https://spotlight.renumics.com', 29 | repositoryUrl: 'https://github.com/renumics/spotlight', 30 | filebrowsingAllowed: window.__filebrowsing_allowed__, 31 | } as const; 32 | 33 | export default application; 34 | -------------------------------------------------------------------------------- /src/client/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | -------------------------------------------------------------------------------- /src/client/.openapi-generator/FILES: -------------------------------------------------------------------------------- 1 | .openapi-generator-ignore 2 | apis/ConfigApi.ts 3 | apis/DefaultApi.ts 4 | apis/FilebrowserApi.ts 5 | apis/IssuesApi.ts 6 | apis/LayoutApi.ts 7 | apis/PluginsApi.ts 8 | apis/TableApi.ts 9 | apis/index.ts 10 | index.ts 11 | models/AnalysisInfo.ts 12 | models/Column.ts 13 | models/DataIssue.ts 14 | models/FileEntry.ts 15 | models/Folder.ts 16 | models/HTTPValidationError.ts 17 | models/LocationInner.ts 18 | models/Plugin.ts 19 | models/ResponseGetValue.ts 20 | models/SetConfigRequest.ts 21 | models/SetLayoutRequest.ts 22 | models/Table.ts 23 | models/ValidationError.ts 24 | models/Value.ts 25 | models/index.ts 26 | runtime.ts 27 | -------------------------------------------------------------------------------- /src/client/.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 7.0.1 2 | -------------------------------------------------------------------------------- /src/client/apis/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './ConfigApi'; 4 | export * from './DefaultApi'; 5 | export * from './FilebrowserApi'; 6 | export * from './IssuesApi'; 7 | export * from './LayoutApi'; 8 | export * from './PluginsApi'; 9 | export * from './TableApi'; 10 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './runtime'; 4 | export * from './apis/index'; 5 | export * from './models/index'; 6 | -------------------------------------------------------------------------------- /src/client/models/LocationInner.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * FastAPI 5 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 6 | * 7 | * The version of the OpenAPI document: 0.1.0 8 | * 9 | * 10 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 11 | * https://openapi-generator.tech 12 | * Do not edit the class manually. 13 | */ 14 | 15 | import { exists, mapValues } from '../runtime'; 16 | /** 17 | * 18 | * @export 19 | * @interface LocationInner 20 | */ 21 | export interface LocationInner {} 22 | 23 | /** 24 | * Check if a given object implements the LocationInner interface. 25 | */ 26 | export function instanceOfLocationInner(value: object): boolean { 27 | let isInstance = true; 28 | 29 | return isInstance; 30 | } 31 | 32 | export function LocationInnerFromJSON(json: any): LocationInner { 33 | return LocationInnerFromJSONTyped(json, false); 34 | } 35 | 36 | export function LocationInnerFromJSONTyped( 37 | json: any, 38 | ignoreDiscriminator: boolean 39 | ): LocationInner { 40 | return json; 41 | } 42 | 43 | export function LocationInnerToJSON(value?: LocationInner | null): any { 44 | return value; 45 | } 46 | -------------------------------------------------------------------------------- /src/client/models/Value.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | * FastAPI 5 | * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) 6 | * 7 | * The version of the OpenAPI document: 0.1.0 8 | * 9 | * 10 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 11 | * https://openapi-generator.tech 12 | * Do not edit the class manually. 13 | */ 14 | 15 | import { exists, mapValues } from '../runtime'; 16 | /** 17 | * 18 | * @export 19 | * @interface Value 20 | */ 21 | export interface Value {} 22 | 23 | /** 24 | * Check if a given object implements the Value interface. 25 | */ 26 | export function instanceOfValue(value: object): boolean { 27 | let isInstance = true; 28 | 29 | return isInstance; 30 | } 31 | 32 | export function ValueFromJSON(json: any): Value { 33 | return ValueFromJSONTyped(json, false); 34 | } 35 | 36 | export function ValueFromJSONTyped(json: any, ignoreDiscriminator: boolean): Value { 37 | return json; 38 | } 39 | 40 | export function ValueToJSON(value?: Value | null): any { 41 | return value; 42 | } 43 | -------------------------------------------------------------------------------- /src/client/models/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './AnalysisInfo'; 4 | export * from './Column'; 5 | export * from './DataIssue'; 6 | export * from './FileEntry'; 7 | export * from './Folder'; 8 | export * from './HTTPValidationError'; 9 | export * from './LocationInner'; 10 | export * from './Plugin'; 11 | export * from './ResponseGetValue'; 12 | export * from './SetConfigRequest'; 13 | export * from './SetLayoutRequest'; 14 | export * from './Table'; 15 | export * from './ValidationError'; 16 | export * from './Value'; 17 | -------------------------------------------------------------------------------- /src/components/ColumnSelector/ColumnList.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const ColumnList = tw.div`flex-grow bg-white flex flex-col min-h-[128px] max-h-[270px] overflow-y-auto border-b border-gray-300 border-t overflow-x-hidden`; 4 | export default ColumnList; 5 | -------------------------------------------------------------------------------- /src/components/ColumnSelector/ColumnListSeparator.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const ColumnListSeparator = tw.div`w-full h-px bg-gray-300`; 4 | export default ColumnListSeparator; 5 | -------------------------------------------------------------------------------- /src/components/ColumnSelector/index.ts: -------------------------------------------------------------------------------- 1 | import ColumnSelector from './ColumnSelector'; 2 | export default ColumnSelector; 3 | -------------------------------------------------------------------------------- /src/components/FileBrowser/elements.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | export const Container = tw.div` 4 | flex flex-col items-stretch 5 | [height:60vh] [width:60vw] 6 | overflow-hidden 7 | text-sm 8 | `; 9 | 10 | export const Header = tw.div` 11 | p-1 12 | border-b border-gray-400 13 | text-xs font-bold 14 | flex flex-row 15 | `; 16 | 17 | export const Content = tw.div` 18 | flex flex-col flex-grow 19 | overflow-hidden 20 | `; 21 | 22 | export const Footer = tw.div` 23 | flex flex-row-reverse 24 | p-1 25 | border-t border-gray-400 26 | `; 27 | -------------------------------------------------------------------------------- /src/components/FileBrowser/index.ts: -------------------------------------------------------------------------------- 1 | import FileBrowser from './FileBrowser'; 2 | export default FileBrowser; 3 | -------------------------------------------------------------------------------- /src/components/FilterBar/Button.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | import BaseButton from '../ui/Button'; 3 | 4 | const Button = tw(BaseButton)` 5 | px-1 py-0 6 | overflow-hidden 7 | bg-transparent 8 | `; 9 | 10 | export default Button; 11 | -------------------------------------------------------------------------------- /src/components/FilterBar/FilterList.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | import { useDataset } from '../../stores/dataset'; 4 | import FilterCreator from './FilterCreator'; 5 | import FilterItem from './FilterItem'; 6 | import FilterSelectedButton from './FilterSelectedButton'; 7 | import MergeFiltersButton from './MergeFiltersButton'; 8 | 9 | const Li = tw.li`border border-gray-400 rounded mx-0.5 bg-gray-100`; 10 | 11 | const FilterList: FunctionComponent = () => { 12 | const filters = useDataset((state) => state.filters); 13 | 14 | return ( 15 | 33 | ); 34 | }; 35 | 36 | export default FilterList; 37 | -------------------------------------------------------------------------------- /src/components/FilterBar/MergeFiltersButton.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import 'twin.macro'; 3 | import { shallow } from 'zustand/shallow'; 4 | import { Dataset, useDataset } from '../../stores/dataset'; 5 | import { SetFilter } from '../../types'; 6 | import UnionIcon from '../../icons/Union'; 7 | import Button from './Button'; 8 | 9 | const dataSelector = (d: Dataset) => ({ 10 | isIndexFiltered: d.isIndexFiltered, 11 | addFilter: d.addFilter, 12 | filterCount: d.filters.length, 13 | }); 14 | 15 | const MergeFiltersButton: FunctionComponent = () => { 16 | const { isIndexFiltered, addFilter, filterCount } = useDataset( 17 | dataSelector, 18 | shallow 19 | ); 20 | 21 | const handleClickCreateFilter = () => { 22 | // only create a filter if something has been selected 23 | addFilter(SetFilter.fromMask(isIndexFiltered)); 24 | }; 25 | 26 | return ( 27 | 34 | ); 35 | }; 36 | 37 | export default MergeFiltersButton; 38 | -------------------------------------------------------------------------------- /src/components/FilterBar/index.tsx: -------------------------------------------------------------------------------- 1 | import FilterBar from './FilterBar'; 2 | 3 | export default FilterBar; 4 | -------------------------------------------------------------------------------- /src/components/GltfViewer/index.ts: -------------------------------------------------------------------------------- 1 | import GltfViewer from './GltfViewer'; 2 | 3 | export default GltfViewer; 4 | export type { MeshAttribute } from './GltfScene'; 5 | export type { Handle, Props } from './GltfViewer'; 6 | export { morphStyles } from './morphing'; 7 | export type { MorphStyle } from './morphing'; 8 | -------------------------------------------------------------------------------- /src/components/GltfViewer/loading.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | 3 | export const postprocessMesh = (mesh: THREE.Mesh): void => { 4 | /* 5 | * Postprocesses a parsed gltf mesh to support the functionality of our viewer. 6 | * - Adds vertex normals if necessary. 7 | * - Sets sane default material parameters. 8 | */ 9 | 10 | const geometry = mesh.geometry as THREE.BufferGeometry; 11 | 12 | // compute vertex normals for smooth shading 13 | geometry.computeVertexNormals(); 14 | 15 | // set material properties for all attached materials 16 | const materials = 17 | mesh.material instanceof THREE.Material ? [mesh.material] : mesh.material; 18 | 19 | for (const material of materials as THREE.MeshStandardMaterial[]) { 20 | material.side = THREE.DoubleSide; 21 | material.flatShading = false; 22 | 23 | // adjust material color for legacy pid coloring 24 | material.color = new THREE.Color(); 25 | material.metalness = 0; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/GltfViewer/morphing.tsx: -------------------------------------------------------------------------------- 1 | export const morphStyles = ['loop', 'oscillate'] as const; 2 | export type MorphStyle = (typeof morphStyles)[number]; 3 | 4 | export function calculateMorphPosition(at: number, style: MorphStyle): number { 5 | /* 6 | * This function takes a time in seconds and outputs the current "position" of the morph between -1 and 1 7 | * depending on the given morph style 8 | * - loops between 0 and 1 resets the animation when it reaches the end 9 | * - oscillate moves back and forth between -1 and 1 10 | */ 11 | 12 | switch (style) { 13 | case 'oscillate': 14 | return Math.sin(2 * Math.PI * at); 15 | case 'loop': 16 | return at % 1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/LineChart/index.ts: -------------------------------------------------------------------------------- 1 | import LineChart from './LineChart'; 2 | 3 | export default LineChart; 4 | 5 | export type { Handle, Series } from './LineChart'; 6 | -------------------------------------------------------------------------------- /src/components/LoadingIndicator.tsx: -------------------------------------------------------------------------------- 1 | import useTimeout from '../hooks/useTimeout'; 2 | import { useCallback, useState } from 'react'; 3 | import tw from 'twin.macro'; 4 | import Spinner from './ui/Spinner'; 5 | 6 | const layoutStyle = tw`relative flex items-center justify-center h-full w-full`; 7 | 8 | interface Props { 9 | delay?: number; 10 | } 11 | 12 | const LoadingIndicator = ({ delay = 0 }: Props): JSX.Element => { 13 | const [visible, setVisible] = useState(!delay); 14 | const show = useCallback(() => setVisible(true), []); 15 | useTimeout(show, delay); 16 | 17 | return ( 18 |
19 | 20 |
21 | ); 22 | }; 23 | 24 | export default LoadingIndicator; 25 | -------------------------------------------------------------------------------- /src/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-7 h-7 inline-block`} 6 | `; 7 | 8 | const Logo: FunctionComponent = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default Logo; 34 | -------------------------------------------------------------------------------- /src/components/WebGLDetector/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { isWebGLAvailable } from 'three-stdlib'; 3 | import { useMessages } from '../../stores/messages'; 4 | 5 | const WebGLDetector = (): null => { 6 | useEffect(() => { 7 | if (!isWebGLAvailable()) { 8 | useMessages 9 | .getState() 10 | .addPersistentWarning( 11 | 'Missing WebGL support! Check if WebGL is enabled in your browser.' 12 | ); 13 | } 14 | }, []); 15 | 16 | return null; 17 | }; 18 | 19 | export default WebGLDetector; 20 | -------------------------------------------------------------------------------- /src/components/Workspace/AddWidgetButton.tsx: -------------------------------------------------------------------------------- 1 | import Button from '../ui/Button'; 2 | import Tag from '../ui/Tag'; 3 | import type { IconType } from 'react-icons'; 4 | import 'twin.macro'; 5 | 6 | interface Props { 7 | name: string; 8 | icon: IconType; 9 | experimental?: boolean; 10 | onClick: () => void; 11 | } 12 | 13 | const AddWidgetButton = ({ 14 | name, 15 | icon, 16 | experimental = false, 17 | onClick, 18 | }: Props): JSX.Element => { 19 | const Icon = icon; 20 | 21 | return ( 22 | 31 | ); 32 | }; 33 | 34 | export default AddWidgetButton; 35 | -------------------------------------------------------------------------------- /src/components/Workspace/icons.tsx: -------------------------------------------------------------------------------- 1 | import MaximizeIcon from '../../icons/Maximize'; 2 | import MinimizeIcon from '../../icons/Minimize'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledMaximizeIcon = styled(MaximizeIcon)` 6 | ${tw`h-4 w-4 block hover:text-blue-600 active:hover:text-blue-200 text-navy-600 transition-colors`} 7 | `; 8 | const StyledMinimizeIcon = styled(MinimizeIcon)` 9 | ${tw`h-4 w-4 block hover:text-blue-600 active:hover:text-blue-200 text-navy-600 transition-colors`} 10 | `; 11 | 12 | const icons = { 13 | maximize: , 14 | restore: , 15 | }; 16 | 17 | export default icons; 18 | -------------------------------------------------------------------------------- /src/components/Workspace/images/maximize.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/Workspace/images/minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/Workspace/index.ts: -------------------------------------------------------------------------------- 1 | import Workspace from './Workspace'; 2 | export type { Handle } from './Workspace'; 3 | 4 | export default Workspace; 5 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppBar } from './AppBar'; 2 | export { default as FilterBar } from './FilterBar'; 3 | -------------------------------------------------------------------------------- /src/components/shared/Plot/index.ts: -------------------------------------------------------------------------------- 1 | import Plot from './Plot'; 2 | import Points from './Points'; 3 | import Zoom from './Zoom'; 4 | 5 | export type { MergeStrategy } from './types'; 6 | export type { Handle as ZoomHandle } from './Zoom'; 7 | export { Points, Zoom }; 8 | 9 | export default Plot; 10 | -------------------------------------------------------------------------------- /src/components/shared/Plot/types.ts: -------------------------------------------------------------------------------- 1 | export interface Margin { 2 | left: number; 3 | right: number; 4 | top: number; 5 | bottom: number; 6 | } 7 | 8 | export type Point2d = [number, number]; 9 | 10 | export type MergeStrategy = 'replace' | 'union' | 'difference' | 'intersect'; 11 | -------------------------------------------------------------------------------- /src/components/ui/Center.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Center = tw.div`w-full h-full flex justify-center items-center`; 4 | 5 | export default Center; 6 | -------------------------------------------------------------------------------- /src/components/ui/Dot.tsx: -------------------------------------------------------------------------------- 1 | import tw, { styled } from 'twin.macro'; 2 | 3 | export default styled.div.attrs(({ color }: { color: string | undefined }) => ({ 4 | style: { 5 | backgroundColor: color, 6 | }, 7 | }))` 8 | ${tw`inline-block rounded-full p-1 ml-1 h-0 w-0`}; 9 | `; 10 | -------------------------------------------------------------------------------- /src/components/ui/Html.tsx: -------------------------------------------------------------------------------- 1 | import tw, { styled } from 'twin.macro'; 2 | 3 | const StyledHtml = styled.div` 4 | ${tw`text-sm content-center items-center h-full w-full p-1 prose`} 5 | `; 6 | 7 | interface Props { 8 | html: string; 9 | } 10 | 11 | const Html = ({ html }: Props) => { 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default Html; 20 | -------------------------------------------------------------------------------- /src/components/ui/Info.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Info = tw.div`flex w-full h-full justify-center items-center text-gray-700 italic text-center`; 4 | export default Info; 5 | -------------------------------------------------------------------------------- /src/components/ui/LabeledSlider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import tw from 'twin.macro'; 3 | import Slider from './Slider'; 4 | 5 | const Wrapper = tw.div`flex`; 6 | const LabeledSlider: React.FunctionComponent< 7 | React.ComponentProps & { className?: string } 8 | > = (props) => { 9 | const { className, ...sliderProps } = props; 10 | const { value, min, max } = sliderProps; 11 | return ( 12 | 13 |
14 | {value} 15 | 16 |
17 | {min} 18 | {max} 19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default LabeledSlider; 26 | -------------------------------------------------------------------------------- /src/components/ui/Markdown.tsx: -------------------------------------------------------------------------------- 1 | import showdown from 'showdown'; 2 | import { useMemo } from 'react'; 3 | 4 | import Html from './Html'; 5 | 6 | interface Props { 7 | content: string; 8 | } 9 | 10 | const Markdown = ({ content }: Props): JSX.Element => { 11 | const html = useMemo(() => { 12 | return new showdown.Converter().makeHtml(content); 13 | }, [content]); 14 | return ; 15 | }; 16 | 17 | export default Markdown; 18 | -------------------------------------------------------------------------------- /src/components/ui/Menu/HorizontalDivider.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | import Item from './Item'; 4 | 5 | const Divider = tw.hr`mb-1`; 6 | 7 | const HorizontalDivider: FunctionComponent = () => ( 8 | 9 | 10 | 11 | ); 12 | export default HorizontalDivider; 13 | -------------------------------------------------------------------------------- /src/components/ui/Menu/Input.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Input = tw.input`rounded p-1 m-1 bg-white focus:outline-none focus:ring-1 focus:border-blue-400 text-xs placeholder-gray-500 border border-gray-300`; 4 | export default Input; 5 | -------------------------------------------------------------------------------- /src/components/ui/Menu/Item.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Item = tw.div`text-gray-800 text-xs pb-0.5 px-0.5`; 4 | export default Item; 5 | -------------------------------------------------------------------------------- /src/components/ui/Menu/Subtitle.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Subtitle = tw.div`text-xs w-auto py-1 text-gray-700 cursor-default capitalize px-0.5`; 4 | export default Subtitle; 5 | -------------------------------------------------------------------------------- /src/components/ui/Menu/Switch.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, ReactNode, useCallback } from 'react'; 2 | import 'twin.macro'; 3 | 4 | export interface Props { 5 | value?: boolean; 6 | onChange?: (value: boolean) => void; 7 | children?: ReactNode; 8 | } 9 | 10 | const Switch = ({ value, onChange, children }: Props): JSX.Element => { 11 | const handleChange = useCallback( 12 | (e: ChangeEvent) => { 13 | onChange?.(e.target.checked); 14 | }, 15 | [onChange] 16 | ); 17 | 18 | return ( 19 | 23 | ); 24 | }; 25 | 26 | export default Switch; 27 | -------------------------------------------------------------------------------- /src/components/ui/Menu/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const TextArea = tw.textarea`rounded p-1 m-1 bg-white focus:outline-none focus:ring-1 focus:border-blue-400 text-xs placeholder-gray-500 border border-gray-300`; 4 | export default TextArea; 5 | -------------------------------------------------------------------------------- /src/components/ui/Menu/Title.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Title = tw.div`font-bold text-xs py-1 text-gray-700 uppercase cursor-default px-0.5`; 4 | export default Title; 5 | -------------------------------------------------------------------------------- /src/components/ui/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | import Menu from './Menu'; 2 | 3 | export default Menu; 4 | -------------------------------------------------------------------------------- /src/components/ui/NeedsUpgradeButton.tsx: -------------------------------------------------------------------------------- 1 | import Button, { Props } from './Button'; 2 | import { forwardRef, ForwardRefRenderFunction } from 'react'; 3 | import 'twin.macro'; 4 | 5 | const NeedsUpgradeButton: ForwardRefRenderFunction< 6 | HTMLButtonElement, 7 | Omit 8 | > = ({ tooltip, children, ...buttonProps }, ref) => { 9 | const full_tooltip = ( 10 |
11 |
{tooltip ?? 'Only in Spotlight'}
12 |
13 | PRO 14 |
15 |
16 | ); 17 | 18 | return ( 19 | 28 | ); 29 | }; 30 | 31 | export default forwardRef(NeedsUpgradeButton); 32 | -------------------------------------------------------------------------------- /src/components/ui/Pill.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Pill = tw.span`bg-gray-400 text-xs align-middle font-normal rounded-full border-0 px-1 text-midnight-100`; 4 | 5 | export default Pill; 6 | -------------------------------------------------------------------------------- /src/components/ui/Select/index.tsx: -------------------------------------------------------------------------------- 1 | import Select from './Select'; 2 | 3 | export default Select; 4 | -------------------------------------------------------------------------------- /src/components/ui/Select/types.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-types 2 | export type Value = string | number | boolean | object | null; 3 | export type OptionType = { value: T }; 4 | export type SelectVariant = 'normal' | 'compact' | 'inline' | 'inset'; 5 | -------------------------------------------------------------------------------- /src/components/ui/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const Svg = tw.svg`text-blue-400 w-full h-full animate-spin rounded-full`; 4 | const Circle = tw.circle`opacity-50 stroke-current`; 5 | const Path = tw.path`text-blue-600 stroke-current`; 6 | 7 | interface Props { 8 | className?: string; 9 | } 10 | 11 | const Spinner = ({ className = '' }: Props): JSX.Element => { 12 | return ( 13 | 19 | 20 | 26 | 27 | ); 28 | }; 29 | 30 | export default Spinner; 31 | -------------------------------------------------------------------------------- /src/components/ui/Tag.tsx: -------------------------------------------------------------------------------- 1 | import chroma, { Color } from 'chroma-js'; 2 | import { theme } from 'twin.macro'; 3 | 4 | interface Props { 5 | tag: string; 6 | color?: Color; 7 | className?: string; 8 | } 9 | 10 | const Tag = ({ 11 | tag, 12 | color = chroma(theme`colors.gray.200`), 13 | className, 14 | }: Props): JSX.Element => { 15 | const white = chroma(theme`colors.white`); 16 | const black = chroma(theme`colors.black`); 17 | 18 | const textColor = 19 | chroma.contrast(color, white) > chroma.contrast(color, black) ? white : black; 20 | 21 | return ( 22 |
27 | {tag} 28 |
29 | ); 30 | }; 31 | 32 | export default Tag; 33 | -------------------------------------------------------------------------------- /src/components/ui/ToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, useState } from 'react'; 2 | import Button, { Props as ButtonProps } from './Button'; 3 | 4 | type Props = { 5 | checked?: boolean; 6 | onChange?: ({ checked }: { checked: boolean }) => void; 7 | tooltip?: string; 8 | } & ButtonProps; 9 | 10 | const ToggleButton: FunctionComponent = ({ 11 | checked, 12 | onChange, 13 | tooltip, 14 | children, 15 | ...buttonProps 16 | }) => { 17 | const [state, setState] = useState(checked ?? false); 18 | 19 | const handleClick = () => { 20 | const newState = !(checked ?? state); 21 | onChange?.({ checked: newState }); 22 | setState(newState); 23 | }; 24 | 25 | return ( 26 | 34 | ); 35 | }; 36 | 37 | export default ToggleButton; 38 | -------------------------------------------------------------------------------- /src/components/ui/WidgetContainer.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | export default tw.div`flex flex-col w-full h-full bg-gray-100`; 4 | -------------------------------------------------------------------------------- /src/components/ui/WidgetContent.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | export default tw.div`flex-grow`; 4 | -------------------------------------------------------------------------------- /src/components/ui/WidgetMenu.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | export default tw.div`w-full h-5 border-b border-gray-400 text-sm flex flex-row content-center items-center overflow-hidden`; 4 | -------------------------------------------------------------------------------- /src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | 3 | export default { 4 | Button, 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/walkthrough/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MainWalkthrough } from './MainWalkthrough'; 2 | -------------------------------------------------------------------------------- /src/globals.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styled from 'styled-components'; 4 | import * as lib from './lib'; 5 | 6 | // globals to be used by plugins 7 | // Note: This can probably also be automatically generated during buildtime 8 | // We should probably figure that out some time and remove this file. 9 | 10 | // React 11 | globalThis.React = React; 12 | globalThis.ReactDOM = ReactDOM; 13 | 14 | // Styled Components 15 | // eslint-disable-next-line 16 | (globalThis as any).styled = styled; 17 | 18 | // spotlight library for plugins 19 | // eslint-disable-next-line 20 | (globalThis as any).spotlight = lib; 21 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useMemoWithPrevious } from './useMemoWithPrevious'; 2 | export { default as useWidgetConfig } from '../widgets/useWidgetConfig'; 3 | export { default as usePrevious } from './usePrevious'; 4 | export { default as useOnClickOutside } from './useOnClickOutside'; 5 | export { default as useColorTransferFunction } from './useColorTransferFunction'; 6 | -------------------------------------------------------------------------------- /src/hooks/useKeyPressed.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | 3 | const useKeyPressed = (key: string): boolean => { 4 | const [isKeyPressed, setIsKeyPressed] = useState(false); 5 | 6 | const onKeyEvent = useCallback( 7 | (e: KeyboardEvent) => { 8 | if (e.key === key) setIsKeyPressed(e.type === 'keydown'); 9 | }, 10 | [key] 11 | ); 12 | 13 | useEffect(() => { 14 | document.addEventListener('keydown', onKeyEvent); 15 | document.addEventListener('keyup', onKeyEvent); 16 | 17 | return () => { 18 | document.removeEventListener('keydown', onKeyEvent); 19 | document.removeEventListener('keyup', onKeyEvent); 20 | }; 21 | }, [onKeyEvent]); 22 | 23 | return isKeyPressed; 24 | }; 25 | 26 | export default useKeyPressed; 27 | -------------------------------------------------------------------------------- /src/hooks/useMemoCompare.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { useRef } from 'react'; 3 | 4 | function useMemoCompare(value: T, isEqual: (a: T, b: T) => boolean = _.isEqual): T { 5 | const prevRef = useRef(value); 6 | if (!isEqual(prevRef.current, value)) prevRef.current = value; 7 | return prevRef.current; 8 | } 9 | 10 | export default useMemoCompare; 11 | -------------------------------------------------------------------------------- /src/hooks/useMemoWithPrevious.ts: -------------------------------------------------------------------------------- 1 | import { useMemo, useRef } from 'react'; 2 | 3 | function useMemoWithPrevious( 4 | callback: (previous: T) => T, 5 | dependencies: unknown[], 6 | defaultValue: T 7 | ): T; 8 | function useMemoWithPrevious( 9 | callback: (previous?: T) => T, 10 | dependencies: unknown[], 11 | defaultValue?: T 12 | ): T { 13 | const valueRef = useRef(defaultValue); 14 | const value = useMemo( 15 | () => callback(valueRef.current), 16 | [...dependencies] // eslint-disable-line react-hooks/exhaustive-deps 17 | ); 18 | 19 | valueRef.current = value; 20 | 21 | return value; 22 | } 23 | 24 | export default useMemoWithPrevious; 25 | -------------------------------------------------------------------------------- /src/hooks/useOnClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useEffect } from 'react'; 2 | 3 | type ElementRef = MutableRefObject; 4 | type Callback = (event: Event) => void; 5 | 6 | function useOnClickOutside(ref: ElementRef, callback?: Callback): void { 7 | useEffect(() => { 8 | if (callback === undefined) return; 9 | 10 | const handleMousedown = (event: Event) => { 11 | const selectMenuRoot = document.getElementById('selectMenuRoot'); 12 | const insideMenuRoot = selectMenuRoot?.contains( 13 | event.target as HTMLElement 14 | ); 15 | 16 | const insideContainer = ref.current?.contains(event.target as HTMLElement); 17 | 18 | if (insideContainer || insideMenuRoot) return; 19 | callback(event); 20 | }; 21 | 22 | document.addEventListener('mousedown', handleMousedown, true); 23 | 24 | return () => { 25 | document.removeEventListener('mousedown', handleMousedown, true); 26 | }; 27 | }, [ref, callback]); 28 | } 29 | 30 | export default useOnClickOutside; 31 | -------------------------------------------------------------------------------- /src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | function usePrevious(value: T): T | undefined { 4 | const valueRef = useRef(); 5 | 6 | const previousValue = valueRef.current; 7 | 8 | useEffect(() => { 9 | valueRef.current = value; 10 | }, [value]); 11 | 12 | return previousValue; 13 | } 14 | 15 | export default usePrevious; 16 | -------------------------------------------------------------------------------- /src/hooks/useSize.ts: -------------------------------------------------------------------------------- 1 | import useResizeObserver from '@react-hook/resize-observer'; 2 | import { RefObject, useLayoutEffect, useState } from 'react'; 3 | 4 | interface Size { 5 | width: number; 6 | height: number; 7 | } 8 | 9 | const useSize = (ref: RefObject): Size => { 10 | const [size, setSize] = useState({ width: 0, height: 0 }); 11 | 12 | useLayoutEffect(() => { 13 | setSize({ 14 | width: ref.current?.offsetWidth ?? 0, 15 | height: ref.current?.offsetHeight ?? 0, 16 | }); 17 | }, [ref]); 18 | 19 | useResizeObserver(ref, (element) => 20 | setSize({ 21 | width: element.contentRect.width, 22 | height: element.contentRect.height, 23 | }) 24 | ); 25 | return size; 26 | }; 27 | 28 | export default useSize; 29 | -------------------------------------------------------------------------------- /src/hooks/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | function useTimeout(callback: () => void, delay = 0): void { 4 | const latestCallback = useRef(callback); 5 | 6 | useEffect(() => (latestCallback.current = callback), [callback]); 7 | 8 | useEffect(() => { 9 | if (!delay) return; 10 | const timeout = setTimeout(() => latestCallback.current(), delay); 11 | return () => clearTimeout(timeout); 12 | }, [delay]); 13 | } 14 | 15 | export default useTimeout; 16 | -------------------------------------------------------------------------------- /src/hooks/useWhyDidYouUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | function useWhyDidYouUpdate(name: string, props: Record): void { 4 | const previousProps = useRef>({}); 5 | 6 | useEffect(() => { 7 | if (previousProps.current) { 8 | const allKeys = Object.keys({ ...previousProps.current, ...props }); 9 | const changesObj: Record = {}; 10 | allKeys.forEach((key) => { 11 | if (previousProps.current[key] !== props[key]) { 12 | changesObj[key] = { 13 | from: previousProps.current[key], 14 | to: props[key], 15 | }; 16 | } 17 | }); 18 | 19 | if (Object.keys(changesObj).length) { 20 | // eslint-disable-next-line no-console 21 | console.log('[why-did-you-update]', name, changesObj); 22 | } 23 | } 24 | 25 | previousProps.current = props; 26 | }); 27 | } 28 | 29 | export default useWhyDidYouUpdate; 30 | -------------------------------------------------------------------------------- /src/icons/Add.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlinePlus } from 'react-icons/hi'; 3 | import tw from 'twin.macro'; 4 | 5 | const Add: IconType = tw( 6 | HiOutlinePlus 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Add; 9 | -------------------------------------------------------------------------------- /src/icons/AddBox.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiAddBoxLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const AddBox: IconType = tw( 6 | RiAddBoxLine 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default AddBox; 9 | -------------------------------------------------------------------------------- /src/icons/AddCell.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlineViewGridAdd } from 'react-icons/hi'; 3 | import tw from 'twin.macro'; 4 | 5 | const AddCell: IconType = tw( 6 | HiOutlineViewGridAdd 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default AddCell; 9 | -------------------------------------------------------------------------------- /src/icons/Annotation.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const AnnotationIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 19 | 20 | ); 21 | }; 22 | 23 | export default AnnotationIcon; 24 | -------------------------------------------------------------------------------- /src/icons/Array.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSymbolArray } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const ArrayIcon: IconType = tw( 6 | VscSymbolArray 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default ArrayIcon; 9 | -------------------------------------------------------------------------------- /src/icons/Audio.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { AiOutlineSound } from 'react-icons/ai'; 3 | import tw from 'twin.macro'; 4 | 5 | const Audio: IconType = tw( 6 | AiOutlineSound 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current stroke-1`; 8 | export default Audio; 9 | -------------------------------------------------------------------------------- /src/icons/Autoplay.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdOutlineHdrAuto } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const Autoplay: IconType = tw( 6 | MdOutlineHdrAuto 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Autoplay; 9 | -------------------------------------------------------------------------------- /src/icons/Ban.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const BanIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 19 | 20 | ); 21 | }; 22 | 23 | export default BanIcon; 24 | -------------------------------------------------------------------------------- /src/icons/BoundingBox.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { PiBoundingBox } from 'react-icons/pi'; 3 | import tw from 'twin.macro'; 4 | 5 | const BoundingBox: IconType = tw( 6 | PiBoundingBox 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default BoundingBox; 9 | -------------------------------------------------------------------------------- /src/icons/Brush.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { BiBrush as Brush } from 'react-icons/bi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledBrush: IconType = styled(Brush)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current fill-current`} 7 | & * { 8 | ${tw`stroke-0`} 9 | } 10 | `; 11 | 12 | export default StyledBrush; 13 | -------------------------------------------------------------------------------- /src/icons/Bubbles.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdBubbleChart } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const Bubbles: IconType = tw( 6 | MdBubbleChart 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Bubbles; 9 | -------------------------------------------------------------------------------- /src/icons/Calendar.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscCalendar } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Calendar: IconType = tw( 6 | VscCalendar 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Calendar; 9 | -------------------------------------------------------------------------------- /src/icons/Categorical.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscTag } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Categorical: IconType = tw(VscTag)`w-4 h-4 inline-block align-middle`; 6 | export default Categorical; 7 | -------------------------------------------------------------------------------- /src/icons/Check.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block`} 6 | `; 7 | 8 | const CheckIcon: FunctionComponent = () => { 9 | return ( 10 | 16 | 22 | 23 | ); 24 | }; 25 | 26 | export default CheckIcon; 27 | -------------------------------------------------------------------------------- /src/icons/Checked.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { ImCheckboxChecked } from 'react-icons/im'; 3 | import tw from 'twin.macro'; 4 | 5 | const CheckedIcon: IconType = tw( 6 | ImCheckboxChecked 7 | )`w-4 h-4 max-w-full max-h-full font-semibold inline-block`; 8 | 9 | export default CheckedIcon; 10 | -------------------------------------------------------------------------------- /src/icons/ClipboardList.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlineClipboardList } from 'react-icons/hi'; 3 | import tw from 'twin.macro'; 4 | 5 | const ClipboardList: IconType = tw( 6 | HiOutlineClipboardList 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default ClipboardList; 9 | -------------------------------------------------------------------------------- /src/icons/ColorPalette.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSymbolColor } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const ColorPalette: IconType = tw(VscSymbolColor)` 6 | w-4 h-4 font-semibold inline-block align-middle stroke-current 7 | `; 8 | 9 | export default ColorPalette; 10 | -------------------------------------------------------------------------------- /src/icons/Copy.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiFileCopyLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Docs: IconType = tw( 6 | RiFileCopyLine 7 | )`w-4 h-4 font-bold inline-block align-middle stroke-current`; 8 | 9 | export default Docs; 10 | -------------------------------------------------------------------------------- /src/icons/Cube.tsx: -------------------------------------------------------------------------------- 1 | import { HiOutlineCube as Cube } from 'react-icons/hi'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledCube = styled(Cube)` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | `; 7 | 8 | export default StyledCube; 9 | -------------------------------------------------------------------------------- /src/icons/Delete.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdDeleteOutline as Delete } from 'react-icons/md'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledDelete: IconType = styled(Delete)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | `; 8 | 9 | export default StyledDelete; 10 | -------------------------------------------------------------------------------- /src/icons/Docs.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiBookReadLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Docs: IconType = tw( 6 | RiBookReadLine 7 | )`w-4 h-4 font-bold inline-block align-middle stroke-current`; 8 | 9 | export default Docs; 10 | -------------------------------------------------------------------------------- /src/icons/Down.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const DownIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default DownIcon; 20 | -------------------------------------------------------------------------------- /src/icons/Edit.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdOutlineEdit } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const Edit: IconType = tw( 6 | MdOutlineEdit 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Edit; 9 | -------------------------------------------------------------------------------- /src/icons/EditOff.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdOutlineEditOff } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const EditOff: IconType = tw( 6 | MdOutlineEditOff 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default EditOff; 9 | -------------------------------------------------------------------------------- /src/icons/Embedding.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSymbolVariable } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Embedding: IconType = tw( 6 | VscSymbolVariable 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Embedding; 9 | -------------------------------------------------------------------------------- /src/icons/File.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscFile } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const File: IconType = tw( 6 | VscFile 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default File; 9 | -------------------------------------------------------------------------------- /src/icons/Filter.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiFilterLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Filter: IconType = tw( 6 | RiFilterLine 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Filter; 9 | -------------------------------------------------------------------------------- /src/icons/FilterOff.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiFilterOffLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const FilterOff: IconType = tw( 6 | RiFilterOffLine 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default FilterOff; 9 | -------------------------------------------------------------------------------- /src/icons/Folder.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscFolder } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Folder: IconType = tw( 6 | VscFolder 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Folder; 9 | -------------------------------------------------------------------------------- /src/icons/Gauge.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { LuGauge } from 'react-icons/lu'; 3 | import tw from 'twin.macro'; 4 | 5 | const Gauge: IconType = tw( 6 | LuGauge 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Gauge; 9 | -------------------------------------------------------------------------------- /src/icons/Github.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscGithubInverted } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Github: IconType = tw( 6 | VscGithubInverted 7 | )`w-4 h-4 font-bold inline-block align-middle stroke-current`; 8 | 9 | export default Github; 10 | -------------------------------------------------------------------------------- /src/icons/Grid.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiLayoutGridLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Grid: IconType = tw( 6 | RiLayoutGridLine 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Grid; 9 | -------------------------------------------------------------------------------- /src/icons/Help.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiQuestionLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Help: IconType = tw( 6 | RiQuestionLine 7 | )`w-4 h-4 font-bold inline-block align-middle stroke-current`; 8 | 9 | export default Help; 10 | -------------------------------------------------------------------------------- /src/icons/Hide.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const HideIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 19 | 20 | ); 21 | }; 22 | 23 | export default HideIcon; 24 | -------------------------------------------------------------------------------- /src/icons/Histogram.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { GiHistogram } from 'react-icons/gi'; 3 | import tw from 'twin.macro'; 4 | 5 | const Histogram: IconType = tw( 6 | GiHistogram 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Histogram; 9 | -------------------------------------------------------------------------------- /src/icons/Image.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { BiImageAlt } from 'react-icons/bi'; 3 | import tw from 'twin.macro'; 4 | 5 | const Image: IconType = tw( 6 | BiImageAlt 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Image; 9 | -------------------------------------------------------------------------------- /src/icons/Layout.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { TbLayoutBoardSplit } from 'react-icons/tb'; 3 | import tw from 'twin.macro'; 4 | 5 | const Layout: IconType = tw( 6 | TbLayoutBoardSplit 7 | )`w-4 h-4 inline-block align-middle stroke-current`; 8 | export default Layout; 9 | -------------------------------------------------------------------------------- /src/icons/Lightbulb.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiLightBulb as Lightbulb } from 'react-icons/hi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledLightbulb: IconType = styled(Lightbulb)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | `; 8 | 9 | export default StyledLightbulb; 10 | -------------------------------------------------------------------------------- /src/icons/Location.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlineLocationMarker as Location } from 'react-icons/hi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledLocation: IconType = styled(Location)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | & * { 8 | ${tw`stroke-2`} 9 | } 10 | `; 11 | 12 | export default StyledLocation; 13 | -------------------------------------------------------------------------------- /src/icons/LockClosed.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | 4 | const Svg = tw.svg`w-4 h-4 font-semibold inline-block`; 5 | 6 | const LockClosedIcon: FunctionComponent = () => { 7 | return ( 8 | 14 | 20 | 21 | ); 22 | }; 23 | 24 | export default LockClosedIcon; 25 | -------------------------------------------------------------------------------- /src/icons/LockOpen.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | 4 | const Svg = tw.svg`w-4 h-4 font-semibold inline-block`; 5 | 6 | const LockOpenIcon: FunctionComponent = () => { 7 | return ( 8 | 14 | 20 | 21 | ); 22 | }; 23 | 24 | export default LockOpenIcon; 25 | -------------------------------------------------------------------------------- /src/icons/Maximize.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { FiMaximize as Maximize } from 'react-icons/fi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const MaximizeIcon: IconType = styled(Maximize)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | & * { 8 | ${tw`stroke-2`} 9 | } 10 | `; 11 | export default MaximizeIcon; 12 | -------------------------------------------------------------------------------- /src/icons/Mesh.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { BiShapePolygon } from 'react-icons/bi'; 3 | import tw from 'twin.macro'; 4 | 5 | const Mesh: IconType = tw( 6 | BiShapePolygon 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Mesh; 9 | -------------------------------------------------------------------------------- /src/icons/Minimize.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { FiMinimize } from 'react-icons/fi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const Minimize: IconType = styled(FiMinimize)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | & * { 8 | ${tw`stroke-2`} 9 | } 10 | `; 11 | export default Minimize; 12 | -------------------------------------------------------------------------------- /src/icons/Number.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { GoNumber } from 'react-icons/go'; 3 | import tw from 'twin.macro'; 4 | 5 | const Number: IconType = tw( 6 | GoNumber 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Number; 9 | -------------------------------------------------------------------------------- /src/icons/OpenFolder.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscFolderOpened } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const OpenFolder: IconType = tw( 6 | VscFolderOpened 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default OpenFolder; 9 | -------------------------------------------------------------------------------- /src/icons/Question.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscQuestion } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Mesh: IconType = tw( 6 | VscQuestion 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Mesh; 9 | -------------------------------------------------------------------------------- /src/icons/Refresh.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | 4 | const Svg = tw.svg`w-4 h-4 font-semibold inline-block`; 5 | 6 | const RefreshIcon: FunctionComponent = () => { 7 | return ( 8 | 14 | 20 | 21 | ); 22 | }; 23 | 24 | export default RefreshIcon; 25 | -------------------------------------------------------------------------------- /src/icons/Repeat.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdRepeat } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const Repeat: IconType = tw( 6 | MdRepeat 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Repeat; 9 | -------------------------------------------------------------------------------- /src/icons/Reset.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const ResetIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 19 | 20 | ); 21 | }; 22 | 23 | export default ResetIcon; 24 | -------------------------------------------------------------------------------- /src/icons/ResetLayout.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdResetTv } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const ResetLayout: IconType = tw( 6 | MdResetTv 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default ResetLayout; 9 | -------------------------------------------------------------------------------- /src/icons/Resize.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlineSelector as Resize } from 'react-icons/hi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledResize: IconType = styled(Resize)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | & * { 8 | ${tw`stroke-2`} 9 | } 10 | `; 11 | 12 | export default StyledResize; 13 | -------------------------------------------------------------------------------- /src/icons/Right.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const RightIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default RightIcon; 20 | -------------------------------------------------------------------------------- /src/icons/Save.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSave } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Layout: IconType = tw(VscSave)`w-4 h-4 inline-block align-middle stroke-current`; 6 | export default Layout; 7 | -------------------------------------------------------------------------------- /src/icons/ScatterPlot.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { AiOutlineDotChart } from 'react-icons/ai'; 3 | import tw from 'twin.macro'; 4 | 5 | const ScatterPlot: IconType = tw( 6 | AiOutlineDotChart 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default ScatterPlot; 9 | -------------------------------------------------------------------------------- /src/icons/Selection.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | 4 | const StyledSvg = tw.svg`w-4 h-4 stroke-current stroke-2`; 5 | 6 | const DownIcon: FunctionComponent = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default DownIcon; 18 | -------------------------------------------------------------------------------- /src/icons/Sequence.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscLayers } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Sequence: IconType = tw( 6 | VscLayers 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Sequence; 9 | -------------------------------------------------------------------------------- /src/icons/Sequence1D.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { MdTimeline } from 'react-icons/md'; 3 | import tw from 'twin.macro'; 4 | 5 | const Sequence1D: IconType = tw( 6 | MdTimeline 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Sequence1D; 9 | -------------------------------------------------------------------------------- /src/icons/Show.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const ShowIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 19 | 24 | 25 | ); 26 | }; 27 | 28 | export default ShowIcon; 29 | -------------------------------------------------------------------------------- /src/icons/Soundwave.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { BsSoundwave } from 'react-icons/bs'; 3 | import tw from 'twin.macro'; 4 | 5 | const Soundwave: IconType = tw( 6 | BsSoundwave 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Soundwave; 9 | -------------------------------------------------------------------------------- /src/icons/Table.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { HiOutlineTable as Table } from 'react-icons/hi'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const StyledTable: IconType = styled(Table)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | & * { 8 | ${tw`stroke-2`} 9 | } 10 | `; 11 | 12 | export default StyledTable; 13 | -------------------------------------------------------------------------------- /src/icons/Text.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSymbolKey } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Text: IconType = tw( 6 | VscSymbolKey 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Text; 9 | -------------------------------------------------------------------------------- /src/icons/Tour.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { BiDirections } from 'react-icons/bi'; 3 | import tw from 'twin.macro'; 4 | 5 | const Tour: IconType = tw( 6 | BiDirections 7 | )`w-4 h-4 font-bold inline-block align-middle stroke-current`; 8 | 9 | export default Tour; 10 | -------------------------------------------------------------------------------- /src/icons/TriangleDown.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscTriangleDown } from 'react-icons/vsc'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const TriangleDown: IconType = styled(VscTriangleDown)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | `; 8 | export default TriangleDown; 9 | -------------------------------------------------------------------------------- /src/icons/TriangleRight.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscTriangleRight } from 'react-icons/vsc'; 3 | import tw, { styled } from 'twin.macro'; 4 | 5 | const TriangleRight: IconType = styled(VscTriangleRight)` 6 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 7 | `; 8 | export default TriangleRight; 9 | -------------------------------------------------------------------------------- /src/icons/Unchecked.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { ImCheckboxUnchecked } from 'react-icons/im'; 3 | import tw from 'twin.macro'; 4 | 5 | const UncheckedIcon: IconType = tw( 6 | ImCheckboxUnchecked 7 | )`w-4 h-4 max-w-full max-h-full font-semibold inline-block`; 8 | 9 | export default UncheckedIcon; 10 | -------------------------------------------------------------------------------- /src/icons/Union.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw from 'twin.macro'; 3 | 4 | const StyledSvg = tw.svg`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 5 | 6 | const Union: FunctionComponent = () => { 7 | return ( 8 | 9 | 10 | 16 | 20 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default Union; 30 | -------------------------------------------------------------------------------- /src/icons/Up.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block align-middle stroke-current`} 6 | & * { 7 | ${tw`stroke-2`} 8 | } 9 | `; 10 | 11 | const UpIcon: FunctionComponent = () => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default UpIcon; 20 | -------------------------------------------------------------------------------- /src/icons/Video.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { RiMovieLine } from 'react-icons/ri'; 3 | import tw from 'twin.macro'; 4 | 5 | const Movie: IconType = tw( 6 | RiMovieLine 7 | )`w-4 h-4 inline-block align-middle stroke-current`; 8 | export default Movie; 9 | -------------------------------------------------------------------------------- /src/icons/Warning.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { IoWarning } from 'react-icons/io5'; 3 | import tw from 'twin.macro'; 4 | 5 | const Warning: IconType = tw( 6 | IoWarning 7 | )`w-4 h-4 inline-block align-middle stroke-current`; 8 | export default Warning; 9 | -------------------------------------------------------------------------------- /src/icons/Window.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { VscSymbolArray } from 'react-icons/vsc'; 3 | import tw from 'twin.macro'; 4 | 5 | const Window: IconType = tw( 6 | VscSymbolArray 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default Window; 9 | -------------------------------------------------------------------------------- /src/icons/WordCloud.tsx: -------------------------------------------------------------------------------- 1 | import type { IconType } from 'react-icons'; 2 | import { AiOutlineCloud } from 'react-icons/ai'; 3 | import tw from 'twin.macro'; 4 | 5 | const WordCloud: IconType = tw( 6 | AiOutlineCloud 7 | )`w-4 h-4 font-semibold inline-block align-middle stroke-current`; 8 | export default WordCloud; 9 | -------------------------------------------------------------------------------- /src/icons/X.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block`} 6 | `; 7 | 8 | const XIcon: FunctionComponent = () => { 9 | return ( 10 | 16 | 22 | 23 | ); 24 | }; 25 | 26 | export default XIcon; 27 | -------------------------------------------------------------------------------- /src/icons/XCircle.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import tw, { styled } from 'twin.macro'; 3 | 4 | const StyledSvg = styled.svg` 5 | ${tw`w-4 h-4 font-semibold inline-block`} 6 | `; 7 | 8 | const XCircleIcon: FunctionComponent = () => { 9 | return ( 10 | 16 | 22 | 23 | ); 24 | }; 25 | 26 | export default XCircleIcon; 27 | -------------------------------------------------------------------------------- /src/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Check } from './Check'; 2 | export { default as Checked } from './Checked'; 3 | export { default as Unchecked } from './Unchecked'; 4 | export { default as Reset } from './Reset'; 5 | export { default as Settings } from './Settings'; 6 | export { default as X } from './X'; 7 | export { default as XCircle } from './XCircle'; 8 | export { default as Soundwave } from './Soundwave'; 9 | export { default as Filter } from './Filter'; 10 | export { default as Edit } from './Edit'; 11 | export { default as FilterOff } from './FilterOff'; 12 | export { default as ScatterPlot } from './ScatterPlot'; 13 | export { default as Table } from './Table'; 14 | export { default as Show } from './Show'; 15 | export { default as Hide } from './Hide'; 16 | export { default as Delete } from './Delete'; 17 | export { default as Add } from './Add'; 18 | export { default as AddBox } from './AddBox'; 19 | export { default as Lightbulb } from './Lightbulb'; 20 | export { default as Copy } from './Copy'; 21 | -------------------------------------------------------------------------------- /src/lenses/HtmlLens.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import Html from '../components/ui/Html'; 3 | import { Lens } from '../types'; 4 | 5 | const HtmlLens: Lens = ({ value }) => { 6 | return ; 7 | }; 8 | 9 | HtmlLens.key = 'HtmlLens'; 10 | HtmlLens.dataTypes = ['str']; 11 | HtmlLens.defaultHeight = 48; 12 | HtmlLens.minHeight = 22; 13 | HtmlLens.maxHeight = 512; 14 | HtmlLens.displayName = 'HTML (unsafe)'; 15 | 16 | export default HtmlLens; 17 | -------------------------------------------------------------------------------- /src/lenses/ImageLens/MenuBar.tsx: -------------------------------------------------------------------------------- 1 | import FitIcon from '../../icons/Reset'; 2 | import Button from '../../components/ui/Button'; 3 | import { FunctionComponent } from 'react'; 4 | import { BsArrowClockwise } from 'react-icons/bs'; 5 | import tw from 'twin.macro'; 6 | 7 | const Styles = tw.div`px-2 py-1 absolute top-0 right-0 items-start flex flex-row-reverse z-10`; 8 | 9 | interface Props { 10 | onReset: () => void; 11 | onRotate: () => void; 12 | } 13 | 14 | const MenuBar: FunctionComponent = ({ onReset, onRotate }) => { 15 | return ( 16 | 17 | 20 | 23 | 24 | ); 25 | }; 26 | 27 | export default MenuBar; 28 | -------------------------------------------------------------------------------- /src/lenses/LensContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { LensSettings, Setter } from '../types'; 3 | 4 | interface LensContextType { 5 | settings: LensSettings; 6 | onChangeSettings: Setter; 7 | sharedState: Record; 8 | setSharedState: Setter>; 9 | } 10 | 11 | const defaultLensContext: LensContextType = { 12 | settings: {}, 13 | onChangeSettings: () => null, 14 | sharedState: {}, 15 | setSharedState: () => null, 16 | }; 17 | 18 | const LensContext = createContext(defaultLensContext); 19 | export default LensContext; 20 | -------------------------------------------------------------------------------- /src/lenses/MarkdownLens.tsx: -------------------------------------------------------------------------------- 1 | import { Lens } from '../types'; 2 | import Markdown from '../components/ui/Markdown'; 3 | 4 | const MarkdownLens: Lens = ({ value }) => { 5 | return ; 6 | }; 7 | 8 | MarkdownLens.key = 'MarkdownLens'; 9 | MarkdownLens.dataTypes = ['str']; 10 | MarkdownLens.defaultHeight = 128; 11 | MarkdownLens.minHeight = 22; 12 | MarkdownLens.displayName = 'Markdown'; 13 | 14 | export default MarkdownLens; 15 | -------------------------------------------------------------------------------- /src/lenses/None.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | 3 | const None = () => { 4 | return ( 5 |
6 | None 7 |
8 | ); 9 | }; 10 | export default None; 11 | -------------------------------------------------------------------------------- /src/lenses/SafeHtmlLens.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import { Lens } from '../types'; 3 | import DOMPurify from 'dompurify'; 4 | import Html from '../components/ui/Html'; 5 | 6 | const SafeHtmlLens: Lens = ({ value }) => { 7 | const safe_html = DOMPurify.sanitize(value); 8 | 9 | return ; 10 | }; 11 | 12 | SafeHtmlLens.key = 'SafeHtmlLens'; 13 | SafeHtmlLens.dataTypes = ['str']; 14 | SafeHtmlLens.defaultHeight = 48; 15 | SafeHtmlLens.minHeight = 22; 16 | SafeHtmlLens.maxHeight = 512; 17 | SafeHtmlLens.displayName = 'HTML'; 18 | 19 | export default SafeHtmlLens; 20 | -------------------------------------------------------------------------------- /src/lenses/ScalarLens.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import ScalarValue from '../components/ScalarValue'; 3 | import { Lens } from '../types'; 4 | 5 | const ScalarView: Lens = ({ value, column }) => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | ScalarView.key = 'ScalarView'; 14 | ScalarView.dataTypes = ['int', 'float', 'bool', 'str', 'datetime', 'Category']; 15 | ScalarView.defaultHeight = 22; 16 | ScalarView.minHeight = 22; 17 | ScalarView.maxHeight = 64; 18 | ScalarView.displayName = 'Value'; 19 | 20 | export default ScalarView; 21 | -------------------------------------------------------------------------------- /src/lenses/TextLens.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import { Lens } from '../types'; 3 | 4 | const TextLens: Lens = ({ value }) => { 5 | return ( 6 |
7 | {value} 8 |
9 | ); 10 | }; 11 | 12 | TextLens.key = 'TextLens'; 13 | TextLens.dataTypes = ['str']; 14 | TextLens.defaultHeight = 48; 15 | TextLens.minHeight = 22; 16 | TextLens.maxHeight = 512; 17 | TextLens.displayName = 'Text'; 18 | 19 | export default TextLens; 20 | -------------------------------------------------------------------------------- /src/lenses/index.ts: -------------------------------------------------------------------------------- 1 | import ArrayLens from './ArrayLens'; 2 | import AudioLens from './AudioLens'; 3 | import HtmlLens from './HtmlLens'; 4 | import ImageLens from './ImageLens'; 5 | import MarkdownLens from './MarkdownLens'; 6 | import MeshLens from './MeshLens'; 7 | import SafeHtmlLens from './SafeHtmlLens'; 8 | import ScalarLens from './ScalarLens'; 9 | import SequenceLens from './SequenceLens'; 10 | import SpectrogramLens from './SpectrogramLens'; 11 | import TextLens from './TextLens'; 12 | import VideoLens from './VideoLens'; 13 | import RougeScoreLens from './RougeScoreLens'; 14 | import BLEUScoreLens from './BLEUScoreLens'; 15 | import BoundingBoxLens from './BoundingBoxLens'; 16 | 17 | export const ALL_LENSES = [ 18 | ArrayLens, 19 | AudioLens, 20 | SpectrogramLens, 21 | VideoLens, 22 | ImageLens, 23 | MeshLens, 24 | SequenceLens, 25 | TextLens, 26 | SafeHtmlLens, 27 | HtmlLens, 28 | MarkdownLens, 29 | ScalarLens, 30 | RougeScoreLens, 31 | BLEUScoreLens, 32 | BoundingBoxLens, 33 | ]; 34 | -------------------------------------------------------------------------------- /src/lenses/useCellValues.ts: -------------------------------------------------------------------------------- 1 | import { useRow } from '../hooks/useCell'; 2 | import { Problem } from '../types'; 3 | 4 | function useCellValues( 5 | rowIndex: number, 6 | columnKeys: string[], 7 | deferLoading = false 8 | ): [unknown[] | undefined, Problem | undefined] { 9 | const fetchDelay = deferLoading ? 200 : 0; 10 | const [values, problem] = useRow(rowIndex, columnKeys, fetchDelay); 11 | return [ 12 | values === undefined ? values : columnKeys.map((key) => values[key]), 13 | problem, 14 | ]; 15 | } 16 | 17 | export default useCellValues; 18 | -------------------------------------------------------------------------------- /src/lenses/useSetting.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext } from 'react'; 2 | import LensContext from './LensContext'; 3 | import { Setter } from '../types'; 4 | 5 | function useSetting(name: string, defaultValue: T): [T, Setter] { 6 | const { settings, onChangeSettings } = useContext(LensContext); 7 | 8 | const setter: Setter = useCallback( 9 | (value) => { 10 | if (typeof value === 'function') { 11 | onChangeSettings((previousSettings) => ({ 12 | [name]: (value as CallableFunction)( 13 | previousSettings?.[name] ?? defaultValue 14 | ), 15 | })); 16 | } else { 17 | onChangeSettings({ [name]: value }); 18 | } 19 | }, 20 | [name, defaultValue, onChangeSettings] 21 | ); 22 | const value = settings?.[name] ?? defaultValue; 23 | 24 | return [value as T, setter]; 25 | } 26 | 27 | export default useSetting; 28 | -------------------------------------------------------------------------------- /src/lenses/useSharedState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext } from 'react'; 2 | import LensContext from './LensContext'; 3 | import { Setter } from '../types'; 4 | import _ from 'lodash'; 5 | 6 | export function useSharedState( 7 | key: string, 8 | defaultValue: T 9 | ): [T, Setter] { 10 | const { sharedState, setSharedState } = useContext(LensContext); 11 | 12 | const setter: Setter = useCallback( 13 | (valueOrCallback) => { 14 | setSharedState((state) => { 15 | const oldValue = (state[key] as T) ?? defaultValue; 16 | const newValue = _.isFunction(valueOrCallback) 17 | ? valueOrCallback(oldValue) 18 | : valueOrCallback; 19 | return { ...state, [key]: newValue }; 20 | }); 21 | }, 22 | [key, defaultValue, setSharedState] 23 | ); 24 | 25 | const value = (sharedState[key] as T) ?? defaultValue; 26 | 27 | return [value, setter]; 28 | } 29 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import GlobalStyles from './styles/GlobalStyles'; 4 | import App from './App'; 5 | import './globals'; 6 | 7 | createRoot(document.getElementById('root') as HTMLElement).render( 8 | 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/math/distances.ts: -------------------------------------------------------------------------------- 1 | function bhattacharyya(a: number[], b: number[]): number { 2 | const len = a.length; 3 | let total = 0; 4 | for (let i = 0; i < len; i++) { 5 | total += Math.sqrt(a[i] * b[i]); 6 | } 7 | return -Math.log(total); 8 | } 9 | 10 | const exports = { bhattacharyya }; 11 | export default exports; 12 | -------------------------------------------------------------------------------- /src/math/downsampling.test.ts: -------------------------------------------------------------------------------- 1 | import { Vec2 } from '../types'; 2 | import { largestTriangleThreeBuckets } from './downsampling'; 3 | 4 | it('get mean for x=y', () => { 5 | const data: Vec2[] = Array.from(Array(100).keys()).map((x) => [x, x]); 6 | const reduced: Vec2[] = [ 7 | [0, 0], 8 | [9, 9], 9 | [20, 20], 10 | [31, 31], 11 | [42, 42], 12 | [53, 53], 13 | [64, 64], 14 | [75, 75], 15 | [86, 86], 16 | [99, 99], 17 | ]; 18 | expect(largestTriangleThreeBuckets(data, 10)).toEqual(reduced); 19 | }); 20 | 21 | it('test more points than in chart', () => { 22 | const data: Vec2[] = [ 23 | [1, 2], 24 | [5, 2], 25 | [6, 6], 26 | ]; 27 | expect(largestTriangleThreeBuckets(data, 10)).toEqual(data); 28 | }); 29 | 30 | it('select 0 points', () => { 31 | const data: Vec2[] = [ 32 | [1, 2], 33 | [5, 2], 34 | [6, 6], 35 | ]; 36 | expect(largestTriangleThreeBuckets(data, 0)).toEqual(data); 37 | }); 38 | -------------------------------------------------------------------------------- /src/services/config.ts: -------------------------------------------------------------------------------- 1 | import application from '../application'; 2 | import { ConfigApi, Configuration } from '../client'; 3 | 4 | export class ConfigService { 5 | api: ConfigApi; 6 | 7 | constructor() { 8 | let apiBasePath = application.apiUrl; 9 | apiBasePath = apiBasePath ?? application.publicUrl; 10 | const apiConfig = new Configuration({ basePath: apiBasePath }); 11 | 12 | this.api = new ConfigApi(apiConfig); 13 | } 14 | 15 | async get(name: string): Promise { 16 | return (await this.api.getValue({ name })) as T; 17 | } 18 | async getItem(name: string): Promise { 19 | return this.get(name); 20 | } 21 | 22 | async set(name: string, value: T) { 23 | await this.api.setValue({ 24 | name, 25 | setConfigRequest: { value: value ?? null }, 26 | }); 27 | } 28 | async setItem(name: string, value: T) { 29 | this.set(name, value); 30 | } 31 | 32 | async remove(name: string) { 33 | await this.api.remove({ name }); 34 | } 35 | async removeItem(name: string) { 36 | this.remove(name); 37 | } 38 | } 39 | 40 | const configService = new ConfigService(); 41 | 42 | export default configService; 43 | -------------------------------------------------------------------------------- /src/services/websocket/types.ts: -------------------------------------------------------------------------------- 1 | export interface Message { 2 | type: string; 3 | data: unknown; 4 | } 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 7 | export type MessageHandler = (data: any) => void; 8 | -------------------------------------------------------------------------------- /src/stores/appSettings.ts: -------------------------------------------------------------------------------- 1 | import configService from '../services/config'; 2 | import { create } from 'zustand'; 3 | import { persist } from 'zustand/middleware'; 4 | 5 | export const notations = ['scientific', 'standard'] as const; 6 | export type Notation = (typeof notations)[number]; 7 | 8 | export interface AppSettings { 9 | numberNotation: Notation; 10 | setNumberNotation: (notation: Notation) => void; 11 | } 12 | 13 | export const useAppSettings = create()( 14 | persist( 15 | (set) => ({ 16 | numberNotation: 'scientific', 17 | setNumberNotation: (notation) => 18 | set({ 19 | numberNotation: notation, 20 | }), 21 | }), 22 | { 23 | name: 'app_settings', 24 | storage: configService, 25 | } 26 | ) 27 | ); 28 | -------------------------------------------------------------------------------- /src/stores/dataset/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dataset'; 2 | -------------------------------------------------------------------------------- /src/stores/messages.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | export interface State { 4 | persistentErrors: string[]; 5 | addPersistentError: (message: string) => void; 6 | removePersistentError: (message: string) => void; 7 | persistentWarnings: string[]; 8 | addPersistentWarning: (message: string) => void; 9 | removePersistentWarning: (message: string) => void; 10 | } 11 | 12 | export const useMessages = create((set) => ({ 13 | persistentErrors: [], 14 | addPersistentError: (message: string) => 15 | set((state) => ({ persistentErrors: [...state.persistentErrors, message] })), 16 | removePersistentError: (message: string) => 17 | set((state) => ({ 18 | persistentErrors: state.persistentErrors.filter((m) => m !== message), 19 | })), 20 | persistentWarnings: [], 21 | addPersistentWarning: (message: string) => 22 | set((state) => ({ 23 | persistentWarnings: [...state.persistentWarnings, message], 24 | })), 25 | removePersistentWarning: (message: string) => 26 | set((state) => ({ 27 | persistentWarnings: state.persistentWarnings.filter((m) => m !== message), 28 | })), 29 | })); 30 | -------------------------------------------------------------------------------- /src/styles/GlobalStyles.tsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | import tw, { GlobalStyles as BaseStyles } from 'twin.macro'; 3 | 4 | const CustomStyles = createGlobalStyle` 5 | body { 6 | ${tw`antialiased overflow-hidden`} 7 | } 8 | 9 | .Toastify__toast-container { 10 | width: auto !important; 11 | max-width: 500px; 12 | } 13 | 14 | input { 15 | user-select: text; 16 | -webkit-user-select: text; /* Chrome all / Safari all */ 17 | -moz-user-select: text; /* Firefox all */ 18 | -ms-user-select: text; /* IE 10+ */ 19 | } 20 | `; 21 | 22 | const GlobalStyles = (): JSX.Element => ( 23 | <> 24 | 25 | 26 | 27 | ); 28 | 29 | export default GlobalStyles; 30 | -------------------------------------------------------------------------------- /src/systems/dnd/Draggable.tsx: -------------------------------------------------------------------------------- 1 | import { useDraggable } from '@dnd-kit/core'; 2 | import { ReactNode, useId } from 'react'; 3 | import { DragData } from './types'; 4 | 5 | interface Props { 6 | data: DragData; 7 | children: ReactNode; 8 | } 9 | 10 | export default function Draggable({ data, children }: Props) { 11 | const id = useId(); 12 | const { attributes, listeners, setNodeRef } = useDraggable({ 13 | id, 14 | data, 15 | }); 16 | 17 | // remove tabIndex from attributes 18 | // as it prevents the keyboard controls of our table from working correctly 19 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 20 | const { tabIndex, ...neededAttributes } = attributes; 21 | 22 | return ( 23 |
24 | {children} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/systems/dnd/OverlayFactory.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import { DragData } from './types'; 3 | import ColumnBadge from '../../components/ui/ColumnBadge'; 4 | import CellBadge from '../../components/ui/CellBadge'; 5 | 6 | interface Props { 7 | data: DragData; 8 | } 9 | 10 | export default function OverlayFactory({ data }: Props): JSX.Element { 11 | if (data.kind === 'column') { 12 | return ; 13 | } 14 | if (data.kind === 'cell') { 15 | return ( 16 | 17 | ); 18 | } 19 | return <>; 20 | } 21 | -------------------------------------------------------------------------------- /src/systems/dnd/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Draggable } from './Draggable'; 2 | export { default as Droppable } from './Droppable'; 3 | export { default as DragContext } from './DragContext'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /src/systems/dnd/types.ts: -------------------------------------------------------------------------------- 1 | import { DataColumn } from '../../types'; 2 | 3 | export interface ColumnDragData { 4 | kind: 'column'; 5 | column: DataColumn; 6 | } 7 | 8 | export interface CellDragData { 9 | kind: 'cell'; 10 | column: DataColumn; 11 | row: number; 12 | } 13 | 14 | export type DragData = ColumnDragData | CellDragData; 15 | export type DragKind = DragData['kind']; 16 | 17 | export interface DropData { 18 | accepts: (data: DragData) => boolean; 19 | onDrop: (data: DragData) => void; 20 | } 21 | -------------------------------------------------------------------------------- /src/types/base.ts: -------------------------------------------------------------------------------- 1 | export type Vec2 = [number, number]; 2 | 3 | export type IndexArray = number[] | Int32Array; 4 | 5 | export type TypedArray = 6 | | Int8Array 7 | | Uint8Array 8 | | Int16Array 9 | | Uint16Array 10 | | Int32Array 11 | | Uint32Array 12 | | Uint8ClampedArray 13 | | Float32Array 14 | | Float64Array; 15 | 16 | export type Setter = (value: T | ((previous: T) => T)) => void; 17 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export * from './dataset'; 3 | export * from './filter'; 4 | export * from './problem'; 5 | export * from './layout'; 6 | export * from './lenses'; 7 | -------------------------------------------------------------------------------- /src/types/layout.ts: -------------------------------------------------------------------------------- 1 | export interface BaseLayoutNode { 2 | kind: string; 3 | } 4 | 5 | export interface SplitNode extends BaseLayoutNode { 6 | kind: 'split'; 7 | weight: number; 8 | orientation?: 'horizontal' | 'vertical'; 9 | children: ContainerNode[]; 10 | } 11 | 12 | export interface WidgetNode extends BaseLayoutNode { 13 | kind: 'widget'; 14 | type: string; 15 | name?: string; 16 | config?: Record; 17 | } 18 | 19 | export interface TabNode extends BaseLayoutNode { 20 | kind: 'tab'; 21 | weight: number; 22 | children: WidgetNode[]; 23 | } 24 | 25 | export type ContainerNode = SplitNode | TabNode; 26 | export type LayoutNode = ContainerNode | WidgetNode; 27 | 28 | export interface AppLayout { 29 | orientation?: 'horizontal' | 'vertical'; 30 | children: ContainerNode[]; 31 | } 32 | -------------------------------------------------------------------------------- /src/types/lenses.ts: -------------------------------------------------------------------------------- 1 | import type { DataColumn } from './dataset'; 2 | import type { DataType } from '../datatypes'; 3 | 4 | export type LensKey = string; 5 | 6 | export type LensSettings = Record; 7 | 8 | export interface LensProps { 9 | value: T; 10 | values: T[]; 11 | column: DataColumn; 12 | columns: DataColumn[]; 13 | rowIndex: number; 14 | url?: string; 15 | urls: (string | undefined)[]; 16 | syncKey?: string; 17 | } 18 | 19 | interface LensAttributes { 20 | displayName: string; 21 | key: LensKey; 22 | dataTypes: DataType['kind'][]; 23 | multi?: boolean; 24 | isEditor?: boolean; 25 | minHeight?: number; 26 | maxHeight?: number; 27 | defaultHeight?: number; 28 | filterAllowedColumns?: ( 29 | allColumns: DataColumn[], 30 | selectedColumns: DataColumn[] 31 | ) => DataColumn[]; 32 | isSatisfied?: (columns: DataColumn[]) => boolean; 33 | handlesNull?: boolean; 34 | } 35 | 36 | export type Lens = React.FunctionComponent> & LensAttributes; 37 | -------------------------------------------------------------------------------- /src/types/problem.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export interface Problem { 4 | type: string; 5 | title: string; 6 | detail?: string; 7 | instance?: string; 8 | } 9 | 10 | export function isProblem(error: unknown): error is Problem { 11 | return _.isObject(error) && 'type' in error && 'title' in error; 12 | } 13 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/widgets/ConfusionMatrix/types.ts: -------------------------------------------------------------------------------- 1 | export interface Bucket { 2 | rows: number[]; 3 | } 4 | 5 | export interface MatrixData { 6 | xNames: string[]; 7 | yNames: string[]; 8 | buckets: Bucket[]; 9 | } 10 | 11 | export interface Cell { 12 | x: number; 13 | y: number; 14 | bucket: Bucket; 15 | } 16 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/Cell/Cell.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react'; 2 | import type { GridChildComponentProps as CellProps } from 'react-window'; 3 | import tw from 'twin.macro'; 4 | import CellFactory from './CellFactory'; 5 | 6 | type Props = CellProps; 7 | 8 | const StyledDiv = tw.div` 9 | whitespace-nowrap px-1 py-0.5 overflow-hidden text-sm border-b border-r w-full h-full 10 | `; 11 | 12 | const Cell: FunctionComponent = ({ style, columnIndex, rowIndex }) => { 13 | return ( 14 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default Cell; 25 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/Cell/CellPlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, memo } from 'react'; 2 | import type { GridChildComponentProps as CellProps } from 'react-window'; 3 | 4 | type Props = CellProps; 5 | 6 | const CellPlaceholder: FunctionComponent = ({ style }) => { 7 | return
; 8 | }; 9 | 10 | export default memo(CellPlaceholder); 11 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/Cell/DefaultCell.tsx: -------------------------------------------------------------------------------- 1 | import Tooltip from '../../../components/ui/Tooltip'; 2 | import { FunctionComponent } from 'react'; 3 | import { DataColumn } from '../../../types'; 4 | import { useDataformat } from '../../../dataformat'; 5 | 6 | interface Props { 7 | column: DataColumn; 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | value: any; 10 | } 11 | 12 | const DefaultCell: FunctionComponent = ({ value, column }) => { 13 | const formatter = useDataformat(); 14 | const formattedValue = formatter.format(value, column?.type); 15 | const preciseValue = formatter.format(value, column?.type, true); 16 | 17 | return ( 18 | 19 |
{formattedValue}
20 |
21 | ); 22 | }; 23 | 24 | export default DefaultCell; 25 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/Cell/NumberCell.tsx: -------------------------------------------------------------------------------- 1 | import ScalarValue from '../../../components/ScalarValue'; 2 | import { FunctionComponent } from 'react'; 3 | import 'twin.macro'; 4 | import { NumberColumn } from '../../../types'; 5 | import { useTableView } from '../context/tableViewContext'; 6 | 7 | interface Props { 8 | column: NumberColumn; 9 | value: number; 10 | } 11 | 12 | const NumberCell: FunctionComponent = ({ value, column }) => { 13 | const { tableView } = useTableView(); 14 | 15 | if (value === undefined || isNaN(value) || value === null) return <>; 16 | 17 | return ( 18 | 24 | ); 25 | }; 26 | 27 | export default NumberCell; 28 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/Cell/index.ts: -------------------------------------------------------------------------------- 1 | import Cell from './Cell'; 2 | 3 | export default Cell; 4 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/MenuBar/AddColumnButton.tsx: -------------------------------------------------------------------------------- 1 | import AddColumnIcon from '../../../icons/AddBox'; 2 | import { FunctionComponent } from 'react'; 3 | import 'twin.macro'; 4 | import NeedsUpgradeButton from '../../../components/ui/NeedsUpgradeButton'; 5 | 6 | const AddColumnButton: FunctionComponent = () => { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default AddColumnButton; 17 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/MenuBar/index.ts: -------------------------------------------------------------------------------- 1 | import MenuBar from './MenuBar'; 2 | 3 | export default MenuBar; 4 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/columnWidthByType.ts: -------------------------------------------------------------------------------- 1 | import { DataType } from '../../datatypes'; 2 | 3 | const columnWidthByType: Record = { 4 | int: 92, 5 | float: 110, 6 | bool: 92, 7 | str: 256, 8 | array: 128, 9 | datetime: 192, 10 | Embedding: 128, 11 | Audio: 200, 12 | Video: 200, 13 | Window: 150, 14 | BoundingBox: 128, 15 | Mesh: 200, 16 | Image: 200, 17 | Sequence1D: 200, 18 | Category: 128, 19 | Sequence: 128, 20 | Unknown: 128, 21 | }; 22 | 23 | export default columnWidthByType; 24 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/context/tableViewContext.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FunctionComponent, useContext } from 'react'; 3 | import { TableView } from '../../../types'; 4 | 5 | type RowContextState = { 6 | tableView: TableView; 7 | setTableView: (tableView: TableView) => void; 8 | }; 9 | 10 | const RowContext = React.createContext({ 11 | tableView: 'full', 12 | setTableView: () => null, 13 | }); 14 | 15 | export const TableViewProvider: FunctionComponent< 16 | RowContextState & { 17 | children: React.ReactNode; 18 | } 19 | > = ({ children, ...props }) => { 20 | const { setTableView, tableView } = props; 21 | 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | }; 28 | 29 | export const useTableView = (): RowContextState => { 30 | return useContext(RowContext); 31 | }; 32 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/getRowHeight.ts: -------------------------------------------------------------------------------- 1 | const getRowHeight = (): number => 24; 2 | 3 | export default getRowHeight; 4 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/hooks/useCellValue.ts: -------------------------------------------------------------------------------- 1 | import { Dataset, useDataset } from '../../../stores/dataset'; 2 | import useSort from './useSort'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | function useCellValue(columnKey: string, rowIndex: number): any { 6 | const originalIndex = useSort().getOriginalIndex(rowIndex); 7 | return useDataset((d: Dataset) => d.columnData[columnKey]?.[originalIndex]); 8 | } 9 | 10 | export default useCellValue; 11 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/hooks/useRowCount.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { SortingContext } from '../context/sortingContext'; 3 | 4 | function useRowCount(): number { 5 | const { sortedIndices } = useContext(SortingContext); 6 | return sortedIndices.length; 7 | } 8 | 9 | export default useRowCount; 10 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/hooks/useSort.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { SortingContext } from '../context/sortingContext'; 3 | 4 | function useSort() { 5 | const { sortedIndices, getOriginalIndex, getSortedIndex } = 6 | useContext(SortingContext); 7 | return { sortedIndices, getOriginalIndex, getSortedIndex }; 8 | } 9 | 10 | export default useSort; 11 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/index.ts: -------------------------------------------------------------------------------- 1 | import DataGrid from './DataGrid'; 2 | 3 | export default DataGrid; 4 | -------------------------------------------------------------------------------- /src/widgets/DataGrid/useRowHighlight.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { Dataset, useDataset } from '../../stores/dataset'; 3 | 4 | const highlightRowSelector = (d: Dataset) => d.highlightRowAt; 5 | const dehighlightRowSelector = (d: Dataset) => d.dehighlightRowAt; 6 | 7 | interface ReturnType { 8 | isHighlighted: boolean; 9 | highlightRow: () => void; 10 | dehighlightRow: () => void; 11 | } 12 | 13 | function useRowHighlight(rowIndex: number): ReturnType { 14 | const isHighlightedSelector = useCallback( 15 | (d: Dataset) => d.isIndexHighlighted[rowIndex], 16 | [rowIndex] 17 | ); 18 | const isHighlighted = useDataset(isHighlightedSelector); 19 | 20 | const highlightRowAt = useDataset(highlightRowSelector); 21 | const highlightRow = useCallback( 22 | () => highlightRowAt(rowIndex), 23 | [rowIndex, highlightRowAt] 24 | ); 25 | 26 | const dehighlightRowAt = useDataset(dehighlightRowSelector); 27 | const dehighlightRow = useCallback( 28 | () => dehighlightRowAt(rowIndex), 29 | [rowIndex, dehighlightRowAt] 30 | ); 31 | 32 | return { isHighlighted, highlightRow, dehighlightRow }; 33 | } 34 | 35 | export default useRowHighlight; 36 | -------------------------------------------------------------------------------- /src/widgets/Histogram/index.ts: -------------------------------------------------------------------------------- 1 | import Histogram from './Histogram'; 2 | 3 | export default Histogram; 4 | -------------------------------------------------------------------------------- /src/widgets/Histogram/types.ts: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | 3 | export type HistogramType = 'discrete' | 'continuous'; 4 | 5 | export interface HistogramData { 6 | xBins: Bin[]; 7 | yBins: Bin[]; 8 | all: Stack[]; 9 | filtered: Stack[]; 10 | kind: HistogramType; 11 | binToRowIndices: Map>; 12 | xColumnName?: string; 13 | yColumnName?: string; 14 | } 15 | 16 | export type Bucket = d3.SeriesPoint<{ [key: string]: number }> & { 17 | yKey: BinKey; 18 | yBin: number; 19 | xKey: BinKey; 20 | xBin: number; 21 | }; 22 | 23 | export type Stack = Bucket[]; 24 | 25 | export type BinKey = number | string | boolean; 26 | 27 | export type Bin = { 28 | index: number; 29 | key: BinKey; 30 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 31 | value: any; 32 | min: number; 33 | max: number; 34 | }; 35 | -------------------------------------------------------------------------------- /src/widgets/Inspector/AddViewButton.tsx: -------------------------------------------------------------------------------- 1 | import AddIcon from '../../icons/AddCell'; 2 | import Dropdown from '../../components/ui/Dropdown'; 3 | import ViewConfigurator from './ViewConfigurator'; 4 | 5 | const AddViewButton = (): JSX.Element => { 6 | return ( 7 | }> 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default AddViewButton; 14 | -------------------------------------------------------------------------------- /src/widgets/Inspector/Header/index.ts: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export * from './Header'; 4 | export default Header; 5 | -------------------------------------------------------------------------------- /src/widgets/Inspector/MenuBar/MenuBar.tsx: -------------------------------------------------------------------------------- 1 | import 'twin.macro'; 2 | import { FunctionComponent } from 'react'; 3 | import AddViewButton from '../AddViewButton'; 4 | import ColumnCountSelect from './ColumnCountSelect'; 5 | import { WidgetMenu } from '../../../lib'; 6 | 7 | interface Props { 8 | visibleColumnsCount: number; 9 | setVisibleColumnsCount: (count: number) => void; 10 | visibleColumnsCountOptions: number[]; 11 | } 12 | 13 | const MenuBar: FunctionComponent = ({ 14 | visibleColumnsCount, 15 | setVisibleColumnsCount, 16 | visibleColumnsCountOptions, 17 | }) => { 18 | return ( 19 | 20 |
21 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default MenuBar; 32 | -------------------------------------------------------------------------------- /src/widgets/Inspector/MenuBar/index.tsx: -------------------------------------------------------------------------------- 1 | import MenuBar from './MenuBar'; 2 | 3 | export default MenuBar; 4 | -------------------------------------------------------------------------------- /src/widgets/Inspector/ViewConfigurator/ColumnList.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const ColumnList = tw.div`flex-grow bg-white flex flex-col min-h-[128px] max-h-[270px] overflow-y-auto border-b border-gray-300 border-t overflow-x-hidden`; 4 | export default ColumnList; 5 | -------------------------------------------------------------------------------- /src/widgets/Inspector/ViewConfigurator/ColumnListSeparator.tsx: -------------------------------------------------------------------------------- 1 | import tw from 'twin.macro'; 2 | 3 | const ColumnListSeparator = tw.div`w-full h-px bg-gray-300`; 4 | export default ColumnListSeparator; 5 | -------------------------------------------------------------------------------- /src/widgets/Inspector/ViewConfigurator/index.ts: -------------------------------------------------------------------------------- 1 | import ViewConfigurator from './ViewConfigurator'; 2 | export default ViewConfigurator; 3 | -------------------------------------------------------------------------------- /src/widgets/Inspector/index.ts: -------------------------------------------------------------------------------- 1 | import Inspector from './Inspector'; 2 | 3 | export default Inspector; 4 | -------------------------------------------------------------------------------- /src/widgets/Inspector/types.tsx: -------------------------------------------------------------------------------- 1 | import { LensKey, LensSettings } from '../../types'; 2 | 3 | export interface LensConfig { 4 | view: LensKey; 5 | key: string; 6 | name: string; 7 | columns: string[]; 8 | settings: LensSettings; 9 | } 10 | -------------------------------------------------------------------------------- /src/widgets/MetricsWidget/confusion.ts: -------------------------------------------------------------------------------- 1 | interface Confusion { 2 | truePositives: number; 3 | falsePositives: number; 4 | trueNegatives: number; 5 | falseNegatives: number; 6 | } 7 | 8 | export function computeConfusion(actualValues: boolean[], assignedValues: boolean[]) { 9 | const confusion: Confusion = { 10 | truePositives: 0, 11 | falsePositives: 0, 12 | trueNegatives: 0, 13 | falseNegatives: 0, 14 | }; 15 | 16 | for (let i = 0; i < actualValues.length; i++) { 17 | const actualValue = actualValues[i]; 18 | const assignedValue = assignedValues[i]; 19 | if (assignedValue) { 20 | if (actualValue) { 21 | confusion.truePositives++; 22 | } else { 23 | confusion.falsePositives++; 24 | } 25 | } else if (actualValue) { 26 | confusion.falseNegatives++; 27 | } else { 28 | confusion.trueNegatives++; 29 | } 30 | } 31 | 32 | return confusion; 33 | } 34 | -------------------------------------------------------------------------------- /src/widgets/MetricsWidget/types.ts: -------------------------------------------------------------------------------- 1 | import { DataKind } from '../../datatypes'; 2 | 3 | export type ValueArray = number[] | Int32Array | boolean[] | string[]; 4 | 5 | export interface Metric { 6 | signature: Record; 7 | compute: (values: ValueArray[]) => number; 8 | } 9 | -------------------------------------------------------------------------------- /src/widgets/ScatterplotView/index.tsx: -------------------------------------------------------------------------------- 1 | import ScatterplotView from './ScatterplotView'; 2 | 3 | export default ScatterplotView; 4 | -------------------------------------------------------------------------------- /src/widgets/SimilarityMap/PCAParameterMenu.tsx: -------------------------------------------------------------------------------- 1 | import Menu from '../../components/ui/Menu'; 2 | import Select from '../../components/ui/Select'; 3 | import { PCANormalization, pcaNormalizations } from '../../services/data'; 4 | 5 | interface Props { 6 | pcaNormalization: PCANormalization; 7 | onChangePcaNormalization: (value?: PCANormalization) => void; 8 | } 9 | 10 | export const PCAParameterMenu = ({ 11 | pcaNormalization, 12 | onChangePcaNormalization, 13 | }: Props): JSX.Element => { 14 | return ( 15 | <> 16 | PCA Settings 17 | Normalization 18 | 19 |