diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..5c99ba7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +node_modules +dist +coverage +**/*.d.ts +tests diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..d66148c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,39 @@ +module.exports = { + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module' + }, + plugins: ['@typescript-eslint'], + rules: { + '@typescript-eslint/naming-convention': [ + 'error', + { + 'selector': 'interface', + 'format': ['PascalCase'], + 'custom': { + 'regex': '^I[A-Z]', + 'match': true + } + } + ], + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/quotes': [ + 'error', + 'single', + { avoidEscape: true, allowTemplateLiterals: false } + ], + curly: ['error', 'all'], + eqeqeq: 'error', + 'prefer-arrow-callback': 'error' + } +}; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..52e87d9 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: Build + +on: + push: + branches: main + pull_request: + branches: '*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install node + uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: '3.7' + architecture: 'x64' + + + - name: Setup pip cache + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: pip-3.7-${{ hashFiles('package.json') }} + restore-keys: | + pip-3.7- + pip- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Setup yarn cache + uses: actions/cache@v2 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + yarn- + + - name: Install dependencies + run: python -m pip install -U jupyterlab~=3.0 jupyter_packaging~=0.7.9 + - name: Build the extension + run: | + jlpm + jlpm run eslint:check + python -m pip install git+https://github.com/yt-project/yt.git + python -m pip install . + + jupyter server extension list 2>&1 | grep -ie "widgyts.*OK" + + jupyter labextension list 2>&1 | grep -ie "@yt-project/yt-widgets.*OK" + python -m jupyterlab.browser_check diff --git a/.gitignore b/.gitignore index 8d135f4..f48aa49 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ node_modules/ *.egg-info/ .ipynb_checkpoints *.tsbuildinfo +widgyts/labextension -*/labextension/*.tgz # Created by https://www.gitignore.io/api/python # Edit at https://www.gitignore.io/?templates=python @@ -36,10 +36,7 @@ pip-wheel-metadata/ share/python-wheels/ .installed.cfg *.egg -node_modules/ -widgyts/static/ -js/package-lock.json -js/yarn.lock +MANIFEST # PyInstaller # Usually these files are written by a python script from a template @@ -54,6 +51,7 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache @@ -67,14 +65,6 @@ coverage.xml *.mo *.pot -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - # Scrapy stuff: .scrapy @@ -84,9 +74,6 @@ docs/_build/ # PyBuilder target/ -# Jupyter Notebook -.ipynb_checkpoints - # pyenv .python-version @@ -96,14 +83,6 @@ celerybeat-schedule # SageMath parsed files *.sage.py -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - # Spyder project settings .spyderproject .spyproject @@ -111,9 +90,23 @@ ENV/ # Rope project settings .ropeproject +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + # mkdocs documentation /site # mypy .mypy_cache/ -.vscode/settings.json +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python + +# OSX files +.DS_Store diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..58e7054 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules +**/node_modules +**/lib +**/package.json +widgyts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b0a179d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid" +} diff --git a/LICENSE b/LICENSE index ffa7ce0..a05e2a4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ BSD 3-Clause License -Copyright (c) 2018, Data Exploration Lab -All rights reserved. +Copyright (c) 2020, Data Exploration Lab All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/MANIFEST.in b/MANIFEST.in index c3942dd..b3d188b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,12 +1,14 @@ include LICENSE include README.md include pyproject.toml - include jupyter-config/widgyts.json include package.json +include install.json include ts*.json -include widgyts/labextension/*.tgz +include yarn.lock + +graft widgyts/labextension # Javascript files graft src diff --git a/examples/galaxy_display.ipynb b/examples/galaxy_display.ipynb index c83f5f0..51c5fc7 100644 --- a/examples/galaxy_display.ipynb +++ b/examples/galaxy_display.ipynb @@ -33,12 +33,10 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ - "ds = yt.load(\"IsolatedGalaxy/galaxy0030/galaxy0030\")\n", + "ds = yt.load_sample(\"IsolatedGalaxy\")\n", "s = ds.r[:,:,0.5]\n", "ss = s.display(\"density\")\n", "ss" @@ -61,9 +59,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.9.1" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/install.json b/install.json new file mode 100644 index 0000000..4259140 --- /dev/null +++ b/install.json @@ -0,0 +1,5 @@ +{ + "packageManager": "python", + "packageName": "widgyts", + "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package widgyts" +} diff --git a/jupyter-config/widgyts.json b/jupyter-config/widgyts.json index 825ebfb..8e89f9b 100644 --- a/jupyter-config/widgyts.json +++ b/jupyter-config/widgyts.json @@ -1,7 +1,7 @@ { - "NotebookApp": { - "nbserver_extensions": { - "widgytsts": true + "ServerApp": { + "jpserver_extensions": { + "widgyts": true } } } diff --git a/package.json b/package.json index 6eef5da..8f99a49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yt-project/yt-widgets", - "version": "0.4.0dev0", + "version": "0.4.0", "description": "A Custom Jupyter Widget Library for Interactive Visualization with yt", "author": "Data Exploration Lab", "contributors": [ @@ -29,7 +29,8 @@ "license": "BSD-3-Clause", "files": [ "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf,wasm}", - "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + "style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}", + "style/index.js" ], "main": "lib/index.js", "types": "lib/index.d.ts", @@ -39,42 +40,74 @@ "url": "https://github.com/yt-project/widgyts.git" }, "scripts": { - "build": "jlpm run build:lib", - "build:labextension": "cd widgyts && rimraf labextension && mkdirp labextension && cd labextension && npm pack ../..", - "build:lib": "tsc", + "build": "jlpm run build:lib && jlpm run build:labextension:dev", "build:all": "jlpm run build:labextension", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc", + "build:prod": "jlpm run build:lib && jlpm run build:labextension", "clean": "jlpm run clean:lib", - "clean:lib": "rimraf lib tsconfig.tsbuildinfo", - "clean:labextension": "rimraf widgyts/labextension", "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", + "clean:labextension": "rimraf widgyts/labextension", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", "eslint": "eslint . --ext .ts,.tsx --fix", "eslint:check": "eslint . --ext .ts,.tsx", - "prepare": "jlpm run clean && jlpm run build", - "watch": "tsc -w" + "install:extension": "jupyter labextension develop --overwrite .", + "prepare": "jlpm run clean && jlpm run build:prod", + "watch": "run-p watch:src watch:labextension", + "watch:labextension": "jupyter labextension watch .", + "watch:src": "tsc -w" }, "dependencies": { "@data-exp-lab/yt-tools": "^0.3.0", - "@jupyterlab/application": "^2.0.0", - "@jupyterlab/coreutils": "^4.0.0", - "@jupyterlab/mainmenu": "^2.1.1", - "@jupyterlab/services": "^5.0.0", - "@lumino/widgets": "^1.13.0", - "@types/node": "^14.0.5", - "ipycanvas": "^0.4.7" + "@jupyterlab/application": "^3.0.4", + "@jupyterlab/coreutils": "^5.0.2", + "@jupyterlab/mainmenu": "^3.0.3", + "@jupyterlab/services": "^6.0.3", + "@jupyter-widgets/base": "^1.1.10 || ^2 || ^3 || ^4", + "@types/node": "^10.11.6", + "ipycanvas": "^0.8.2" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^2.25.0", - "@typescript-eslint/parser": "^2.25.0", - "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.1", - "eslint-plugin-prettier": "^3.1.2", - "mkdirp": "^1.0.3", - "prettier": "1.16.4", - "rimraf": "^2.6.1", - "typescript": "^3.9.3" + "@jupyterlab/builder": "^3.0.0", + "@phosphor/application": "^1.6.0", + "@phosphor/widgets": "^1.6.0", + "@types/expect.js": "^0.3.29", + "@types/mocha": "^5.2.5", + "@types/node": "^10.11.6", + "@types/webpack-env": "^1.13.6", + "@typescript-eslint/eslint-plugin": "^3.6.0", + "@typescript-eslint/parser": "^3.6.0", + "acorn": "^7.2.0", + "css-loader": "^3.2.0", + "eslint": "^7.4.0", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-prettier": "^3.1.4", + "expect.js": "^0.3.1", + "fs-extra": "^7.0.0", + "karma": "^3.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.5", + "karma-typescript": "^5.0.3", + "karma-typescript-es6-transform": "^5.0.3", + "mkdirp": "^0.5.1", + "mocha": "^5.2.0", + "npm-run-all": "^4.1.3", + "prettier": "^2.0.5", + "rimraf": "^2.6.2", + "source-map-loader": "^0.2.4", + "style-loader": "^1.0.0", + "ts-loader": "^5.2.1", + "typescript": "^4.1.3", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2" }, "sideEffects": [ - "style/*.css" + "style/*.css", + "style/index.js" ], "jupyterlab": { "discovery": { @@ -87,6 +120,15 @@ } } }, - "extension": "lib/plugin" - } + "extension": "lib/plugin", + "outputDir": "widgyts/labextension", + "webpackConfig": "./webpack.config.js", + "sharedPackages": { + "@jupyter-widgets/base": { + "bundled": false, + "singleton": true + } + } + }, + "styleModule": "style/index.js" } diff --git a/pyproject.toml b/pyproject.toml index 5cf16d6..35c6456 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["jupyter_packaging~=0.4.0", "jupyterlab~=2.0", "setuptools>=40.8.0", "wheel"] +requires = ["jupyter_packaging~=0.7.9", "jupyterlab~=3.0", "setuptools>=40.8.0", "wheel"] build-backend = "setuptools.build_meta" [tool.black] diff --git a/setup.py b/setup.py index b3f8d15..441bb38 100644 --- a/setup.py +++ b/setup.py @@ -1,90 +1,94 @@ """ -Setup Module to setup Python Handlers for the widgyts extension. +widgyts setup """ -import os +import json +from pathlib import Path import setuptools from jupyter_packaging import ( combine_commands, create_cmdclass, - ensure_python, ensure_targets, install_npm, + skip_if_exists, ) -HERE = os.path.abspath(os.path.dirname(__file__)) +HERE = Path(__file__).parent.resolve() # The name of the project name = "widgyts" -# Ensure a valid python version -ensure_python(">=3.5") - -# Get our version -version = "0.4.0dev0" - -lab_path = os.path.join(HERE, name, "labextension") +lab_path = HERE / name / "labextension" # Representative files that should exist after a successful build jstargets = [ - os.path.join(HERE, "lib", "widgyts.js"), + str(lab_path / "package.json"), ] -package_data_spec = {name: ["*"]} +package_data_spec = { + name: ["*"], +} + +labext_name = "@yt-project/yt-widgets" data_files_spec = [ - ("share/jupyter/lab/extensions", lab_path, "*.tgz"), - ("etc/jupyter/jupyter_notebook_config.d", "jupyter-config", "widgyts.json"), + (f"share/jupyter/labextensions/{labext_name}", str(lab_path), "**"), + (f"share/jupyter/labextensions/{labext_name}", str(HERE), "install.json"), + ("etc/jupyter/jupyter_server_config.d", "jupyter-config", "widgyts.json"), ] cmdclass = create_cmdclass( "jsdeps", package_data_spec=package_data_spec, data_files_spec=data_files_spec ) -cmdclass["jsdeps"] = combine_commands( - install_npm(HERE, build_cmd="build:all", npm=["jlpm"]), ensure_targets(jstargets), +js_command = combine_commands( + install_npm(HERE, build_cmd="build:prod", npm=["jlpm"]), ensure_targets(jstargets), ) -with open("README.md", "r") as fh: - long_description = fh.read() +is_repo = (HERE / ".git").exists() +if is_repo: + cmdclass["jsdeps"] = js_command +else: + cmdclass["jsdeps"] = skip_if_exists(jstargets, js_command) + +long_description = (HERE / "README.md").read_text() + +# Get the package info from package.json +pkg_json = json.loads((HERE / "package.json").read_bytes()) setup_args = dict( name=name, - version=version, - url="https://github.com/yt-project/widgyts", - author="Data Exploration Lab", - author_email="mmunk2@illinois.edu", - description="A Custom Jupyter Widget Library for yt", + version=pkg_json["version"], + url=pkg_json["homepage"], + author=pkg_json["author"], + description=pkg_json["description"], + license=pkg_json["license"], long_description=long_description, long_description_content_type="text/markdown", cmdclass=cmdclass, packages=setuptools.find_packages(), install_requires=[ - "jupyterlab~=2.0", + "jupyterlab~=3.0", "ipycanvas>=0.4.7", "ipywidgets>=7.5.1", "numpy>=1.14", "traitlets>=4.3.3", - "yt>=3.5.1", + "yt>=4.0.dev0", "pythreejs>=2.2.0", ], zip_safe=False, include_package_data=True, - license="BSD-3-Clause", + python_requires=">=3.6", platforms="Linux, Mac OS X, Windows", - keywords=["Jupyter", "JupyterLab"], + keywords=["Jupyter", "JupyterLab", "JupyterLab3"], classifiers=[ "License :: OSI Approved :: BSD License", - "Development Status :: 4 - Beta", - "Framework :: IPython", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Topic :: Multimedia :: Graphics", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Framework :: Jupyter", ], ) diff --git a/src/index.ts b/src/index.ts index 6de4005..5d7aae4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export * from './widgyts'; \ No newline at end of file +export * from './widgyts'; diff --git a/src/plugin.ts b/src/plugin.ts index 7f5b745..390ca5a 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,38 +1,28 @@ import { - Application, IPlugin -} from '@lumino/application'; + JupyterFrontEndPlugin, + JupyterFrontEnd +} from '@jupyterlab/application'; -import { - Widget -} from '@lumino/widgets'; - -import { - IJupyterWidgetRegistry -} from '@jupyter-widgets/base'; +import { IJupyterWidgetRegistry } from '@jupyter-widgets/base'; import * as widgytsExports from './widgyts'; -import { - MODULE_NAME, - MODULE_VERSION -} from './version'; -const EXTENSION_ID = MODULE_NAME + ":plugin"; -console.log("widgyts version " + MODULE_VERSION); -console.log("widgyts module " + MODULE_NAME); - -const widgytsPlugin: IPlugin, void> = { - id: EXTENSION_ID, - requires: [IJupyterWidgetRegistry], - activate: activateWidgetExtension, - autoStart: true -} as unknown as IPlugin, void>; +import { MODULE_NAME, MODULE_VERSION } from './version'; +const EXTENSION_ID = MODULE_NAME + ':plugin'; +console.log('widgyts version ' + MODULE_VERSION); +console.log('widgyts module ' + MODULE_NAME); -export default widgytsPlugin; - -function activateWidgetExtension(app: Application, registry: IJupyterWidgetRegistry): void { +const widgytsPlugin: JupyterFrontEndPlugin = { + id: EXTENSION_ID, + requires: [IJupyterWidgetRegistry], + activate: (app: JupyterFrontEnd, registry: IJupyterWidgetRegistry): void => { registry.registerWidget({ - name: MODULE_NAME, - version: MODULE_VERSION, - exports: widgytsExports, + name: MODULE_NAME, + version: MODULE_VERSION, + exports: widgytsExports }); -} \ No newline at end of file + }, + autoStart: true +}; + +export default widgytsPlugin; diff --git a/src/version.ts b/src/version.ts index 8b74c74..25f78d8 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,7 +1,8 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const data = require('../package.json'); /* As seen in the ipycanvas repository */ export const MODULE_VERSION = data.version; -export const MODULE_NAME = data.name; \ No newline at end of file +export const MODULE_NAME = data.name; diff --git a/src/widgyts.ts b/src/widgyts.ts index b02b3bb..ea8f232 100644 --- a/src/widgyts.ts +++ b/src/widgyts.ts @@ -1,14 +1,24 @@ -import { DOMWidgetModel, ISerializers, WidgetModel, unpack_models } from '@jupyter-widgets/base'; +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { + DOMWidgetModel, + ISerializers, + WidgetModel, + unpack_models +} from '@jupyter-widgets/base'; import { CanvasView, CanvasModel } from 'ipycanvas'; -import type { FixedResolutionBuffer, ColormapCollection, VariableMesh } from '@data-exp-lab/yt-tools'; +import type { + FixedResolutionBuffer, + ColormapCollection, + VariableMesh +} from '@data-exp-lab/yt-tools'; import { MODULE_NAME, MODULE_VERSION } from './version'; const _yt_tools = import('@data-exp-lab/yt-tools'); -function serializeArray(array: Float64Array) { +function serializeArray(array: Float64Array): DataView { return new DataView(array.buffer.slice(0)); } -function deserializeArray(dataview: DataView | null) { +function deserializeArray(dataview: DataView | null): Float64Array | null { if (dataview === null) { return null; } @@ -16,12 +26,12 @@ function deserializeArray(dataview: DataView | null) { return new Float64Array(dataview.buffer); } -/* +/* * We have this as we can potentially have more than one FRB for a variable mesh * */ export class VariableMeshModel extends DOMWidgetModel { - defaults() { + defaults(): any { return { ...super.defaults(), _model_name: VariableMeshModel.model_name, @@ -36,7 +46,7 @@ export class VariableMeshModel extends DOMWidgetModel { }; } - initialize(attributes: any, options: any) { + initialize(attributes: any, options: any): void { super.initialize(attributes, options); _yt_tools.then(yt_tools => { this.variable_mesh = new yt_tools.VariableMesh( @@ -46,7 +56,7 @@ export class VariableMeshModel extends DOMWidgetModel { this.get('pdy'), this.get('val') ); - }) + }); } static serializers: ISerializers = { @@ -55,9 +65,8 @@ export class VariableMeshModel extends DOMWidgetModel { pdx: { serialize: serializeArray, deserialize: deserializeArray }, py: { serialize: serializeArray, deserialize: deserializeArray }, pdy: { serialize: serializeArray, deserialize: deserializeArray }, - val: { serialize: serializeArray, deserialize: deserializeArray }, - - } + val: { serialize: serializeArray, deserialize: deserializeArray } + }; px: Float64Array; pdx: Float64Array; @@ -66,21 +75,20 @@ export class VariableMeshModel extends DOMWidgetModel { val: Float64Array; variable_mesh: VariableMesh; - static model_name = "VariableMeshModel"; + static model_name = 'VariableMeshModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; } -interface FRBViewBounds { - x_low: number, - x_high: number, - y_low: number, - y_high: number +interface IFRBViewBounds { + x_low: number; + x_high: number; + y_low: number; + y_high: number; } export class FRBModel extends DOMWidgetModel { - - defaults() { + defaults(): any { return { ...super.defaults(), _model_name: FRBModel.model_name, @@ -96,7 +104,7 @@ export class FRBModel extends DOMWidgetModel { }; } - initialize(attributes: any, options: any) { + initialize(attributes: any, options: any): void { super.initialize(attributes, options); this.on_some_change(['width', 'height'], this.sizeChanged, this); this.sizeChanged(); @@ -104,38 +112,44 @@ export class FRBModel extends DOMWidgetModel { static serializers: ISerializers = { ...DOMWidgetModel.serializers, variable_mesh_model: { deserialize: unpack_models } - } - + }; - sizeChanged() { + sizeChanged(): void { this.width = this.get('width'); this.height = this.get('height'); this.data_buffer = new Float64Array(this.width * this.height); } - calculateViewBounds(): FRBViewBounds { + calculateViewBounds(): IFRBViewBounds { this.view_width = this.get('view_width'); this.view_center = this.get('view_center'); - let hwidths: [number, number] = [ - this.view_width[0] / 2, this.view_width[1] / 2]; - let bounds = - { - x_low: this.view_center[0] - hwidths[0], - x_high: this.view_center[0] + hwidths[0], - y_low: this.view_center[1] - hwidths[1], - y_high: this.view_center[1] + hwidths[1] - }; + const hwidths: [number, number] = [ + this.view_width[0] / 2, + this.view_width[1] / 2 + ]; + const bounds = { + x_low: this.view_center[0] - hwidths[0], + x_high: this.view_center[0] + hwidths[0], + y_low: this.view_center[1] - hwidths[1], + y_high: this.view_center[1] + hwidths[1] + }; return bounds; } - async depositDataBuffer(variable_mesh_model: VariableMeshModel) { - let bounds: FRBViewBounds = this.calculateViewBounds(); - let yt_tools = await _yt_tools; + async depositDataBuffer( + variable_mesh_model: VariableMeshModel + ): Promise { + const bounds: IFRBViewBounds = this.calculateViewBounds(); + const yt_tools = await _yt_tools; this.frb = new yt_tools.FixedResolutionBuffer( - this.width, this.height, - bounds.x_low, bounds.x_high, bounds.y_low, bounds.y_high); - this.frb.deposit(variable_mesh_model.variable_mesh, - this.data_buffer); + this.width, + this.height, + bounds.x_low, + bounds.x_high, + bounds.y_low, + bounds.y_high + ); + this.frb.deposit(variable_mesh_model.variable_mesh, this.data_buffer); return this.data_buffer; } @@ -147,13 +161,13 @@ export class FRBModel extends DOMWidgetModel { view_center: [number, number]; view_width: [number, number]; - static model_name = "FRBModel" + static model_name = 'FRBModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; } export class WidgytsCanvasModel extends CanvasModel { - defaults() { + defaults(): any { return { ...super.defaults(), _model_name: WidgytsCanvasModel.model_name, @@ -165,7 +179,7 @@ export class WidgytsCanvasModel extends CanvasModel { min_val: undefined, max_val: undefined, is_log: true, - colormap_name: "viridis", + colormap_name: 'viridis', colormaps: null, frb_model: null, variable_mesh_model: null, @@ -173,22 +187,22 @@ export class WidgytsCanvasModel extends CanvasModel { image_data: undefined, _dirty_frb: false, _dirty_bitmap: false - } + }; } - initialize(attributes: any, options: any) { - super.initialize(attributes, options) + initialize(attributes: any, options: any): void { + super.initialize(attributes, options); this.frb_model = this.get('frb_model'); this.variable_mesh_model = this.get('variable_mesh_model'); this.colormaps = this.get('colormaps'); } static serializers: ISerializers = { - ...DOMWidgetModel.serializers, + ...CanvasModel.serializers, frb_model: { deserialize: unpack_models }, variable_mesh_model: { deserialize: unpack_models }, colormaps: { deserialize: unpack_models } - } + }; min_val: number; max_val: number; @@ -200,16 +214,16 @@ export class WidgytsCanvasModel extends CanvasModel { _dirty_frb: boolean; _dirty_bitmap: boolean; - static view_name = "WidgytsCanvasView"; + static view_name = 'WidgytsCanvasView'; static view_module = MODULE_NAME; static view_module_version = MODULE_VERSION; - static model_name = "WidgytsCanvasModel"; + static model_name = 'WidgytsCanvasModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; } export class WidgytsCanvasView extends CanvasView { - render() { + render(): void { /* This is where we update stuff! * Render in the base class will set up the ctx, but also calls * updateCanvas, so we need to check before calling anything in there. @@ -233,86 +247,110 @@ export class WidgytsCanvasView extends CanvasView { dragStartCenter: [number, number]; frbWidth: [number, number]; - setupEventListeners() { - this.model.frb_model.on_some_change(['width', 'height'], - this.resizeFromFRB, this); - this.model.frb_model.on_some_change(['view_center', 'view_width'], - this.dirtyFRB, this); - this.model.on_some_change(['_dirty_frb', '_dirty_bitmap'], - this.updateBitmap, this); - this.model.on_some_change(['min_val', 'max_val', 'colormap_name', - 'is_log'], this.dirtyBitmap, this); - this.canvas.addEventListener("wheel", this.conductZoom.bind(this)); - this.canvas.addEventListener("mousedown", this.startDrag.bind(this)); - this.canvas.addEventListener("mousemove", this.conductDrag.bind(this)); - window.addEventListener("mouseup", this.endDrag.bind(this)); + setupEventListeners(): void { + this.model.frb_model.on_some_change( + ['width', 'height'], + this.resizeFromFRB, + this + ); + this.model.frb_model.on_some_change( + ['view_center', 'view_width'], + this.dirtyFRB, + this + ); + this.model.on_some_change( + ['_dirty_frb', '_dirty_bitmap'], + this.updateBitmap, + this + ); + this.model.on_some_change( + ['min_val', 'max_val', 'colormap_name', 'is_log'], + this.dirtyBitmap, + this + ); + this.el.addEventListener('wheel', this.conductZoom.bind(this)); + this.el.addEventListener('mousedown', this.startDrag.bind(this)); + this.el.addEventListener('mousemove', this.conductDrag.bind(this)); + window.addEventListener('mouseup', this.endDrag.bind(this)); } - conductZoom(event: WheelEvent) { + conductZoom(event: WheelEvent): void { event.preventDefault(); - let view_width: [number, number] = this.model.frb_model.get("view_width"); - let n_units: number = 0; - if (event.deltaMode == event.DOM_DELTA_PIXEL) { + const view_width: [number, number] = this.model.frb_model.get('view_width'); + let n_units = 0; + if (event.deltaMode === event.DOM_DELTA_PIXEL) { // let's say we have 10 units per image n_units = event.deltaY / (this.frbWidth[1] / 10); - } else if (event.deltaMode == event.DOM_DELTA_LINE) { + } else if (event.deltaMode === event.DOM_DELTA_LINE) { // two lines per unit let's say n_units = event.deltaY / 2; - } else if (event.deltaMode == event.DOM_DELTA_PAGE) { + } else if (event.deltaMode === event.DOM_DELTA_PAGE) { // yeah i don't know return; } - let zoomFactor: number = 1.1 ** n_units; - let new_view_width: [number, number] = [view_width[0] * zoomFactor, view_width[1] * zoomFactor]; - this.model.frb_model.set("view_width", new_view_width); + const zoomFactor: number = 1.1 ** n_units; + const new_view_width: [number, number] = [ + view_width[0] * zoomFactor, + view_width[1] * zoomFactor + ]; + this.model.frb_model.set('view_width', new_view_width); this.model.frb_model.save_changes(); } - startDrag(event: MouseEvent) { + startDrag(event: MouseEvent): void { this.drag = true; this.dragStart = [event.offsetX, event.offsetY]; - this.dragStartCenter = this.model.frb_model.get("view_center"); + this.dragStartCenter = this.model.frb_model.get('view_center'); } - conductDrag(event: MouseEvent) { - if (!this.drag) return; - let shiftValue: [number, number] = [event.offsetX - this.dragStart[0], event.offsetY - this.dragStart[1]]; + conductDrag(event: MouseEvent): void { + if (!this.drag) { + return; + } + const shiftValue: [number, number] = [ + event.offsetX - this.dragStart[0], + event.offsetY - this.dragStart[1] + ]; // Now we shift the actual center - let view_width: [number, number] = this.model.frb_model.get("view_width"); - let dx = view_width[0] / this.frbWidth[0]; // note these are FRB dims, which are *pixel* dims, not display dims - let dy = view_width[1] / this.frbWidth[1] * -1; // origin is upper left, so flip dy - let new_view_center: [number, number] = [this.dragStartCenter[0] - dx * shiftValue[0], - this.dragStartCenter[1] - dy * shiftValue[1]]; - this.model.frb_model.set("view_center", new_view_center); + const view_width: [number, number] = this.model.frb_model.get('view_width'); + const dx = view_width[0] / this.frbWidth[0]; // note these are FRB dims, which are *pixel* dims, not display dims + const dy = (view_width[1] / this.frbWidth[1]) * -1; // origin is upper left, so flip dy + const new_view_center: [number, number] = [ + this.dragStartCenter[0] - dx * shiftValue[0], + this.dragStartCenter[1] - dy * shiftValue[1] + ]; + this.model.frb_model.set('view_center', new_view_center); } - endDrag(event: MouseEvent) { - if (!this.drag) return; + endDrag(event: MouseEvent): void { + if (!this.drag) { + return; + } this.drag = false; this.model.frb_model.save_changes(); } - dirtyBitmap() { + dirtyBitmap(): void { this.model.set('_dirty_bitmap', true); } - dirtyFRB() { + dirtyFRB(): void { this.model.set('_dirty_frb', true); } - async initializeArrays() { - this.regenerateBuffer(); // This will stick stuff into the FRB's data buffer - this.resizeFromFRB(); // This will create image_buffer and image_data + async initializeArrays(): Promise { + this.regenerateBuffer(); // This will stick stuff into the FRB's data buffer + this.resizeFromFRB(); // This will create image_buffer and image_data await this.createBitmap(); // This creates a bitmap array and normalizes } - updateCanvas() { - /* + updateCanvas(): void { + /* * We don't call super.updateCanvas here, and we just re-do what it does. * This means we'll have to update it when the base class changes, but it * also means greater control. */ - this.clear() + this.clear(); if (this.image_bitmap !== undefined) { //console.log("Drawing this.image_bitmap"); this.ctx.drawImage(this.image_bitmap, 0, 0); @@ -323,8 +361,10 @@ export class WidgytsCanvasView extends CanvasView { } } - async updateBitmap() { - if (this.locked) return; + async updateBitmap(): Promise { + if (this.locked) { + return; + } //console.log("Locking."); this.locked = true; //console.log("Update bitmap"); @@ -341,41 +381,45 @@ export class WidgytsCanvasView extends CanvasView { this.locked = false; } - resizeFromFRB() { + resizeFromFRB(): void { //console.log("resizeFromFRB"); if (this.model.frb_model !== null && this.ctx !== null) { //console.log("frb initialized; creating new clamped array and image"); - let width = this.model.frb_model.get('width'); - let height = this.model.frb_model.get('height'); + const width = this.model.frb_model.get('width'); + const height = this.model.frb_model.get('height'); this.frbWidth = [width, height]; - let npix = width * height; + const npix = width * height; // Times four so that we have one for *each* channel :) this.image_buffer = new Uint8ClampedArray(npix * 4); - this.image_data = this.ctx.createImageData(width, height) + this.image_data = this.ctx.createImageData(width, height); } } - regenerateBuffer() { + regenerateBuffer(): void { //console.log("regenerateBuffer"); this.model.frb_model.depositDataBuffer(this.model.variable_mesh_model); this.model.set('_dirty_frb', false); this.model.set('_dirty_bitmap', true); } - async createBitmap() { - /* + async createBitmap(): Promise { + /* * This needs to make sure our deposition is up to date, * normalize it, and then re-set our image data - */ + */ /* Need to normalize here somehow */ //console.log("Creating bitmap."); - await this.model.colormaps.normalize(this.model.get('colormap_name'), - this.model.frb_model.data_buffer, this.image_buffer, - this.model.get('min_val'), this.model.get('max_val'), - this.model.get('is_log')); + await this.model.colormaps.normalize( + this.model.get('colormap_name'), + this.model.frb_model.data_buffer, + this.image_buffer, + this.model.get('min_val'), + this.model.get('max_val'), + this.model.get('is_log') + ); this.image_data.data.set(this.image_buffer); - let nx = this.model.frb_model.get('width'); - let ny = this.model.frb_model.get('height'); + const nx = this.model.frb_model.get('width'); + const ny = this.model.frb_model.get('height'); /* This has to be called every time image_data changes */ this.image_bitmap = await createImageBitmap(this.image_data, 0, 0, nx, ny); this.model.set('_dirty_bitmap', false); @@ -384,48 +428,61 @@ export class WidgytsCanvasView extends CanvasView { } export class ColormapContainerModel extends WidgetModel { - defaults() { + defaults(): any { return { ...super.defaults(), colormap_values: {}, _initialized: false, _model_name: ColormapContainerModel.model_name, _model_module: ColormapContainerModel.model_module, - _model_module_version: ColormapContainerModel.model_module_version, - } + _model_module_version: ColormapContainerModel.model_module_version + }; } - initialize(attributes: any, options: any) { + initialize(attributes: any, options: any): void { super.initialize(attributes, options); this.colormap_values = this.get('colormap_values'); } - async normalize(colormap_name: string, data_array: Float64Array, - output_array: Uint8ClampedArray, min_val: number, max_val: number, - take_log: boolean) { + async normalize( + colormap_name: string, + data_array: Float64Array, + output_array: Uint8ClampedArray, + min_val: number, + max_val: number, + take_log: boolean + ): Promise { if (!this._initialized) { await this.setupColormaps(); } - let unclamped: Uint8Array = new Uint8Array(output_array.buffer); - this.colormaps.normalize(colormap_name, data_array, - unclamped, min_val, max_val, take_log); + const unclamped: Uint8Array = new Uint8Array(output_array.buffer); + this.colormaps.normalize( + colormap_name, + data_array, + unclamped, + min_val, + max_val, + take_log + ); } - private async setupColormaps() { - if (this._initialized) return; - let yt_tools = await _yt_tools; + private async setupColormaps(): Promise { + if (this._initialized) { + return; + } + const yt_tools = await _yt_tools; this.colormaps = new yt_tools.ColormapCollection(); - for (let [name, values] of Object.entries(this.colormap_values)) { - let arr_values: Uint8Array = Uint8Array.from(values); + for (const [name, values] of Object.entries(this.colormap_values)) { + const arr_values: Uint8Array = Uint8Array.from(values); this.colormaps.add_colormap(name, arr_values); } this._initialized = true; } - colormap_values: Object; + colormap_values: unknown; colormaps: ColormapCollection; _initialized: boolean; - static model_name = "ColormapContainerModel"; + static model_name = 'ColormapContainerModel'; static model_module = MODULE_NAME; static model_module_version = MODULE_VERSION; } diff --git a/style/base.css b/style/base.css new file mode 100644 index 0000000..e69de29 diff --git a/style/index.css b/style/index.css index e69de29..8a7ea29 100644 --- a/style/index.css +++ b/style/index.css @@ -0,0 +1 @@ +@import url('base.css'); diff --git a/style/index.js b/style/index.js new file mode 100644 index 0000000..a028a76 --- /dev/null +++ b/style/index.js @@ -0,0 +1 @@ +import './base.css'; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..7f6473e --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,6 @@ +module.exports = { + experiments: { + topLevelAwait: true, + asyncWebAssembly: true, + } +}; diff --git a/widgyts/__init__.py b/widgyts/__init__.py index 4287876..5406249 100644 --- a/widgyts/__init__.py +++ b/widgyts/__init__.py @@ -1,4 +1,8 @@ -__version__ = "0.4.0dev0" +import json +from pathlib import Path + +from ._version import __version__ + EXTENSION_VERSION = "~" + __version__ from .dataset_viewer import ( @@ -9,28 +13,25 @@ ) from .image_canvas import * +HERE = Path(__file__).parent.resolve() + +with (HERE / "labextension" / "package.json").open() as fid: + data = json.load(fid) + -def _jupyter_nbextension_paths(): - # Not sure we need this anymore - return [ - { - "section": "notebook", - "src": "static", - "dest": "yt-widgets", - "require": "yt-widgets/extension", - } - ] +def _jupyter_labextension_paths(): + return [{"src": "labextension", "dest": data["name"]}] -def _jupyter_server_extension_paths(): +def _jupyter_server_extension_points(): return [{"module": "widgyts"}] -def load_jupyter_server_extension(lab_app): +def _load_jupyter_server_extension(server_app): """ Just add to mimetypes. """ import mimetypes mimetypes.add_type("application/wasm", ".wasm") - lab_app.log.info("Registered application/wasm MIME type") + server_app.log.info("Registered application/wasm MIME type") diff --git a/widgyts/_version.py b/widgyts/_version.py new file mode 100644 index 0000000..d0bfd88 --- /dev/null +++ b/widgyts/_version.py @@ -0,0 +1,20 @@ +import json +from pathlib import Path + +__all__ = ["__version__"] + + +def _fetchVersion(): + HERE = Path(__file__).parent.resolve() + + for settings in HERE.rglob("package.json"): + try: + with settings.open() as f: + return json.load(f)["version"] + except FileNotFoundError: + pass + + raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}") + + +__version__ = _fetchVersion() diff --git a/widgyts/dataset_viewer.py b/widgyts/dataset_viewer.py index d51a23b..586df79 100644 --- a/widgyts/dataset_viewer.py +++ b/widgyts/dataset_viewer.py @@ -129,7 +129,7 @@ def _r2_falloff_default(self): def _colormap_texture_default(self): viridis = mcm.get_cmap("viridis") values = (viridis(np.mgrid[0.0:1.0:256j]) * 255).astype("u1") - values = np.stack([values[:, :],] * 256, axis=1).copy(order="C") + values = np.stack([values[:, :],] * 256, axis=1,).copy(order="C") colormap_texture = pythreejs.BaseDataTexture(data=values) return colormap_texture