From 4e52907b8ed79cc08d8c9ce1b823b72f026a4004 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Wed, 5 Jul 2023 16:40:45 -0400 Subject: [PATCH 01/25] fix: fix duplicated data and odd tile load timing in the bam data fetcher (#927) * fix: fix duplicated data in bam data fetcher and odd triggering timing * chore: format --- src/data-fetchers/bam/bam-worker.ts | 9 ++++++++- src/gosling-track/gosling-track.ts | 30 +++++++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/data-fetchers/bam/bam-worker.ts b/src/data-fetchers/bam/bam-worker.ts index 0fc4e6f72..221086e02 100644 --- a/src/data-fetchers/bam/bam-worker.ts +++ b/src/data-fetchers/bam/bam-worker.ts @@ -333,12 +333,13 @@ const tile = async (uid: string, z: number, x: number): Promise // pairAcrossChr: typeof loadMates === 'undefined' ? false : loadMates, }; + tileValues.set(`${uid}.${z}.${x}`, []); + /* eslint-disable-next-line @typescript-eslint/prefer-for-of */ for (let i = 0; i < cumPositions.length; i++) { const chromName = cumPositions[i].chr; const chromStart = cumPositions[i].pos; const chromEnd = cumPositions[i].pos + chromLengths[chromName]; - tileValues.set(`${uid}.${z}.${x}`, []); if (chromStart <= minX && minX < chromEnd) { // start of the visible region is within this chromosome @@ -390,6 +391,12 @@ const tile = async (uid: string, z: number, x: number): Promise }); }; +/** + * Not like other data fetchers, the Bam Data Fetcher fetches all the tiles at once. + * @param uid + * @param tileIds + * @returns + */ const fetchTilesDebounced = async (uid: string, tileIds: string[]) => { const tiles: Record = {}; const validTileIds: string[] = []; diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 5207430ae..738d53974 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -269,8 +269,12 @@ const factory: PluginTrackFactory = (HGC, context, op this.pMouseHover?.clear(); const processTilesAndDraw = () => { + // Should we force to process all tiles? + // For BAM, yes, since all tiles are stored in a single tile and visible tiles had been changed. + const isBamDataFetcher = this.dataFetcher instanceof BamDataFetcher; + // Preprocess all tiles at once so that we can share scales across tiles. - this.processAllTiles(); + this.processAllTiles(isBamDataFetcher); // This function calls `drawTile` on each tile. super.draw(); @@ -490,17 +494,19 @@ const factory: PluginTrackFactory = (HGC, context, op async updateTileAsync(tabularDataFetcher: TabularDataFetcher, callback: () => void) { if (!this.tilesetInfo) return; - const tabularData = await tabularDataFetcher.getTabularData( - Object.values(this.fetchedTiles).map(x => x.remoteId) - ); const tiles = this.visibleAndFetchedTiles(); - if (tiles?.[0]) { - const tile = tiles[0]; - const [refTile] = HGC.utils.trackUtils.calculate1DVisibleTiles(this.tilesetInfo, this._xScale); - tile.tileData.zoomLevel = refTile[0]; - tile.tileData.tilePos = [refTile[1], refTile[1]]; - (tile.tileData as TabularTileData).tabularData = tabularData; - } + const tabularData = await tabularDataFetcher.getTabularData(Object.values(tiles).map(x => x.remoteId)); + const tilesetInfo = this.tilesetInfo; + tiles.forEach((tile, i) => { + if (i === 0) { + const [refTile] = HGC.utils.trackUtils.calculate1DVisibleTiles(tilesetInfo, this._xScale); + tile.tileData.zoomLevel = refTile[0]; + tile.tileData.tilePos = [refTile[1], refTile[1]]; + (tile.tileData as TabularTileData).tabularData = tabularData; + } else { + (tile.tileData as TabularTileData).tabularData = []; + } + }); callback(); } @@ -765,7 +771,7 @@ const factory: PluginTrackFactory = (HGC, context, op }; // BAM data fetcher already combines the datasets; const isBamDataFetcher = this.dataFetcher instanceof BamDataFetcher; - return (includesDisplaceTransform && !hasDenseTiles()) || isBamDataFetcher; + return includesDisplaceTransform && !hasDenseTiles() && !isBamDataFetcher; } /** From b61cb2996ae30cec659777f18f77c2213959cd5a Mon Sep 17 00:00:00 2001 From: sehilyi Date: Wed, 5 Jul 2023 16:41:10 -0400 Subject: [PATCH 02/25] v0.9.32 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2146bbb..4fd870134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.9.32](https://github.com/gosling-lang/gosling.js/compare/v0.9.31...v0.9.32) (2023-07-05) + + +### Bug Fixes + +* fix duplicated data and odd tile load timing in the bam data fetcher ([#927](https://github.com/gosling-lang/gosling.js/issues/927)) ([4e52907](https://github.com/gosling-lang/gosling.js/commit/4e52907b8ed79cc08d8c9ce1b823b72f026a4004)) + + + ## [0.9.31](https://github.com/gosling-lang/gosling.js/compare/v0.9.30...v0.9.31) (2023-05-31) diff --git a/package.json b/package.json index 632ab7cb6..a45a4ceab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gosling.js", "author": "Sehi L'Yi", - "version": "0.9.31", + "version": "0.9.32", "license": "MIT", "repository": { "type": "git", From afcf002654ee59fdbcada474458eacd56d264f71 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Thu, 6 Jul 2023 10:30:30 -0400 Subject: [PATCH 03/25] docs: update `CONTRIBUTING.md` for the instruction to bump version (#928) docs: update CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fdb5b4928..e514c315b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,3 +120,12 @@ To learn more about the commitlint, please visit [conventional-changelog/commitl ## Opening Pull Requests We use the [commitlint](#commitlint) for the title of PR. So, if the title of PR is not following the commitlint conventions, [Semantic Pull Request](https://github.com/zeke/semantic-pull-requests) will complain about it, disallowing your PR to be merged. When your PR is accepted and merged into the master branch, the title of the PR will be recorded as a single commit message which will then added as a single item in [CHANGELOG.md](/CHANGELOG.md). + +## Bumping Gosling.js + +GitHub Action handles bumping the version of Gosling.js. The pattern looks like the following: + +``` +yarn version --patch +git push origin master --tags +``` From c94ecaea5ac010e17603816c6bf01c2d2b2a086c Mon Sep 17 00:00:00 2001 From: etowahadams Date: Fri, 7 Jul 2023 16:08:16 -0400 Subject: [PATCH 04/25] fix(data-fetcher): support genomicFieldsToConvert (#932) Adds support for genomicFieldsToConvert in JSON data fetcher --- .../json/json-data-fetcher.test.ts | 67 +++++++++++++++++++ src/data-fetchers/json/json-data-fetcher.ts | 50 +++++++------- 2 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/data-fetchers/json/json-data-fetcher.test.ts b/src/data-fetchers/json/json-data-fetcher.test.ts index 2173e8e66..642fe595a 100644 --- a/src/data-fetchers/json/json-data-fetcher.test.ts +++ b/src/data-fetchers/json/json-data-fetcher.test.ts @@ -42,3 +42,70 @@ describe('JSON data fetcher', () => { ); })); }); + +describe('JSON data fetcher', () => { + const fetcher = new (JsonDataFetcher as any)( + {}, + { + type: 'json', + genomicFieldsToConvert: [ + { + chromosomeField: 'chr1', + genomicFields: ['start1', 'end1'] + }, + { + chromosomeField: 'chr2', + genomicFields: ['start2', 'end2'] + } + ], + values: [ + { + chr1: 'chr1', + start1: 1221574, + end1: 1221575, + chr2: 'chr13', + start2: 36001515, + end2: 36001516 + }, + { + chr1: 'chr10', + start1: 9257519, + end1: 9257520, + chr2: 'chr18', + start2: 82441834, + end2: 82441835 + } + ] + } + ); + + it('converts genomic fields correctly', () => + new Promise(resolve => { + fetcher.fetchTilesDebounced( + (loadedTile: any) => { + expect(loadedTile['0.0'].tabularData).toMatchInlineSnapshot(` + [ + { + "chr1": "chr1", + "chr2": "chr13", + "end1": "1221575", + "end2": "2113044498", + "start1": "1221574", + "start2": "2113044497", + }, + { + "chr1": "chr10", + "chr2": "chr18", + "end1": "1684141149", + "end2": "2656479838", + "start1": "1684141148", + "start2": "2656479837", + }, + ] + `); + resolve(); + }, + ['0.0'] + ); + })); +}); diff --git a/src/data-fetchers/json/json-data-fetcher.ts b/src/data-fetchers/json/json-data-fetcher.ts index f160dac12..8975ddd32 100644 --- a/src/data-fetchers/json/json-data-fetcher.ts +++ b/src/data-fetchers/json/json-data-fetcher.ts @@ -58,36 +58,32 @@ function JsonDataFetcher(HGC: any, ...args: any): any { chromLengths: chromosomeSizes }; - const { chromosomeField, genomicFields } = this.dataConfig; + const { chromosomeField, genomicFields, genomicFieldsToConvert } = this.dataConfig; this.values = dataConfig.values.map((row: any) => { - let successfullyGotChrInfo = true; - - if (genomicFields && chromosomeField) { - genomicFields.forEach((g: any) => { - if (!row[chromosomeField]) { - // TODO: - return; - } - try { - const chrName = sanitizeChrName(row[chromosomeField], this.assembly); - row[g] = computeChromSizes(this.assembly).interval[chrName][0] + +row[g]; - } catch (e) { - // genomic position did not parse properly - successfullyGotChrInfo = false; - // console.warn( - // '[Gosling Data Fetcher] Genomic position cannot be parsed correctly.', - // this.dataConfig.chromosomeField - // ); - } - }); - } - - if (!successfullyGotChrInfo) { - // store row only when chromosome information is correctly parsed + try { + if (genomicFieldsToConvert) { + // This spec is used when multiple chromosomes are stored in a single row + genomicFieldsToConvert.forEach(chromMap => { + const genomicFields = chromMap.genomicFields; + const chromName = sanitizeChrName(row[chromMap.chromosomeField], this.assembly) as string; + + genomicFields.forEach((positionCol: string) => { + const chromPosition = row[positionCol] as string; + row[positionCol] = String(this.chromSizes.chrToAbs(chromName, chromPosition)); + }); + }); + } else if (chromosomeField && genomicFields) { + genomicFields.forEach((positionCol: string) => { + const chromPosition = row[positionCol] as string; + const chromName = sanitizeChrName(row[chromosomeField], this.assembly) as string; + row[positionCol] = String(this.chromSizes.chrToAbs(chromName, chromPosition)); + }); + } + return row; + } catch { + // skip the rows that had errors in them return undefined; } - - return row; }); } From 3a40551f2d46189c35ad7d3d12d8ce2830a08ff4 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 10 Jul 2023 11:19:36 -0400 Subject: [PATCH 05/25] ci: specify allowed scopes in semantic-pull-request.yml (#933) * ci: specify allowed scopes in semantic-pull-request.yml * docs: update CONTRIBUTING.md * docs: change order and wording * docs: force scope and add core and track type scopes * chore: add disallowScope to semantic-pull-request.yml * docs: update CONTRIBUTING.md --------- Co-authored-by: etowahadams --- .github/workflows/semantic-pull-request.yml | 29 ++++++++++- CONTRIBUTING.md | 57 ++++++++++++--------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 339859494..d22ad0add 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -14,4 +14,31 @@ jobs: steps: - uses: amannn/action-semantic-pull-request@v5 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed (newline-delimited). + types: | + feat + fix + ci + chore + docs + refactor + test + # Configure which scopes are allowed. + scopes: | + core + track + data-fetcher + api + editor + # Configure if a scope must always be provided. + requireScope: true + # Configure which scopes are disallowed in PR titles (newline-delimited). + # Anything but `feat` and `fix` + disallowScopes: | + ci + chore + docs + refactor + test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e514c315b..c1fc293f8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,39 @@ yarn start Then, you can open http://localhost:3000/ in a web browser to test the online editor. +## Commit Messages + +We use [commitlint](https://github.com/conventional-changelog/commitlint#what-is-commitlint) to maintain commit messages in a consistent manner and automatically update a [CHANGELOG.md](/CHANGELOG.md) based on the commit messages. + +The allowed pattern of commit messages is: + +```sh +type(scope?): subject # scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",") +``` + +where `type` can be either `feat`, `fix`, `ci`, `chore`, `docs`, `perf`, `refactor`, or `test`. + +Additionally, `scope` should be defined for `feat` and `fix` which should be one of the following: + +- `core`: any general updates to the library, e.g., grammar change, new rendering, etc. +- `track`: any updates that are specific to tracks in the library, including `gosling-track` and `gosling-axis`. +- `data-fetcher`: any updates related to data fetchers +- `editor`: UI and other updates to the online editor +- `api`: the Gosling APIs + +Example commit messages are as follows: + +```sh +git commit -m 'fix(core): correctly position views' +git commit -m 'feat(editor): add a data preview panel in editor' +git commit -m 'docs: add details about commitlint in README.md' +``` + +To learn more about the commitlint, please visit [conventional-changelog/commitlint](https://github.com/conventional-changelog/commitlint#what-is-commitlint). + +## Opening Pull Requests +We use the [commitlint](#commitlint) for the title of PR. So, if the title of PR is not following the commitlint conventions, [Semantic Pull Request](https://github.com/zeke/semantic-pull-requests) will complain about it, disallowing your PR to be merged. When your PR is accepted and merged into the master branch, the title of the PR will be recorded as a single commit message which will then added as a single item in [CHANGELOG.md](/CHANGELOG.md). + ## Testing Production Build Using Editor It is sometimes necessary to test the production build of Gosling.js. This frequently happened to us when we needed to ensure that certain data fetchers, like BAM and VCF, work correctly without errors in a deployed app. @@ -96,30 +129,6 @@ If there is an example you would like to add to the editor example library, plea 5. Select the example in the editor to make sure your example works as expected. -## Commit Messages - -We use [commitlint](https://github.com/conventional-changelog/commitlint#what-is-commitlint) to maintain commit messages in a consistent manner and automatically update a [CHANGELOG.md](/CHANGELOG.md) based on the commit messages. - -The allowed pattern of commit messages is: - -```sh -type(scope?): subject # scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",") -``` - -where `type` can be either `build`, `ci`, `chore`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, or `test`. - -Example commit messages are as follows: - -```sh -git commit -m 'fix: correctly position views' -git commit -m 'feat: add a data preview panel in editor' -git commit -m 'docs: add details about commitlint in README.md' -``` - -To learn more about the commitlint, please visit [conventional-changelog/commitlint](https://github.com/conventional-changelog/commitlint#what-is-commitlint). - -## Opening Pull Requests -We use the [commitlint](#commitlint) for the title of PR. So, if the title of PR is not following the commitlint conventions, [Semantic Pull Request](https://github.com/zeke/semantic-pull-requests) will complain about it, disallowing your PR to be merged. When your PR is accepted and merged into the master branch, the title of the PR will be recorded as a single commit message which will then added as a single item in [CHANGELOG.md](/CHANGELOG.md). ## Bumping Gosling.js From 9eb7a7bf458c2862996b5c558f1f93a7e74066f6 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 10 Jul 2023 13:20:23 -0400 Subject: [PATCH 06/25] docs: update CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1fc293f8..9af223d21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,6 +57,7 @@ To test this, you need to build the project first and then preview the productio ```sh yarn build +yarn build-editor yarn preview ``` From d9012326f4eadbf0c50f3c70424951c4735dcb69 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Tue, 11 Jul 2023 15:08:09 -0400 Subject: [PATCH 07/25] fix(data-fetcher): Bump tabix-js version to fix errors when loading BED files (#938) fix(datafetcher): bump tabix version --- yarn.lock | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index fdad1aa70..25dd7ef4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -334,7 +334,17 @@ resolved "https://registry.yarnpkg.com/@gmod/bed/-/bed-2.1.2.tgz#02e27a3a75269dec06ecc22f0ff7eb10b8affc42" integrity sha512-LnCmA+jb0xfbSWO7isi1dVvqbQi8Icqaj8FeUcnCc8t4jiNe1eFoe1YU8Chn7a8EDGFqS06kMNpO1vodO+q4IA== -"@gmod/bgzf-filehandle@^1.3.3", "@gmod/bgzf-filehandle@^1.4.4": +"@gmod/bgzf-filehandle@^1.3.3": + version "1.4.6" + resolved "https://registry.yarnpkg.com/@gmod/bgzf-filehandle/-/bgzf-filehandle-1.4.6.tgz#151947506e0319f78ffd2377e15f886ece59f4a1" + integrity sha512-WFFUv0QmCrUxPWvLf+NmILS9n6dMTRJL0xsHjzWEe7jiJc/AJFo3siBCdmWVioJysjLaI9zw6uPiqThClfexdA== + dependencies: + es6-promisify "^7.0.0" + generic-filehandle "^3.0.0" + long "^5.1.0" + pako "^1.0.11" + +"@gmod/bgzf-filehandle@^1.4.4": version "1.4.5" resolved "https://registry.yarnpkg.com/@gmod/bgzf-filehandle/-/bgzf-filehandle-1.4.5.tgz#a4749bff7b52cb3dd3f39d03e3a5a8a3853ef946" integrity sha512-33HAh5PGxT15XfB3n/ZvUXxkghZ/+aPAcfYn7oaueyObQsPmHU6IS0MzYnOQiQANyXDW1GpJc/lCB5DhHEmhtg== @@ -345,9 +355,9 @@ pako "^1.0.11" "@gmod/tabix@^1.5.6": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@gmod/tabix/-/tabix-1.5.10.tgz#d4ada7789686f1e229897b9a53049af9592bbc75" - integrity sha512-klKSlG2c/JkPatXj7Nj+u/vEQSOMgwXgB+e5pDxuQiD7x2DojnpR1CP+Sacl09OaRt0x1DEQrxqc+hJoCtg7mg== + version "1.5.11" + resolved "https://registry.yarnpkg.com/@gmod/tabix/-/tabix-1.5.11.tgz#c7fb06a20d2048a487d2cf65d0d360cc2111ac96" + integrity sha512-zYgivq8r9pSwNKbzAqHSSfRQ3KEe+oiTNhABJIW9kBqjiKW4m7ncqKhhcmxjG4afhJNUzBJ9Gt/w3grbG+Q5bA== dependencies: "@gmod/bgzf-filehandle" "^1.3.3" abortable-promise-cache "^1.4.1" From c00671aa84a0cff82a06ee72b53222156590995e Mon Sep 17 00:00:00 2001 From: sehilyi Date: Tue, 11 Jul 2023 15:09:16 -0400 Subject: [PATCH 08/25] v0.9.33 --- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd870134..10bdef48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [0.9.33](https://github.com/gosling-lang/gosling.js/compare/v0.9.32...v0.9.33) (2023-07-11) + + +### Bug Fixes + +* **data-fetcher:** Bump tabix-js version to fix errors when loading BED files ([#938](https://github.com/gosling-lang/gosling.js/issues/938)) ([d901232](https://github.com/gosling-lang/gosling.js/commit/d9012326f4eadbf0c50f3c70424951c4735dcb69)) +* **data-fetcher:** support genomicFieldsToConvert ([#932](https://github.com/gosling-lang/gosling.js/issues/932)) ([c94ecae](https://github.com/gosling-lang/gosling.js/commit/c94ecaea5ac010e17603816c6bf01c2d2b2a086c)) + + + ## [0.9.32](https://github.com/gosling-lang/gosling.js/compare/v0.9.31...v0.9.32) (2023-07-05) diff --git a/package.json b/package.json index a45a4ceab..776e6fe84 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gosling.js", "author": "Sehi L'Yi", - "version": "0.9.32", + "version": "0.9.33", "license": "MIT", "repository": { "type": "git", From 526882fb82463ad4262568d9569840431c709fc9 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 13 Jul 2023 11:15:07 -0400 Subject: [PATCH 09/25] feat(data-fetcher): GFF3 with tabix (#923) * feat(data-fetcher): GFF3 with tabix * docs(data-fetcher): add GFF3 example --------- Co-authored-by: Sehi L'Yi --- editor/example/doc-examples/gff-demo.ts | 96 ++++++ editor/example/doc-examples/index.ts | 1 + editor/example/index.ts | 6 + package.json | 2 + schema/gosling.schema.json | 51 +++ src/core/gosling-to-higlass.ts | 1 + src/core/gosling.schema.guards.ts | 1 + src/core/gosling.schema.ts | 32 +- src/data-fetchers/gff/gff-data-fetcher.ts | 74 +++++ src/data-fetchers/gff/gff-worker.ts | 359 ++++++++++++++++++++++ src/data-fetchers/gff/utils.ts | 20 ++ src/data-fetchers/index.ts | 1 + yarn.lock | 24 +- 13 files changed, 665 insertions(+), 3 deletions(-) create mode 100644 editor/example/doc-examples/gff-demo.ts create mode 100644 src/data-fetchers/gff/gff-data-fetcher.ts create mode 100644 src/data-fetchers/gff/gff-worker.ts create mode 100644 src/data-fetchers/gff/utils.ts diff --git a/editor/example/doc-examples/gff-demo.ts b/editor/example/doc-examples/gff-demo.ts new file mode 100644 index 000000000..469ad48a1 --- /dev/null +++ b/editor/example/doc-examples/gff-demo.ts @@ -0,0 +1,96 @@ +import type { GoslingSpec } from 'gosling.js'; + +export const GFF_DEMO: GoslingSpec = { + title: 'GFF3 file', + subtitle: 'E. coli genome, colored by gene type.', + spacing: 0, + layout: 'linear', + assembly: [['U00096.3', 4641652]], + style: { enableSmoothPath: true }, + views: [ + { + xDomain: { chromosome: 'U00096.3', interval: [222000, 240000] }, + alignment: 'overlay', + data: { + url: 'https://s3.amazonaws.com/gosling-lang.org/data/gff/E_coli_MG1655.gff3.gz', + indexUrl: 'https://s3.amazonaws.com/gosling-lang.org/data/gff/E_coli_MG1655.gff3.gz.tbi', + type: 'gff', + attributesToFields: [ + { attribute: 'gene_biotype', defaultValue: 'unknown' }, + { attribute: 'Name', defaultValue: 'unknown' } + ] + }, + color: { + type: 'nominal', + field: 'gene_biotype', + domain: ['protein_coding', 'tRNA', 'rRNA', 'ncRNA', 'pseudogene', 'unknown'], + range: ['orange', 'blue', 'green', 'red', 'purple', 'black'] + }, + tracks: [ + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'triangleRight', + x: { field: 'end', type: 'genomic', axis: 'top' }, + size: { value: 10 } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'Name', type: 'nominal' }, + x: { field: 'start', type: 'genomic' }, + xe: { field: 'end', type: 'genomic' }, + style: { dy: -10 } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'triangleLeft', + x: { field: 'start', type: 'genomic' }, + size: { value: 10 }, + style: { align: 'right' } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic' }, + strokeWidth: { value: 3 }, + xe: { field: 'end', type: 'genomic' }, + style: { linePattern: { type: 'triangleRight', size: 5 } } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic' }, + strokeWidth: { value: 3 }, + xe: { field: 'end', type: 'genomic' }, + style: { linePattern: { type: 'triangleLeft', size: 5 } } + } + ], + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + opacity: { value: 0.8 }, + width: 800, + height: 80 + } + ] +}; diff --git a/editor/example/doc-examples/index.ts b/editor/example/doc-examples/index.ts index 1cfb5164c..a2a7d5455 100644 --- a/editor/example/doc-examples/index.ts +++ b/editor/example/doc-examples/index.ts @@ -14,3 +14,4 @@ export { TRIANGLE } from './triangle'; export { VCF_INDELS } from './vcf-indels'; export { VCF_POINT_MUTATIONS } from './vcf-point-mutations'; export { BED_DEMO } from './bed-demo'; +export { GFF_DEMO } from './gff-demo'; diff --git a/editor/example/index.ts b/editor/example/index.ts index c7a07c858..9a937cba3 100644 --- a/editor/example/index.ts +++ b/editor/example/index.ts @@ -236,6 +236,12 @@ const docExampleObj: { name: 'BED file', spec: docExamples.BED_DEMO, hidden: true + }, + doc_gff: { + group: 'Doc', + name: 'GFF file', + spec: docExamples.GFF_DEMO, + hidden: true } }; diff --git a/package.json b/package.json index 776e6fe84..2a435460f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "dependencies": { "@gmod/bam": "^1.1.18", "@gmod/bbi": "^3.0.1", + "@gmod/gff": "^1.3.0", "@gmod/bed": "^2.1.2", "@gmod/tabix": "^1.5.6", "@gmod/vcf": "^5.0.10", @@ -64,6 +65,7 @@ "d3-scale": "^3.2.1", "d3-scale-chromatic": "^2.0.0", "d3-shape": "^2.0.0", + "events": "^3.3.0", "fflate": "^0.7.1", "generic-filehandle": "^3.0.1", "gosling-theme": "^0.0.10", diff --git a/schema/gosling.schema.json b/schema/gosling.schema.json index cb352d856..b5d017cfe 100644 --- a/schema/gosling.schema.json +++ b/schema/gosling.schema.json @@ -553,6 +553,9 @@ }, { "$ref": "#/definitions/VcfData" + }, + { + "$ref": "#/definitions/GffData" } ] }, @@ -1035,6 +1038,54 @@ ], "type": "object" }, + "GffData": { + "additionalProperties": false, + "description": "Generic Feature Format Version 3 (GFF3) format data. It parses files that follow the [GFF3 specification](https://github.com/The-Sequence-Ontology/Specifications/blob/master/gff3.md).", + "properties": { + "attributesToFields": { + "description": "Specifies which attributes to include as a fields. GFF files have an \"attributes\" column which contains a list of attributes which are each tag-value pairs (`tag=value`). This option allows for specific attributes to be accessible as a field. For example, if you have an attribute called \"gene_name\" and you want label features on your track using those values, you can use this option so that you can use `\"field\": \"gene_name\"` in the schema.\n\nIf there is a single `value` corresponding to the `tag`, Gosling will parse that value as a string. If there are multiple `value`s corresponding to a `tag`, Gosling will parse it as a comma-separated list string. If a feature does not have a particular attribute, then the attribute value will be set to the `defaultValue`.", + "items": { + "additionalProperties": false, + "properties": { + "attribute": { + "type": "string" + }, + "defaultValue": { + "type": "string" + } + }, + "required": [ + "attribute", + "defaultValue" + ], + "type": "object" + }, + "type": "array" + }, + "indexUrl": { + "description": "URL link to the tabix index file", + "type": "string" + }, + "sampleLength": { + "description": "The maximum number of samples to be shown on the track. Samples are uniformly randomly selected so that this threshold is not exceeded. __Default:__ `1000`", + "type": "number" + }, + "type": { + "const": "gff", + "type": "string" + }, + "url": { + "description": "URL link to the GFF file", + "type": "string" + } + }, + "required": [ + "type", + "url", + "indexUrl" + ], + "type": "object" + }, "GoslingSpec": { "anyOf": [ { diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 4f1d17625..b3b605669 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -117,6 +117,7 @@ export function goslingToHiGlass( firstResolvedSpec.data.type === 'bigwig' || firstResolvedSpec.data.type === 'bam' || firstResolvedSpec.data.type === 'vcf' || + firstResolvedSpec.data.type === 'gff' || firstResolvedSpec.data.type === 'bed') ) { const getFieldName = (c: 'x' | 'xe' | 'x1' | 'x1e') => { diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index b3acb75ef..034fa6b1d 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -182,6 +182,7 @@ export function IsDataDeepTileset( _.type === 'matrix' || _.type === 'bam' || _.type === 'vcf' || + _.type === 'gff' || _.type === 'bed') ); } diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 49823a128..82aed45b5 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -809,7 +809,8 @@ export type DataDeep = | VectorData | MatrixData | BamData - | VcfData; + | VcfData + | GffData; /** Values in the form of JSON. */ export interface Datum { @@ -1101,6 +1102,35 @@ export interface BamData { maxInsertSize?: number; // https://github.com/GMOD/bam-js#async-getrecordsforrangerefname-start-end-opts } +/** + * Generic Feature Format Version 3 (GFF3) format data. It parses files that follow the + * [GFF3 specification](https://github.com/The-Sequence-Ontology/Specifications/blob/master/gff3.md). + */ +export interface GffData { + type: 'gff'; + /** URL link to the GFF file */ + url: string; + + /** URL link to the tabix index file */ + indexUrl: string; + + /** The maximum number of samples to be shown on the track. Samples are uniformly randomly selected so that this + * threshold is not exceeded. __Default:__ `1000` */ + sampleLength?: number; + /** + * Specifies which attributes to include as a fields. + * GFF files have an "attributes" column which contains a list of attributes which are each tag-value pairs (`tag=value`). + * This option allows for specific attributes to be accessible as a field. For example, if you have an attribute called + * "gene_name" and you want label features on your track using those values, you can use this option so that you can use + * `"field": "gene_name"` in the schema. + * + * If there is a single `value` corresponding to the `tag`, Gosling will parse that value as a string. If there are + * multiple `value`s corresponding to a `tag`, Gosling will parse it as a comma-separated list string. If a feature + * does not have a particular attribute, then the attribute value will be set to the `defaultValue`. + */ + attributesToFields?: { attribute: string; defaultValue: string }[]; +} + /** * The Variant Call Format (VCF). */ diff --git a/src/data-fetchers/gff/gff-data-fetcher.ts b/src/data-fetchers/gff/gff-data-fetcher.ts new file mode 100644 index 000000000..09ade1333 --- /dev/null +++ b/src/data-fetchers/gff/gff-data-fetcher.ts @@ -0,0 +1,74 @@ +/* + * The GFF data fetcher is based heavily on the BED and VCF data fetchers + */ +import { spawn } from 'threads'; +import Worker from './gff-worker.ts?worker&inline'; + +import { computeChromSizes } from '../../core/utils/assembly'; + +import type { ModuleThread } from 'threads'; +import type { Assembly, GffData } from '../../core/gosling.schema'; +import type { WorkerApi, TilesetInfo, GffTile, EmptyTile } from './gff-worker'; +import type { TabularDataFetcher } from '../utils'; + +const DEBOUNCE_TIME = 200; + +export type GFFDataConfig = GffData & { assembly: Assembly }; + +class GffDataFetcher implements TabularDataFetcher { + static config = { type: 'gff' }; + dataConfig = {}; // required for higlass + uid: string; + prevRequestTime: number; + track?: any; + + private toFetch: Set; + private fetchTimeout?: ReturnType; + private worker: Promise>; + + constructor(HGC: import('@higlass/types').HGC, config: GFFDataConfig) { + this.uid = HGC.libraries.slugid.nice(); + this.prevRequestTime = 0; + this.toFetch = new Set(); + const { url, indexUrl, assembly, ...options } = config; + this.worker = spawn(new Worker()).then(async worker => { + const chromSizes = Object.entries(computeChromSizes(assembly).size); + await worker.init(this.uid, { url, indexUrl }, chromSizes, options); + return worker; + }); + } + + /* + * Collect Tileset Information, such as tile size and genomic positions + */ + async tilesetInfo(callback: (info: TilesetInfo) => void) { + (await this.worker).tilesetInfo(this.uid).then(callback); + } + + fetchTilesDebounced(receivedTiles: (tiles: Record) => void, tileIds: string[]) { + this.track.drawLoadingCue(); + + tileIds.forEach(tileId => this.toFetch.add(tileId)); + + if (this.fetchTimeout) { + clearTimeout(this.fetchTimeout); + } + + this.fetchTimeout = setTimeout(() => { + this.sendFetch(receivedTiles, [...this.toFetch]); + this.toFetch.clear(); + }, DEBOUNCE_TIME); + } + + async sendFetch(receivedTiles: (tiles: Record) => void, tileIds: string[]) { + (await this.worker).fetchTilesDebounced(this.uid, tileIds).then(receivedTiles); + } + + async getTabularData(tileIds: string[]): Promise { + const buf = await (await this.worker).getTabularData(this.uid, tileIds); + const parsed = JSON.parse(new TextDecoder().decode(buf)); + return parsed; + } +} + +export default GffDataFetcher; diff --git a/src/data-fetchers/gff/gff-worker.ts b/src/data-fetchers/gff/gff-worker.ts new file mode 100644 index 000000000..3824e84eb --- /dev/null +++ b/src/data-fetchers/gff/gff-worker.ts @@ -0,0 +1,359 @@ +import { TabixIndexedFile } from '@gmod/tabix'; +import GFF from '@gmod/gff'; +import type { GFF3FeatureLineWithRefs, GFF3Feature, GFF3Sequence } from '@gmod/gff'; +import { expose, Transfer } from 'threads/worker'; +import { sampleSize } from 'lodash-es'; +import type { TilesetInfo } from '@higlass/types'; +import type { ChromSizes } from '@gosling.schema'; +import { DataSource, RemoteFile } from '../utils'; +import { isGFF3Feature, makeRandomSortedArray } from './utils'; + +export type GffFileOptions = { + sampleLength: number; + attributesToFields?: { attribute: string; defaultValue: string }[]; +}; + +export interface GffTile extends GFF3FeatureLineWithRefs { + attribute?: string; +} + +export interface EmptyTile { + tilePositionId: string; +} + +/** + * A class to represent a GFF file. It takes care of setting up gmod/tabix. + */ +export class GffFile { + #uid: string; + tbi: TabixIndexedFile; + #n_features?: number; + + constructor(tbi: TabixIndexedFile, uid: string) { + this.tbi = tbi; + this.#uid = uid; + } + /** + * Function to create an instance of BedFile + * @param url A string which is the URL of the bed file + * @param indexUrl A string which is the URL of the bed index file + * @param uid A unique identifier for the worker + * @returns an instance of BedFile + */ + static fromUrl(url: string, indexUrl: string, uid: string) { + const tbi = new TabixIndexedFile({ + filehandle: new RemoteFile(url), + tbiFilehandle: new RemoteFile(indexUrl) + }); + return new GffFile(tbi, uid); + } + /** + * Calculates the total number of features in a file + * @returns A number of the features in a file + */ + async #getNumberFeatures() { + if (!this.#n_features) { + const source = dataSources.get(this.#uid)!; + const { cumPositions } = source.chromInfo; + const countPromises: Promise[] = []; + for (const cumPos of cumPositions) { + const chromName = cumPos.chr; + countPromises.push(this.tbi.lineCount(chromName)); + } + const counts = await Promise.all(countPromises); + const total_counts = counts.reduce((acc, cur) => acc + cur, 0); + this.#n_features = total_counts; + } + return this.#n_features; + } + /** + * Retrieves the lines between a range of absolute genomic coordinates + * @param minX the minimum absolute coordinate + * @param maxX the maximum absolute coordinate + * @returns A promise to an array of strings, where each string is a line from the GFF + */ + #getLinePromises(minX: number, maxX: number): Promise[] { + const source = dataSources.get(this.#uid)!; + let curMinX = minX; + const { chromLengths, cumPositions } = source.chromInfo; + const linePromises: Promise[] = []; + // This helps with the performance of viewing many features over a large genomic range + // Strategy: Get features from randomly selected windows within the entire region + // If the total number of features in the file is less than MAX_FEATURES, then we don't do this. + const MAX_FEATURES = 500000; // if the number of features is less than this, we just get all the lines + const MAX_WINDOW = 4000000; // if the maxX - minX is less than this, then we'll just get all the lines + if (this.#n_features && this.#n_features > MAX_FEATURES && maxX - minX > MAX_WINDOW) { + const diff = maxX - minX; + const windowSize = 50000; + const n_samples = 100; + + const totalBins = Math.floor(diff / windowSize); + const selectedBins = makeRandomSortedArray(n_samples, totalBins); + const binLines = selectedBins.map(bin => { + const binStart = minX + bin * windowSize; + const binEnd = binStart + windowSize; + return this.#getLinePromises(binStart, binEnd); + }); + return binLines.flat(); + } + + for (const cumPos of cumPositions) { + const chromName = cumPos.chr; + const chromStart = cumPos.pos; + const chromEnd = cumPos.pos + chromLengths[chromName]; + let startPos, endPos; + + // Early break, rather than creating an nested if + if (chromStart > curMinX || curMinX >= chromEnd) { + continue; + } + + // start of the visible region is within this chromosome + const linePromise = new Promise(resolve => { + const lines: string[] = []; + const lineCallback = (line: string) => { + lines.push(line); + }; + + if (maxX > chromEnd) { + startPos = curMinX - chromStart; + endPos = chromEnd - chromStart; + } else { + startPos = Math.floor(curMinX - chromStart); + endPos = Math.ceil(maxX - chromStart); + } + + this.tbi.getLines(chromName, startPos, endPos, lineCallback).then(() => { + resolve(lines); + }); + }); + + linePromises.push(linePromise); + + if (maxX <= chromEnd) { + continue; + } + + curMinX = chromEnd; + } + return linePromises; + } + /** + * Makes tiles for a given range of genomic positions + * @param minX the minimum absolute coordinate + * @param maxX the maximum absolute coordinate + * @returns A promise to an array of GffTiles + */ + async getTileData(minX: number, maxX: number): Promise { + const source = dataSources.get(this.#uid)!; + const sampleLength = source.options.sampleLength; // maximum number of features that can be shown + const attributesToFields = source.options.attributesToFields; + + await this.#getNumberFeatures(); + /** + * Function to filter out child features if there are too many features + * @param lines an array of strings, where each string is a line from the GFF + * @returns an array of strings, where each string is a line from the GFF + */ + function filterLines(lines: string[]): string[] { + // Keep only the non child lines + const nonChildLines = lines.filter(line => { + const lineColumns = line.split('\t'); + const attributes = lineColumns[8]; // this is the attributes column + return !attributes.includes('Parent='); + }); + return sampleSize(nonChildLines, sampleLength); + } + + /** + * Function to reformat the output of GFF.parseStringSync() into tiles + * @param parsed Output from GFF.parseStringSync() + * @returns An array of GffTile + */ + function parsedLinesToTiles(parsed: (GFF3Feature | GFF3Sequence)[]): GffTile[] { + let tiles: GffTile[] = []; + for (const line of parsed) { + if (isGFF3Feature(line)) { + for (const feature of line) { + // make the start and end absolute + if (feature.seq_id && feature.start) + feature.start = source.chromInfo.chrToAbs([feature.seq_id, feature.start]); + if (feature.seq_id && feature.end) + feature.end = source.chromInfo.chrToAbs([feature.seq_id, feature.end]); + tiles.push(feature); + } + } + } + // Add specific attributes to each tile if specified + if (attributesToFields) { + tiles = tiles.map(tile => { + const attributes = tile.attributes; + const cleanAtt: { [key: string]: unknown } = {}; + attributesToFields.forEach(attMap => { + const attName = attMap.attribute; + const attDefault = attMap.defaultValue; + if (attributes == null || !(attName in attributes) || !Array.isArray(attributes[attName])) + cleanAtt[attName] = attDefault; + else { + const attVal = attributes[attName]; + if (Array.isArray(attVal)) { + // Join the array array if there is more than one element + cleanAtt[attName] = attVal.length == 1 ? attVal[0] : attVal.join(','); + } + } + }); + return { ...tile, ...cleanAtt }; + }); + } + return tiles; + } + + // First, we get the lines we want to parse and filter out the lines we don't want to parse + const linePromises = this.#getLinePromises(minX, maxX); + const allLines = (await Promise.all(linePromises)).flat(); + let linesToParse = []; + if (allLines.length > sampleLength) { + linesToParse = filterLines(allLines); + } else { + linesToParse = allLines; + } + // Second, we parse the lines using gmod/gff + const parseOptions = { + disableDerivesFromReferences: true, + parseFeatures: true, + parseComments: false, + parseDirectives: false, + parseSequences: false + }; + const parsedLines = GFF.parseStringSync(linesToParse.join('\n'), parseOptions); + // Third, we reformat the parsed GFF into the expected tile format + const tiles = parsedLinesToTiles(parsedLines); + return tiles; + } +} + +// promises indexed by urls +const gffFiles: Map = new Map(); + +/** + * Object to store tile data. Each key a string which contains the coordinates of the tile + */ +const tileValues: Record = {}; +/** + * Maps from UID to GFF File info + */ +const dataSources: Map> = new Map(); + +function init( + uid: string, + bed: { url: string; indexUrl: string }, + chromSizes: ChromSizes, + options: Partial = {} +) { + let gffFile = gffFiles.get(bed.url); + if (!gffFile) { + gffFile = GffFile.fromUrl(bed.url, bed.indexUrl, uid); + } + const dataSource = new DataSource(gffFile, chromSizes, { + sampleLength: 1000, // default sampleLength + ...options + }); + dataSources.set(uid, dataSource); +} + +const tilesetInfo = (uid: string) => { + return dataSources.get(uid)!.tilesetInfo; +}; + +/** + * Updates `tileValues` with the data for a specific tile. + * @param uid A string which is the unique identifier of the worker + * @param z A number which is the z coordinate of the tile + * @param x A number which is the x coordinate of the tile + */ +const tile = async (uid: string, z: number, x: number): Promise => { + const source = dataSources.get(uid)!; + // const parseLine = await source.file.getParser(); + + const CACHE_KEY = `${uid}.${z}.${x}`; + + // TODO: Caching is needed + // if (!tileValues[CACHE_KEY]) { + tileValues[CACHE_KEY] = []; + // } + + const tileWidth = +source.tilesetInfo.max_width / 2 ** +z; + + // get bounds of this tile + const minX = source.tilesetInfo.min_pos[0] + x * tileWidth; + const maxX = source.tilesetInfo.min_pos[0] + (x + 1) * tileWidth; + tileValues[CACHE_KEY] = await source.file.getTileData(minX, maxX); + return []; +}; + +const fetchTilesDebounced = async (uid: string, tileIds: string[]) => { + const tiles: Record = {}; + const validTileIds: string[] = []; + const tilePromises: Promise[] = []; + + for (const tileId of tileIds) { + const parts = tileId.split('.'); + const z = parseInt(parts[0], 10); + const x = parseInt(parts[1], 10); + + if (Number.isNaN(x) || Number.isNaN(z)) { + console.warn('Invalid tile zoom or position:', z, x); + continue; + } + validTileIds.push(tileId); + tilePromises.push(tile(uid, z, x)); + } + return Promise.all(tilePromises).then(values => { + for (let i = 0; i < values.length; i++) { + const validTileId = validTileIds[i]; + tiles[validTileId] = { tilePositionId: validTileId }; + } + return tiles; + }); +}; + +/** + * Sends the data fetcher data from `tileValues` + * @param uid A string which is the unique identifier of the worker + * @param tileIds An array of strings where each string identifies a tile with a particular coordinate + * @returns A transferable buffer + */ +const getTabularData = (uid: string, tileIds: string[]) => { + const data: GffTile[][] = []; + + tileIds.forEach(tileId => { + const parts = tileId.split('.'); + const z = parseInt(parts[0], 10); + const x = parseInt(parts[1], 10); + + const tileValue = tileValues[`${uid}.${z}.${x}`]; + + if (!tileValue) { + console.warn(`No tile data constructed (${tileId})`); + } + + data.push(tileValue); + }); + const output = Object.values(data).flat(); + + const buffer = new TextEncoder().encode(JSON.stringify(output)).buffer; + return Transfer(buffer, [buffer]); +}; + +const tileFunctions = { + init, + tilesetInfo, + fetchTilesDebounced, + tile, + getTabularData +}; + +export type WorkerApi = typeof tileFunctions; +export type { TilesetInfo }; + +expose(tileFunctions); diff --git a/src/data-fetchers/gff/utils.ts b/src/data-fetchers/gff/utils.ts new file mode 100644 index 000000000..535747302 --- /dev/null +++ b/src/data-fetchers/gff/utils.ts @@ -0,0 +1,20 @@ +import type { GFF3Feature, GFF3Sequence } from '@gmod/gff'; +/** + * Type guard for GFF3Feature + * @param entry An element from GFF.parseStringSync() return value + * @returns True if type is GFF3Feature + */ +export function isGFF3Feature(entry: GFF3Feature | GFF3Sequence): entry is GFF3Feature { + return Array.isArray(entry as GFF3Feature); +} + +/** Returns an array of the unique values sampled n times between 0 and maxValue */ +export function makeRandomSortedArray(n: number, maxValue: number) { + const randomArray = []; + for (let i = 0; i < n; i++) { + const randomNumber = Math.floor(Math.random() * (maxValue + 1)); + randomArray.push(randomNumber); + } + const sorted = randomArray.sort((a, b) => a - b); + return [...new Set(sorted)]; +} \ No newline at end of file diff --git a/src/data-fetchers/index.ts b/src/data-fetchers/index.ts index 3b22b3faa..eda39a021 100644 --- a/src/data-fetchers/index.ts +++ b/src/data-fetchers/index.ts @@ -3,5 +3,6 @@ export { default as VcfDataFetcher } from './vcf/vcf-data-fetcher'; export { default as BigWigDataFetcher } from './bigwig/bigwig-data-fetcher'; export { default as CsvDataFetcher } from './csv/csv-data-fetcher'; export { default as JsonDataFetcher } from './json/json-data-fetcher'; +export { default as GffDataFetcher } from './gff/gff-data-fetcher'; export { default as BedDataFetcher } from './bed/bed-data-fetcher'; export { type TabularDataFetcher } from './utils'; diff --git a/yarn.lock b/yarn.lock index 25dd7ef4d..10af5afc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -354,6 +354,13 @@ long "^5.1.0" pako "^1.0.11" +"@gmod/gff@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@gmod/gff/-/gff-1.3.0.tgz#d231a6f807cfb8f191c26c6410021c17155e4093" + integrity sha512-OjEnQLR6iIcrau603blFfUkmnWGDVfOu/LQoJNa7TsvKnjWlHYPlvqb5h2IV7wI+zElDY648mQ9zrElt2uR80A== + dependencies: + stream-browserify "^3.0.0" + "@gmod/tabix@^1.5.6": version "1.5.11" resolved "https://registry.yarnpkg.com/@gmod/tabix/-/tabix-1.5.11.tgz#c7fb06a20d2048a487d2cf65d0d360cc2111ac96" @@ -3484,6 +3491,11 @@ eventemitter3@^5.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -4404,7 +4416,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6679,7 +6691,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2: +readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.5.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -7320,6 +7332,14 @@ stream-array@^1.1.2: dependencies: readable-stream "~2.1.0" +stream-browserify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-combiner2@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" From cd8d3000f7e1e86422ec89f765585f6056d64a9f Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Fri, 14 Jul 2023 12:36:07 -0400 Subject: [PATCH 10/25] feat(core, api, editor): support using view IDs (#939) * chore: working on view ids * chore: working on view ID * chore: use both track IDs and view IDs * chore: more comments * feat: ensure ID to all tracks as well, docs, tests * chore: lint * chore: enable scrolling * chore: no support for responsive tracks * chore: remove duplicate ID assignments * chore: format * chore: fix test --- editor/Editor.tsx | 56 +++++++++++++++++++++- schema/gosling.schema.json | 38 +++++++++++++++ schema/template.schema.json | 1 + src/core/api-data.ts | 45 ++++++++++++++++++ src/core/api.ts | 31 +++++++++--- src/core/compile.ts | 5 +- src/core/create-higlass-models.ts | 24 ++++++++-- src/core/gosling-component.tsx | 11 ++--- src/core/gosling-to-higlass.ts | 7 +-- src/core/gosling.schema.ts | 36 ++++++++++---- src/core/track-and-view-ids.test.ts | 74 +++++++++++++++++++++++++++++ src/core/track-and-view-ids.ts | 63 ++++++++++++++++++++++++ src/core/utils/bounding-box.ts | 14 ++---- src/core/utils/linking.test.ts | 1 + src/core/utils/spec-preprocess.ts | 10 ++++ src/gosling-track/gosling-track.ts | 2 +- 16 files changed, 375 insertions(+), 43 deletions(-) create mode 100644 src/core/api-data.ts create mode 100644 src/core/track-and-view-ids.test.ts create mode 100644 src/core/track-and-view-ids.ts diff --git a/editor/Editor.tsx b/editor/Editor.tsx index d8cde044a..6481b612d 100644 --- a/editor/Editor.tsx +++ b/editor/Editor.tsx @@ -27,6 +27,7 @@ import EditorPanel, { type EditorLangauge } from './EditorPanel'; import EditorExamples from './EditorExamples'; import './Editor.css'; +import { v4 } from 'uuid'; function json2js(jsonCode: string) { return `var spec = ${jsonCode} \nexport { spec }; \n`; @@ -249,6 +250,7 @@ function Editor(props: RouteComponentProps) { const [selectedPreviewData, setSelectedPreviewData] = useState(0); const [gistTitle, setGistTitle] = useState(); const [description, setDescription] = useState(); + const [showViews, setShowViews] = useState(false); const [expertMode, setExpertMode] = useState(false); // This parameter only matter when a markdown description was loaded from a gist but the user wants to hide it @@ -688,6 +690,44 @@ function Editor(props: RouteComponentProps) { setHideDescription(true); } + // Layers to be shown on top of the Gosling visualization to show the hiererchy of Gosling views and tracks + const VisHierarchy = useMemo(() => { + const tracksAndViews = gosRef.current?.api.getTracksAndViews(); + const maxHeight = Math.max(...(tracksAndViews?.map(d => d.shape.height) ?? [])); + return ( +
+ {tracksAndViews + ?.sort(a => (a.type === 'track' ? 1 : -1)) + ?.map(d => { + let { x: left, y: top, width, height } = d.shape; + let background = 'rgba(255, 50, 50, 0.3)'; + if (d.type === 'view') { + const VIEW_PADDING = 3; + left -= VIEW_PADDING; + top -= VIEW_PADDING; + width += VIEW_PADDING * 2; + height += VIEW_PADDING * 2; + background = 'rgba(50, 50, 255, 0.1)'; + } + return ( +
+ ); + })} +
+ ); + }, [hg, demo]); + // console.log('editor.render()'); return ( <> @@ -990,7 +1030,20 @@ function Editor(props: RouteComponentProps) { - +
{/* {expertMode && false ? (
{ + const internalSpec = getInternalSpecById(spec, viewId); + const trackIds = getTrackIds(internalSpec as View | PartialTrack); + const bb = { + x: Number.MAX_SAFE_INTEGER, + y: Number.MAX_SAFE_INTEGER, + xe: -Number.MAX_SAFE_INTEGER, + ye: -Number.MAX_SAFE_INTEGER + }; + trackIds + .map(trackId => tracks.find(t => t.id === trackId)) + .forEach(track => { + if (!track) return; + const { shape } = track; + if (bb.x > shape.x) { + bb.x = shape.x; + } + if (bb.y > shape.y) { + bb.y = shape.y; + } + if (bb.xe < shape.x + shape.width) { + bb.xe = shape.x + shape.width; + } + if (bb.ye < shape.y + shape.height) { + bb.ye = shape.y + shape.height; + } + }); + return { + id: viewId, + spec: internalSpec as View, + shape: { ...bb, width: bb.xe - bb.x, height: bb.ye - bb.y } + }; + }); +} diff --git a/src/core/api.ts b/src/core/api.ts index 63d5a62d4..2b561cb15 100644 --- a/src/core/api.ts +++ b/src/core/api.ts @@ -1,5 +1,5 @@ import * as PIXI from 'pixi.js'; -import type { TrackMouseEventData } from '@gosling.schema'; +import type { TrackApiData, VisUnitApiData, ViewApiData } from '@gosling.schema'; import type { HiGlassApi } from './higlass-component-wrapper'; import type { HiGlassSpec } from '@higlass.schema'; import { subscribe, unsubscribe } from './pubsub'; @@ -25,9 +25,12 @@ export interface GoslingApi { zoomToExtent(viewId: string, duration?: number): void; zoomToGene(viewId: string, gene: string, padding?: number, duration?: number): void; suggestGene(viewId: string, keyword: string, callback: (suggestions: GeneSuggestion[]) => void): void; + getTracksAndViews(): VisUnitApiData[]; getViewIds(): string[]; - getTracks(): TrackMouseEventData[]; - getTrack(trackId: string): TrackMouseEventData | undefined; + getTracks(): TrackApiData[]; + getTrack(trackId: string): TrackApiData | undefined; + getViews(): ViewApiData[]; + getView(viewId: string): ViewApiData | undefined; exportPng(transparentBackground?: boolean): void; exportPdf(transparentBackground?: boolean): void; getCanvas(options?: { resolution?: number; transparentBackground?: boolean }): { @@ -41,19 +44,32 @@ export interface GoslingApi { export function createApi( hg: Readonly, hgSpec: HiGlassSpec | undefined, - trackInfos: readonly TrackMouseEventData[], + tracksAndViews: readonly VisUnitApiData[], theme: Required ): GoslingApi { + const getTracksAndViews = () => { + return [...tracksAndViews]; + }; const getTracks = () => { - return [...trackInfos]; + return [...getTracksAndViews().filter(d => d.type === 'track')] as TrackApiData[]; }; const getTrack = (trackId: string) => { - const trackInfoFound = trackInfos.find(d => d.id === trackId); + const trackInfoFound = getTracks().find(d => d.id === trackId); if (!trackInfoFound) { console.warn(`[getTrack()] Unable to find a track using the ID (${trackId})`); } return trackInfoFound; }; + const getViews = () => { + return [...getTracksAndViews().filter(d => d.type === 'view')] as ViewApiData[]; + }; + const getView = (viewId: string) => { + const view = getViews().find(d => d.id === viewId); + if (!view) { + console.warn(`Unable to find a view with the ID of ${viewId}`); + } + return view; + }; const getCanvas: GoslingApi['getCanvas'] = options => { const resolution = options?.resolution ?? 4; const transparentBackground = options?.transparentBackground ?? false; @@ -118,8 +134,11 @@ export function createApi( }); return ids; }, + getTracksAndViews, getTracks, getTrack, + getView, + getViews, getCanvas, exportPng: transparentBackground => { const { canvas } = getCanvas({ resolution: 4, transparentBackground }); diff --git a/src/core/compile.ts b/src/core/compile.ts index 6986852aa..af2278fa0 100644 --- a/src/core/compile.ts +++ b/src/core/compile.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, TemplateTrackDef, TrackMouseEventData } from './gosling.schema'; +import type { GoslingSpec, TemplateTrackDef, VisUnitApiData } from './gosling.schema'; import type { HiGlassSpec } from './higlass.schema'; import { traverseToFixSpecDownstream, overrideDataTemplates } from './utils/spec-preprocess'; import { replaceTrackTemplates } from './utils/template'; @@ -7,7 +7,8 @@ import type { CompleteThemeDeep } from './utils/theme'; import { renderHiGlass as createHiGlassModels } from './create-higlass-models'; import { manageResponsiveSpecs } from './responsive'; -export type CompileCallback = (hg: HiGlassSpec, size: Size, gs: GoslingSpec, trackInfos: TrackMouseEventData[]) => void; +/** The callback function called everytime after the spec has been compiled */ +export type CompileCallback = (hg: HiGlassSpec, size: Size, gs: GoslingSpec, tracksAndViews: VisUnitApiData[]) => void; export function compile( spec: GoslingSpec, diff --git a/src/core/create-higlass-models.ts b/src/core/create-higlass-models.ts index 5907ffc46..d456ba8ae 100644 --- a/src/core/create-higlass-models.ts +++ b/src/core/create-higlass-models.ts @@ -2,9 +2,17 @@ import { getBoundingBox, type TrackInfo } from './utils/bounding-box'; import { goslingToHiGlass } from './gosling-to-higlass'; import { HiGlassModel } from './higlass-model'; import { getLinkingInfo } from './utils/linking'; -import type { GoslingSpec, OverlaidTrack, SingleTrack, TrackMouseEventData } from './gosling.schema'; +import type { + GoslingSpec, + OverlaidTrack, + SingleTrack, + TrackApiData, + VisUnitApiData, + ViewApiData +} from '@gosling.schema'; import type { CompleteThemeDeep } from './utils/theme'; import type { CompileCallback } from './compile'; +import { getViewApiData } from './api-data'; export function renderHiGlass( spec: GoslingSpec, @@ -68,7 +76,7 @@ export function renderHiGlass( }); }); - const trackInfosWithShapes: TrackMouseEventData[] = trackInfos.map(d => { + const tracks: TrackApiData[] = trackInfos.map(d => { return { id: d.track.id!, spec: d.track as SingleTrack | OverlaidTrack, @@ -76,6 +84,7 @@ export function renderHiGlass( d.track.layout === 'linear' ? d.boundingBox : { + ...d.boundingBox, cx: d.boundingBox.x + d.boundingBox.width / 2.0, cy: d.boundingBox.y + d.boundingBox.height / 2.0, innerRadius: d.track.innerRadius!, @@ -86,5 +95,14 @@ export function renderHiGlass( }; }); - callback(hgModel.spec(), getBoundingBox(trackInfos), spec, trackInfosWithShapes); + // Get the view information needed to support JS APIs (e.g., providing view bounding boxes) + const views: ViewApiData[] = getViewApiData(spec, tracks); + + // Merge the tracks and views + const tracksAndViews = [ + ...tracks.map(d => ({ ...d, type: 'track' } as VisUnitApiData)), + ...views.map(d => ({ ...d, type: 'view' } as VisUnitApiData)) + ]; + + callback(hgModel.spec(), getBoundingBox(trackInfos), spec, tracksAndViews); } diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index b07c8e675..ef7819e2f 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -1,5 +1,6 @@ /* eslint-disable react/prop-types */ import { type HiGlassApi, HiGlassComponentWrapper } from './higlass-component-wrapper'; +import type { TemplateTrackDef, VisUnitApiData } from './gosling.schema'; import React, { useState, useEffect, useMemo, useRef, forwardRef, useCallback, useImperativeHandle } from 'react'; import ResizeSensor from 'css-element-queries/src/ResizeSensor'; import * as gosling from '..'; @@ -10,8 +11,6 @@ import { omitDeep } from './utils/omit-deep'; import { isEqual } from 'lodash-es'; import * as uuid from 'uuid'; -import type { TemplateTrackDef, TrackMouseEventData } from './gosling.schema'; - // Before rerendering, wait for a few time so that HiGlass container is resized already. // If HiGlass is rendered and then the container resizes, the viewport position changes, unmatching `xDomain` specified by users. const DELAY_FOR_CONTAINER_RESIZE_BEFORE_RERENDER = 300; @@ -42,7 +41,7 @@ export const GoslingComponent = forwardRef((props, const wrapperSize = useRef(); const wrapperParentSize = useRef(); const prevSpec = useRef(); - const trackInfos = useRef([]); + const tracksAndViews = useRef([]); // HiGlass API // https://dev.to/wojciechmatuszewski/mutable-and-immutable-useref-semantics-with-react-typescript-30c9 @@ -56,8 +55,8 @@ export const GoslingComponent = forwardRef((props, ref, () => { const hgApi = refAsReadonlyProxy(hgRef); - const infos = refAsReadonlyProxy(trackInfos); - const api = createApi(hgApi, viewConfig, infos, theme); + const visUnits = refAsReadonlyProxy(tracksAndViews); + const api = createApi(hgApi, viewConfig, visUnits, theme); return { api, hgApi }; }, [viewConfig, theme] @@ -104,7 +103,7 @@ export const GoslingComponent = forwardRef((props, } prevSpec.current = newGs; - trackInfos.current = newTrackInfos; + tracksAndViews.current = newTrackInfos; }, [...GoslingTemplates], // TODO: allow user definitions theme, diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index b3b605669..8cc048db9 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -1,4 +1,3 @@ -import * as uuid from 'uuid'; import type { Track as HiGlassTrack } from './higlass.schema'; import { HiGlassModel, HIGLASS_AXIS_SIZE } from './higlass-model'; import { parseServerAndTilesetUidFromUrl } from './utils'; @@ -35,10 +34,6 @@ export function goslingToHiGlass( // we only look into the first resolved spec to get information, such as size of the track const firstResolvedSpec = resolveSuperposedTracks(gosTrack)[0]; - if (!firstResolvedSpec.id) { - firstResolvedSpec.id = uuid.v4(); - } - const assembly = firstResolvedSpec.assembly; if (IsDataDeep(firstResolvedSpec.data)) { @@ -172,7 +167,7 @@ export function goslingToHiGlass( hgModel .setViewOrientation(firstResolvedSpec.orientation) // TODO: Orientation should be assigned to 'individual' views .setAssembly(assembly) // TODO: Assembly should be assigned to 'individual' views - .addDefaultView(firstResolvedSpec.id, assembly) + .addDefaultView(firstResolvedSpec.id!, assembly) .setDomain(xDomain, yDomain ?? xDomain) .adjustDomain(firstResolvedSpec.orientation, width, height) .setMainTrack(hgTrack) diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 82aed45b5..3abecd60f 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -89,6 +89,8 @@ export type Assembly = 'hg38' | 'hg19' | 'hg18' | 'hg17' | 'hg16' | 'mm10' | 'mm export type ZoomLimits = [number | null, number | null]; export interface CommonViewDef { + /** The ID of a view that is maintained for the use of JS API functions, e.g., positions of a view */ + id?: string; /** Specify the layout type of all tracks. */ layout?: Layout; /** Specify the orientation. */ @@ -155,9 +157,8 @@ export interface CommonViewDef { export type Track = SingleTrack | OverlaidTrack | DataTrack | TemplateTrack; export interface CommonTrackDef extends CommonViewDef { - // !! TODO: we can check if the same id is used multiple times. - // !! TODO: this should be track-specific and not defined in views. - id?: string; // Assigned to `uid` in a HiGlass view config, used for API and caching. + /** Assigned to `uid` in a HiGlass view config, used for API and caching. */ + id?: string; /** If defined, will show the textual label on the left-top corner of a track. */ title?: string; @@ -254,10 +255,10 @@ interface RangeMouseEventData extends CommonEventData { } /** - * The visual parameters that determine the shape of a linear track. + * The visual parameters that determine the shape of a linear track or a view. * Origin is the left top corner. */ -interface LinearTrackShape { +export interface BoundingBox { x: number; y: number; width: number; @@ -276,8 +277,22 @@ interface CircularTrackShape { endAngle: number; } +/** + * The information of a view exposed to users through JS API. + */ +export type ViewApiData = { + /** ID of a source view, i.e., `view.id` */ + id: string; + + /** Expanded view specification processed by the Gosling compiler, e.g., default properties filled in. */ + spec: View; + + /** The shape of the source view */ + shape: BoundingBox; +}; + /** The information for a track mouse event */ -export type TrackMouseEventData = { +export type TrackApiData = { /** ID of a source track, i.e., `track.id` */ id: string; @@ -285,16 +300,19 @@ export type TrackMouseEventData = { spec: SingleTrack | OverlaidTrack; /** The shape of the source track */ - shape: LinearTrackShape | CircularTrackShape; + shape: BoundingBox | (BoundingBox & CircularTrackShape); }; +/** The API data of tracks or views */ +export type VisUnitApiData = ({ type: 'view' } & ViewApiData) | ({ type: 'track' } & TrackApiData); + export type _EventMap = { mouseOver: PointMouseEventData; click: PointMouseEventData; rangeSelect: RangeMouseEventData; rawData: CommonEventData; - trackMouseOver: TrackMouseEventData; - trackClick: TrackMouseEventData; // TODO (Jul-25-2022): with https://github.com/higlass/higlass/pull/1098, we can support circular layouts + trackMouseOver: TrackApiData; + trackClick: TrackApiData; // TODO (Jul-25-2022): with https://github.com/higlass/higlass/pull/1098, we can support circular layouts }; /** Options for determining mouse events in detail, e.g., turning on specific events only */ diff --git a/src/core/track-and-view-ids.test.ts b/src/core/track-and-view-ids.test.ts new file mode 100644 index 000000000..d551641e8 --- /dev/null +++ b/src/core/track-and-view-ids.test.ts @@ -0,0 +1,74 @@ +import type { GoslingSpec } from '@gosling.schema'; +import { getInternalSpecById, getTrackIds, getViewIds } from './track-and-view-ids'; + +describe('Retrieve IDs and Sub-spec', () => { + it('Nested Spec', () => { + const nested: GoslingSpec = { + views: [ + { + id: 'a', + views: [ + { + id: 'b', + tracks: [ + { + id: 'c' + } + ] + } + ] + } + ] + }; + expect(getViewIds(nested)).toEqual(['a', 'b']); + expect(getTrackIds(nested)).toEqual(['c']); + expect(getInternalSpecById(nested, 'b')).toMatchInlineSnapshot(` + { + "id": "b", + "tracks": [ + { + "id": "c", + }, + ], + } + `); + }); + it('Nested Spec with Overlaid Track', () => { + const overlay: GoslingSpec = { + views: [ + { + id: 'a', + views: [ + { + id: 'b', + tracks: [ + { + alignment: 'overlay', + id: 'c', + tracks: [ + { + id: 'd' + } + ] + } + ] + } + ] + } + ] + }; + expect(getViewIds(overlay)).toEqual(['a', 'b', 'c']); + expect(getTrackIds(overlay)).toEqual(['d']); + expect(getInternalSpecById(overlay, 'c')).toMatchInlineSnapshot(` + { + "alignment": "overlay", + "id": "c", + "tracks": [ + { + "id": "d", + }, + ], + } + `); + }); +}); diff --git a/src/core/track-and-view-ids.ts b/src/core/track-and-view-ids.ts new file mode 100644 index 000000000..e263fca19 --- /dev/null +++ b/src/core/track-and-view-ids.ts @@ -0,0 +1,63 @@ +import type { CommonTrackDef, CommonViewDef, GoslingSpec, PartialTrack, View } from '@gosling.schema'; +import { traverseTracksAndViews } from './utils/spec-preprocess'; + +/** + * Find all unique IDs of 'views' in a Gosling spec and return them as an array. + * @param spec + * @returns + */ +export function getViewIds(spec: GoslingSpec | View | PartialTrack) { + const viewIds = new Set(); + if (spec.id) { + // root view + viewIds.add(spec.id); + } + traverseTracksAndViews(spec, subSpec => { + if ('views' in subSpec || 'tracks' in subSpec) { + // encountered a view + if (subSpec.id) { + // found a valid view id + viewIds.add(subSpec.id); + } + } + }); + return Array.from(viewIds); +} + +/** + * Find all unique IDs of 'tracks' in a Gosling spec and return them as an array. + * @param spec + * @returns + */ +export function getTrackIds(spec: GoslingSpec | View | PartialTrack) { + const trackIds = new Set(); + traverseTracksAndViews(spec, subSpec => { + if (!('views' in subSpec) && !('tracks' in subSpec)) { + // encountered a track + if (subSpec.id) { + // found a valid track id + trackIds.add(subSpec.id); + } + } + }); + return Array.from(trackIds); +} + +/** + * Get an internal spec using an ID of a track or a view. `undefined` if unfound. + * @param spec + * @returns + */ +export function getInternalSpecById(spec: GoslingSpec | View | PartialTrack, id: string) { + let internalSpec: CommonViewDef | CommonTrackDef | undefined; + if (spec.id === id) { + // root view + internalSpec = spec; + } + traverseTracksAndViews(spec, subSpec => { + if (subSpec.id === id) { + internalSpec = subSpec; + } + }); + return internalSpec; +} diff --git a/src/core/utils/bounding-box.ts b/src/core/utils/bounding-box.ts index 9c0ebcd80..e58aa70bb 100644 --- a/src/core/utils/bounding-box.ts +++ b/src/core/utils/bounding-box.ts @@ -1,4 +1,4 @@ -import type { MultipleViews, CommonViewDef, GoslingSpec, Track, SingleView } from '../gosling.schema'; +import type { MultipleViews, CommonViewDef, GoslingSpec, Track, SingleView } from '@gosling.schema'; import { Is2DTrack, IsOverlaidTrack, IsXAxis, IsYAxis } from '../gosling.schema.guards'; import { HIGLASS_AXIS_SIZE } from '../higlass-model'; import { @@ -16,13 +16,6 @@ export interface Size { height: number; } -export interface GridInfo extends Size { - columnSizes: number[]; - rowSizes: number[]; - columnGaps: number[]; - rowGaps: number[]; -} - /** * Position information of each track. */ @@ -79,7 +72,10 @@ export function getBoundingBox(trackInfos: TrackInfo[]) { export function getRelativeTrackInfo( spec: GoslingSpec, theme: CompleteThemeDeep -): { trackInfos: TrackInfo[]; size: { width: number; height: number } } { +): { + trackInfos: TrackInfo[]; + size: { width: number; height: number }; +} { let trackInfos: TrackInfo[] = [] as TrackInfo[]; // Collect track information including spec, bounding boxes, and RGL' `layout`. diff --git a/src/core/utils/linking.test.ts b/src/core/utils/linking.test.ts index bc7c68cc3..fc29d8199 100644 --- a/src/core/utils/linking.test.ts +++ b/src/core/utils/linking.test.ts @@ -9,6 +9,7 @@ describe('Should get linking information correctly', () => { new HiGlassModel(), { data: { type: 'csv', url: 'https://' }, + id: 'track', overlay: [ { mark: 'point', diff --git a/src/core/utils/spec-preprocess.ts b/src/core/utils/spec-preprocess.ts index 0681af500..51530c519 100644 --- a/src/core/utils/spec-preprocess.ts +++ b/src/core/utils/spec-preprocess.ts @@ -175,6 +175,11 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare // Nothing to do when `xLinkID` not suggested } + // ID should be assigned to each view and track for an API usage + if (!spec.id) { + spec.id = uuid.v4(); + } + if ('tracks' in spec) { let tracks: Track[] = convertToFlatTracks(spec); @@ -187,6 +192,11 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare const linkID = uuid.v4(); tracks.forEach((track, i, array) => { + // ID should be assigned to each view and track for an API usage + if (!track.id) { + track.id = uuid.v4(); + } + // If size not defined, set default ones if (!track.width) { track.width = Is2DTrack(track) ? DEFAULT_TRACK_SIZE_2D : DEFAULT_TRACK_WIDTH_LINEAR; diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 738d53974..5be9d7057 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -1077,7 +1077,7 @@ const factory: PluginTrackFactory = (HGC, context, op publish(eventType, { id: context.viewUid, spec: structuredClone(this.options.spec), - shape: { cx, cy, innerRadius, outerRadius, startAngle, endAngle } + shape: { x, y, width, height, cx, cy, innerRadius, outerRadius, startAngle, endAngle } }); } } else { From 388845d56b82e60a800e0b25abe4f0917a282eca Mon Sep 17 00:00:00 2001 From: etowahadams Date: Tue, 18 Jul 2023 06:05:13 -0400 Subject: [PATCH 11/25] docs(core): explain how spec compiling works (#942) Explains in the contributing doc how the spec compiling works. --- Co-authored-by: Sehi L'Yi --- CONTRIBUTING.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9af223d21..e2abbb4b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -130,7 +130,6 @@ If there is an example you would like to add to the editor example library, plea 5. Select the example in the editor to make sure your example works as expected. - ## Bumping Gosling.js GitHub Action handles bumping the version of Gosling.js. The pattern looks like the following: @@ -139,3 +138,30 @@ GitHub Action handles bumping the version of Gosling.js. The pattern looks like yarn version --patch git push origin master --tags ``` + +# Internal Explanations +## How does a Gosling spec get turned into a HiGlass spec? +A Gosling schema goes through the following steps: + +1. **Preprocessing**: The implicit aspects of the Gosling schema is made explicit here. Default values are added, properties set in parent elements are added to child elements, and IDs for every Gosling track and view are generated if they aren't specified by the user. +2. **Track data intermediate representation**: From the preprocessed schema, a Gosling track-based intermediate representation is created. All the information from the schema gets converted into an array of Gosling track data objects, each with track-specific data. What happens to Gosling views? View specifications get applied to each track intermediate representation. +3. **HiGlass schema generation**: By iterating over the Gosling track intermediate representations, a HiGlass schema is generated. Each Gosling track corresponds to a HiGlass view. This HiGlass view will contain one or more HiGlass track, depending on the Gosling track it was derived from. + +One important nuance to the Gosling schema to HiGlass schema conversion is that there is not a direct correspondence between Gosling tracks and view and HiGlass tracks and views. Instead, each Gosling track corresponds to a HiGlass view. Please examine the relationship between the view and track IDs. + +- **Gosling track ID**: ID that is associated with each track. It is set by the user or is generated during the preprocessing step. +- **Gosling view ID**: ID associated with each view. It is set by the user or is generated during the preprocessing step. +- **HiGlass track ID**: The HiGlass track ID is the same as the Gosling track ID with `-track` appended to it. +- **HiGlass view ID**: Each HiGlass view has an ID, and *this ID is the same as the Gosling track ID.* An instance of GoslingTrackClass knows what the Gosling track ID is by calling `context.viewUid`, which is the HiGlass view ID which is the same as the Gosling track ID. + +The one exception to this are overlaid tracks where the `id` defined at the root level of an overlaid track is used as the HiGlass' view ID. This limits the number of gosling-track instances generated (i.e., multiple tracks that are overlaid can share the same data, hence reducing the number of data requests). +``` +tracks: [ + { ..., id: '1' }, // ← This is a track in Gosling. This becomes a HiGlass view '1' + { ..., alignment: 'overlay', id: '2', // ← This is actually a view in Gosling, but this becomes a HiGlass view '2' + tracks: [ + { ..., id: '3' }, // ← This track is included in a HiGlass view '2' + { ..., id: '4' }, // ← This track is included in a HiGlass view '2' + ]} +] +``` \ No newline at end of file From a98ee69367273943becbeae8696b1511b35f04e4 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Mon, 24 Jul 2023 08:17:06 -0400 Subject: [PATCH 12/25] feat(api): onNewTrack, onNewView (#943) * add two new API methods: `onNewTrack` and `onNewView` --- editor/Editor.tsx | 8 ++++++++ src/core/gosling-component.tsx | 33 ++++++++++++++++++++++-------- src/core/gosling.schema.ts | 10 +++++++++ src/gosling-track/gosling-track.ts | 19 +++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/editor/Editor.tsx b/editor/Editor.tsx index 6481b612d..5e890b0f8 100644 --- a/editor/Editor.tsx +++ b/editor/Editor.tsx @@ -334,6 +334,14 @@ function Editor(props: RouteComponentProps) { // gosRef.current.api.subscribe('trackClick', (type, eventData) => { // console.warn(type, eventData.id, eventData.spec, eventData.shape); // }); + // New Track + // gosRef.current.api.subscribe('onNewTrack', (type, eventData) => { + // console.warn(type, eventData); + // }); + // New View + // gosRef.current.api.subscribe('onNewView', (type, eventData) => { + // console.warn(type, eventData); + // }); } return () => { // gosRef.current?.api.unsubscribe('mouseOver'); diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index ef7819e2f..85fa2b874 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -10,6 +10,7 @@ import { GoslingTemplates } from '..'; import { omitDeep } from './utils/omit-deep'; import { isEqual } from 'lodash-es'; import * as uuid from 'uuid'; +import { publish } from './pubsub'; // Before rerendering, wait for a few time so that HiGlass container is resized already. // If HiGlass is rendered and then the container resizes, the viewport position changes, unmatching `xDomain` specified by users. @@ -50,6 +51,22 @@ export const GoslingComponent = forwardRef((props, const theme = getTheme(props.theme || 'light'); const wrapperDivId = props.id ?? uuid.v4(); + /** + * Publishes event if there is a new view added + * @param currentTracksAndViews newly retrieved tracks and views from compile() callback + */ + const publishOnNewView = (currentTracksAndViews: VisUnitApiData[]) => { + // Compare the previous and current views to figure out the difference + const prevViews = tracksAndViews.current.filter(data => data.type == 'view'); + const currentViews = currentTracksAndViews.filter(data => data.type == 'view'); + const prevViewIds = new Set(prevViews.map(data => data.id)); + const newViews = currentViews.filter(view => !prevViewIds.has(view.id)); + // Publish if there are any new changes + newViews.forEach(view => { + publish('onNewView', { id: view.id }); + }); + }; + // Gosling APIs useImperativeHandle( ref, @@ -74,18 +91,18 @@ export const GoslingComponent = forwardRef((props, gosling.compile( props.spec, - (newHs, newSize, newGs, newTrackInfos) => { + (newHiGlassSpec, newSize, newGoslingSpec, newTracksAndViews) => { // TODO: `linkingId` should be updated // We may not want to re-render this if ( prevSpec.current && - isEqual(omitDeep(prevSpec.current, ['linkingId']), omitDeep(newGs, ['linkingId'])) + isEqual(omitDeep(prevSpec.current, ['linkingId']), omitDeep(newGoslingSpec, ['linkingId'])) ) { return; } // If a callback function is provided, return compiled information. - props.compiled?.(props.spec!, newHs); + props.compiled?.(props.spec!, newHiGlassSpec); // Change the size of wrapper `
` elements setSize(newSize); @@ -95,15 +112,15 @@ export const GoslingComponent = forwardRef((props, if (props.experimental?.reactive && isMountedOnce) { // Use API to update visualization. setTimeout(() => { - hgRef.current?.api.setViewConfig(newHs); + hgRef.current?.api.setViewConfig(newHiGlassSpec); }, DELAY_FOR_CONTAINER_RESIZE_BEFORE_RERENDER); } else { // Mount `HiGlassComponent` using this view config. - setViewConfig(newHs); + setViewConfig(newHiGlassSpec); } - - prevSpec.current = newGs; - tracksAndViews.current = newTrackInfos; + publishOnNewView(newTracksAndViews); + prevSpec.current = newGoslingSpec; + tracksAndViews.current = newTracksAndViews; }, [...GoslingTemplates], // TODO: allow user definitions theme, diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 3abecd60f..a940b3125 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -242,6 +242,14 @@ export interface GenomicPosition { chromosome: string; position: number; } +interface OnNewTrackEventData { + /** Source visualization ID, i.e., `track.id` */ + id: string; +} +interface OnNewViewEventData { + /** Source visualization ID, i.e., `track.id` */ + id: string; +} interface PointMouseEventData extends CommonEventData { /** A genomic coordinate, e.g., `chr1:100,000`. */ @@ -313,6 +321,8 @@ export type _EventMap = { rawData: CommonEventData; trackMouseOver: TrackApiData; trackClick: TrackApiData; // TODO (Jul-25-2022): with https://github.com/higlass/higlass/pull/1098, we can support circular layouts + onNewTrack: OnNewTrackEventData; + onNewView: OnNewViewEventData; }; /** Options for determining mouse events in detail, e.g., turning on specific events only */ diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 5be9d7057..35db29b03 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -147,6 +147,7 @@ const factory: PluginTrackFactory = (HGC, context, op mRangeBrush: LinearBrushModel; #assembly?: Assembly; // Used to get the relative genomic position #processedTileInfo: Record; + #firstDraw = true; // False if draw has been called once already. Used with onNewTrack API // Used in mark/legend.ts gLegend? = HGC.libraries.d3Selection.select(context.svgElement).append('g'); displayedLegends: DisplayedLegend[] = []; // Store the color legends added so far so that we can avoid overlaps and redundancy @@ -294,6 +295,11 @@ const factory: PluginTrackFactory = (HGC, context, op // Based on the updated marks, update range selection this.mRangeBrush?.drawBrush(true); + // Publish onNewTrack if this is the first draw + if (this.#firstDraw) { + this.#publishOnNewTrack(); + this.#firstDraw = false; + } } /* @@ -1293,6 +1299,19 @@ const factory: PluginTrackFactory = (HGC, context, op return ''; } + /** + * Javscript subscription API methods (besides for mouse) + */ + + /** + * Publishes track information. Triggered when track gets created + */ + #publishOnNewTrack() { + publish('onNewTrack', { + id: context.viewUid + }); + } + /* * * * Other misc methods and overrides From a2c36f500f0d8e1181c9c72a970eb385af4fe3b5 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Mon, 24 Jul 2023 12:12:54 -0400 Subject: [PATCH 13/25] feat(api): subscription for genomic axis changes (#935) * Add a `location` Javascript API event which responds to genomic axis changes --- editor/Editor.tsx | 4 +++ src/core/gosling.schema.ts | 8 +++++ src/core/utils/assembly.test.ts | 12 ++++++++ src/core/utils/assembly.ts | 49 ++++++++++++++++++++++++------ src/gosling-track/gosling-track.ts | 12 ++++++++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/editor/Editor.tsx b/editor/Editor.tsx index 5e890b0f8..e7a7a79d1 100644 --- a/editor/Editor.tsx +++ b/editor/Editor.tsx @@ -334,6 +334,9 @@ function Editor(props: RouteComponentProps) { // gosRef.current.api.subscribe('trackClick', (type, eventData) => { // console.warn(type, eventData.id, eventData.spec, eventData.shape); // }); + // Location API + // gosRef.current.api.subscribe('location', (type, eventData) => { + // console.warn(type, eventData.id, eventData.genomicRange); // New Track // gosRef.current.api.subscribe('onNewTrack', (type, eventData) => { // console.warn(type, eventData); @@ -348,6 +351,7 @@ function Editor(props: RouteComponentProps) { // gosRef.current?.api.unsubscribe('click'); // gosRef.current?.api.unsubscribe('rangeSelect'); // gosRef.current?.api.unsubscribe('trackClick'); + // gosRef.current?.api.unsubscribe('location'); }; }, [gosRef.current]); diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index a940b3125..397041ddb 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -262,6 +262,13 @@ interface RangeMouseEventData extends CommonEventData { genomicRange: [GenomicPosition, GenomicPosition] | null; } +/** + * Data about the genomic range of a track + */ +interface LocationEventData extends Omit { + genomicRange: [GenomicPosition, GenomicPosition]; +} + /** * The visual parameters that determine the shape of a linear track or a view. * Origin is the left top corner. @@ -323,6 +330,7 @@ export type _EventMap = { trackClick: TrackApiData; // TODO (Jul-25-2022): with https://github.com/higlass/higlass/pull/1098, we can support circular layouts onNewTrack: OnNewTrackEventData; onNewView: OnNewViewEventData; + location: LocationEventData; }; /** Options for determining mouse events in detail, e.g., turning on specific events only */ diff --git a/src/core/utils/assembly.test.ts b/src/core/utils/assembly.test.ts index abd7d6957..c6963104f 100644 --- a/src/core/utils/assembly.test.ts +++ b/src/core/utils/assembly.test.ts @@ -41,6 +41,18 @@ describe('Assembly', () => { chromosome: 'unknown', position: outOfPos }); + expect(getRelativeGenomicPosition(outOfPos, 'hg38', true)).toMatchInlineSnapshot(` + { + "chromosome": "chrY", + "position": 3088269832, + } + `); + expect(getRelativeGenomicPosition(-1, 'hg38', true)).toMatchInlineSnapshot(` + { + "chromosome": "chr1", + "position": 0, + } + `); }); it('Parse string to genomic positions', () => { const customChromSizes: Assembly = [ diff --git a/src/core/utils/assembly.ts b/src/core/utils/assembly.ts index f8e2997ad..25a4830de 100644 --- a/src/core/utils/assembly.ts +++ b/src/core/utils/assembly.ts @@ -18,19 +18,48 @@ export interface ChromSize { /** * Get relative chromosome position (e.g., `100` => `{ chromosome: 'chr1', position: 100 }`) + * @param absPos number which is the absolute chromosome position + * @param assembly the assembly used to calculate which chromosome position + * @param returnWithinAssembly If true, then if the absolute position is before the first chromosome, it returns the + * first position of the first chromosome. If the absolute position is after the last chromosome, it returns the last + * position of the last chromosome + * @returns the genomic position of the absPos */ -export function getRelativeGenomicPosition(absPos: number, assembly?: Assembly): GenomicPosition { - const [chromosome, absInterval] = Object.entries(computeChromSizes(assembly).interval).find(d => { - const [start, end] = d[1]; - return start <= absPos && absPos < end; - }) ?? [null, null]; - - if (!chromosome || !absInterval) { - // The number is out of range +export function getRelativeGenomicPosition( + absPos: number, + assembly?: Assembly, + returnWithinAssembly = false +): GenomicPosition { + const chrSizes = Object.entries(computeChromSizes(assembly).interval); + const minPosChr = { chromosome: 'unknown', position: Infinity } as GenomicPosition; + const maxPosChr = { chromosome: 'unknown', position: 0 } as GenomicPosition; + for (const chrSize of chrSizes) { + const [chromosome, absInterval] = chrSize; + const [start, end] = absInterval; + // absPos was found within this chromosome + if (start <= absPos && absPos < end) { + return { chromosome, position: absPos - start } as GenomicPosition; + } + // Update the min and max chromosomes found + if (start < minPosChr.position) { + minPosChr.chromosome = chromosome; + minPosChr.position = start; + } + if (end > maxPosChr.position) { + maxPosChr.chromosome = chromosome; + maxPosChr.position = end; + } + } + if (returnWithinAssembly) { + // Return either the min or max chromosome position + if (absPos < minPosChr.position) { + return minPosChr; + } else { + return maxPosChr; + } + } else { return { chromosome: 'unknown', position: absPos }; } - - return { chromosome, position: absPos - absInterval[0] }; } /** diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 35db29b03..649a85ff5 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -459,6 +459,18 @@ const factory: PluginTrackFactory = (HGC, context, op this.refreshTiles(); this.draw(); this.forceDraw(); + + // Publish the new genomic axis domain + const genomicRange = newXScale + .domain() + .map(absPos => getRelativeGenomicPosition(absPos, this.#assembly, true)) as [ + GenomicPosition, + GenomicPosition + ]; + publish('location', { + id: context.viewUid, + genomicRange: genomicRange + }); } /* * From ef5f58f43ee33ca2de068a547f21ed1767426437 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Mon, 24 Jul 2023 14:18:20 -0400 Subject: [PATCH 14/25] fix(data-fetcher): correctly calculate the distance to previous mutation in VCF (`DISTPREV`) (#949) * fix: correct DISTPREV calculation for vcf * chore: format * chore: rename var for clarity --- src/data-fetchers/gff/utils.ts | 2 +- src/data-fetchers/vcf/utils.ts | 14 +++++++------- src/data-fetchers/vcf/vcf-data-fetcher.test.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/data-fetchers/gff/utils.ts b/src/data-fetchers/gff/utils.ts index 535747302..0bbb11287 100644 --- a/src/data-fetchers/gff/utils.ts +++ b/src/data-fetchers/gff/utils.ts @@ -17,4 +17,4 @@ export function makeRandomSortedArray(n: number, maxValue: number) { } const sorted = randomArray.sort((a, b) => a - b); return [...new Set(sorted)]; -} \ No newline at end of file +} diff --git a/src/data-fetchers/vcf/utils.ts b/src/data-fetchers/vcf/utils.ts index 3b663ab58..b4896448f 100644 --- a/src/data-fetchers/vcf/utils.ts +++ b/src/data-fetchers/vcf/utils.ts @@ -37,10 +37,10 @@ export const getSubstitutionType = (ref: string, alt?: string) => { * Convert a VCF record to a tile data * @param vcfRecord A row of a VCF files loaded * @param chrPos Cumulative start position of a chromosome - * @param prevPos Previous position of a point mutation for calculating 'distance to previous' + * @param prevAbsPos Previous position of a point mutation for calculating 'distance to previous' */ -export function recordToTile(vcfRecord: VcfRecord, chrPos: number, prevPos?: number) { - const POS = chrPos + vcfRecord.POS + 1; +export function recordToTile(vcfRecord: VcfRecord, chrPos: number, prevAbsPos?: number) { + const absPos = chrPos + vcfRecord.POS + 1; let ALT: string | undefined; if (Array.isArray(vcfRecord.ALT) && vcfRecord.ALT.length > 0) { @@ -48,11 +48,11 @@ export function recordToTile(vcfRecord: VcfRecord, chrPos: number, prevPos?: num } // Additionally inferred values - const DISTPREV = !prevPos ? null : vcfRecord.POS - prevPos; - const DISTPREVLOGE = !prevPos ? null : Math.log(vcfRecord.POS - prevPos); + const DISTPREV = !prevAbsPos ? null : absPos - prevAbsPos; + const DISTPREVLOGE = !prevAbsPos ? null : Math.log(absPos - prevAbsPos); const MUTTYPE = getMutationType(vcfRecord.REF, ALT); const SUBTYPE = getSubstitutionType(vcfRecord.REF, ALT); - const POSEND = POS + vcfRecord.REF.length; + const POSEND = absPos + vcfRecord.REF.length; // Create key values const data: VcfTile = { @@ -62,7 +62,7 @@ export function recordToTile(vcfRecord: VcfRecord, chrPos: number, prevPos?: num SUBTYPE, INFO: JSON.stringify(vcfRecord.INFO), ORIGINALPOS: vcfRecord.POS, - POS, + POS: absPos, POSEND, DISTPREV, DISTPREVLOGE diff --git a/src/data-fetchers/vcf/vcf-data-fetcher.test.ts b/src/data-fetchers/vcf/vcf-data-fetcher.test.ts index e952661c0..5b573d720 100644 --- a/src/data-fetchers/vcf/vcf-data-fetcher.test.ts +++ b/src/data-fetchers/vcf/vcf-data-fetcher.test.ts @@ -3,9 +3,10 @@ import type { VcfRecord } from './vcf-data-fetcher'; describe('VCF file parser', () => { it('Convert a VCF record to a tile data', () => { + const chrPos = 123456; const record: VcfRecord = { CHROM: '20', - POS: 14370, + POS: chrPos + 14370, ID: ['rs6054257'], REF: 'G', ALT: ['A'], @@ -17,7 +18,6 @@ describe('VCF file parser', () => { AF: ['0.5', 'DB', 'H2'] } }; - const chrPos = 123456; const prevPos = record.POS - 100; const tile = recordToTile(record, chrPos, prevPos); @@ -26,8 +26,8 @@ describe('VCF file parser', () => { "AF": "0.5, DB, H2", "ALT": "A", "CHROM": "20", - "DISTPREV": 100, - "DISTPREVLOGE": 4.605170185988092, + "DISTPREV": 123557, + "DISTPREVLOGE": 11.724457867035593, "DP": "14", "FILTER": "PASS", "ID": [ @@ -36,9 +36,9 @@ describe('VCF file parser', () => { "INFO": "{\\"NS\\":[3],\\"DP\\":[14],\\"AF\\":[\\"0.5\\",\\"DB\\",\\"H2\\"]}", "MUTTYPE": "substitution", "NS": "3", - "ORIGINALPOS": 14370, - "POS": 137827, - "POSEND": 137828, + "ORIGINALPOS": 137826, + "POS": 261283, + "POSEND": 261284, "QUAL": 29, "REF": "G", "SUBTYPE": "C>T", From 529e9a38484fe81b624292a582870ae0cc144d28 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Thu, 27 Jul 2023 13:21:00 -0400 Subject: [PATCH 15/25] fix(track): remove private properties from draw() (#952) --- src/gosling-track/gosling-track.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 649a85ff5..6f58e79cb 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -147,7 +147,7 @@ const factory: PluginTrackFactory = (HGC, context, op mRangeBrush: LinearBrushModel; #assembly?: Assembly; // Used to get the relative genomic position #processedTileInfo: Record; - #firstDraw = true; // False if draw has been called once already. Used with onNewTrack API + firstDraw = true; // False if draw has been called once already. Used with onNewTrack API. Public because used in draw() // Used in mark/legend.ts gLegend? = HGC.libraries.d3Selection.select(context.svgElement).append('g'); displayedLegends: DisplayedLegend[] = []; // Store the color legends added so far so that we can avoid overlaps and redundancy @@ -296,9 +296,9 @@ const factory: PluginTrackFactory = (HGC, context, op // Based on the updated marks, update range selection this.mRangeBrush?.drawBrush(true); // Publish onNewTrack if this is the first draw - if (this.#firstDraw) { + if (this.firstDraw) { this.#publishOnNewTrack(); - this.#firstDraw = false; + this.firstDraw = false; } } From ee02d38312ae7431ccd460a6d5e1904013488427 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Tue, 1 Aug 2023 08:24:32 -0400 Subject: [PATCH 16/25] feat(track): dummy-track (#946) * feat: add SVGTrack type * feat: dummy track * feat: dummy track styles * fix: update schema * fix: simplify logic * feat(track): add text stroke properties * fix: update schema * test(core): add test for dummy track compiling * fix(track): put text in the center Co-authored-by: Sehi L'Yi * fix: remove hardcoded values * feat(track): circular placeholder layout * fix: update schema * feat(api): onNewTrack * docs: add dummy track example * refactor(track): make dummy track type minimal * feat(track): add outline option * fix: filter out circular dummy track * test(compile): dummy track removed if layout circle * fix: shorthand name * fix: update schema --------- Co-authored-by: Sehi L'Yi --- editor/example/doc-examples/dummy-track.ts | 441 +++++++++++++++++++++ editor/example/doc-examples/index.ts | 1 + editor/example/index.ts | 6 + schema/gosling.schema.json | 176 +++++++- schema/higlass.schema.json | 3 +- src/core/compile.test.ts | 78 ++++ src/core/create-higlass-models.ts | 3 +- src/core/gosling-to-higlass.ts | 8 +- src/core/gosling.schema.guards.ts | 6 +- src/core/gosling.schema.ts | 49 ++- src/core/higlass-model.ts | 23 +- src/core/higlass.schema.ts | 3 +- src/core/init.ts | 10 + src/core/utils/bounding-box.ts | 13 +- src/core/utils/overlay.ts | 4 +- src/core/utils/spec-preprocess.ts | 22 +- src/dummy-track/dummy-track.ts | 70 ++++ src/dummy-track/index.ts | 1 + src/missing-types.d.ts | 10 + tsconfig.json | 1 + vite.config.js | 1 + 21 files changed, 903 insertions(+), 26 deletions(-) create mode 100644 editor/example/doc-examples/dummy-track.ts create mode 100644 src/dummy-track/dummy-track.ts create mode 100644 src/dummy-track/index.ts diff --git a/editor/example/doc-examples/dummy-track.ts b/editor/example/doc-examples/dummy-track.ts new file mode 100644 index 000000000..cd4a2c332 --- /dev/null +++ b/editor/example/doc-examples/dummy-track.ts @@ -0,0 +1,441 @@ +import type { GoslingSpec } from 'gosling.js'; + +export const DUMMY_TRACK: GoslingSpec = { + title: 'Dummy track example', + subtitle: 'This example demonstrates a dummy track alongside other tracks', + layout: 'linear', + xDomain: { chromosome: 'chr3', interval: [52168000, 52890000] }, + arrangement: 'horizontal', + views: [ + { + arrangement: 'vertical', + views: [ + { + alignment: 'overlay', + title: 'HiGlass', + data: { + url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ], + exonIntervalFields: [ + { index: 12, name: 'start' }, + { index: 13, name: 'end' } + ] + }, + tracks: [ + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'triangleRight', + x: { field: 'end', type: 'genomic', axis: 'top' }, + size: { value: 15 } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'name', type: 'nominal' }, + x: { field: 'start', type: 'genomic' }, + xe: { field: 'end', type: 'genomic' }, + style: { dy: -15 } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'triangleLeft', + x: { field: 'start', type: 'genomic' }, + size: { value: 15 }, + style: { align: 'right' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['exon'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic' }, + size: { value: 15 }, + xe: { field: 'end', type: 'genomic' } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic' }, + strokeWidth: { value: 3 }, + xe: { field: 'end', type: 'genomic' }, + style: { linePattern: { type: 'triangleRight', size: 5 } } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic' }, + strokeWidth: { value: 3 }, + xe: { field: 'end', type: 'genomic' }, + style: { linePattern: { type: 'triangleLeft', size: 5 } } + } + ], + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + color: { + field: 'strand', + type: 'nominal', + domain: ['+', '-'], + range: ['#7585FF', '#FF8A85'] + }, + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + opacity: { value: 0.8 }, + width: 350, + height: 100 + }, + { + tracks: [ + { + type: 'dummy-track', + title: 'Placeholder', + style: { background: '#e6e6e6' } + } + ], + width: 350, + height: 130 + }, + { + alignment: 'overlay', + title: 'IGV', + data: { + url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ], + exonIntervalFields: [ + { index: 12, name: 'start' }, + { index: 13, name: 'end' } + ] + }, + tracks: [ + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'name', type: 'nominal' }, + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' } + }, + { + type: 'dummy-track', + title: 'Placeholder track', + style: { background: '#e6e6e6' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + size: { value: 15 }, + xe: { field: 'end', type: 'genomic' } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic', axis: 'top' }, + strokeWidth: { value: 0 }, + xe: { field: 'end', type: 'genomic' }, + color: { value: 'white' }, + opacity: { value: 0.6 }, + style: { linePattern: { type: 'triangleLeft', size: 10 } } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'rule', + x: { field: 'start', type: 'genomic', axis: 'top' }, + strokeWidth: { value: 0 }, + xe: { field: 'end', type: 'genomic' }, + color: { value: 'white' }, + opacity: { value: 0.6 }, + style: { linePattern: { type: 'triangleRight', size: 10 } } + } + ], + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + color: { value: '#0900B1' }, + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + width: 350, + height: 100 + } + ] + }, + { + arrangement: 'vertical', + views: [ + { + alignment: 'overlay', + title: 'Cyverse-QUBES', + data: { + url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ], + exonIntervalFields: [ + { index: 12, name: 'start' }, + { index: 13, name: 'end' } + ] + }, + tracks: [ + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'name', type: 'nominal' }, + x: { field: 'start', type: 'genomic' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: 'black' } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'triangleRight', + x: { field: 'end', type: 'genomic', axis: 'top' }, + color: { value: '#999999' } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'triangleLeft', + x: { field: 'start', type: 'genomic', axis: 'top' }, + color: { value: '#999999' }, + style: { align: 'right' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: 'lightgray' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'rule', + x: { field: 'start', type: 'genomic', axis: 'top' }, + strokeWidth: { value: 5 }, + xe: { field: 'end', type: 'genomic' }, + color: { value: 'gray' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['exon'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: '#E2A6F5' }, + stroke: { value: '#BB57C9' }, + strokeWidth: { value: 1 } + } + ], + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + size: { value: 15 }, + width: 350, + height: 100 + }, + { + alignment: 'overlay', + title: 'GmGDV', + data: { + url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ], + exonIntervalFields: [ + { index: 12, name: 'start' }, + { index: 13, name: 'end' } + ] + }, + tracks: [ + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'name', type: 'nominal' }, + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + style: { dy: -14 } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['+'] } + ], + mark: 'triangleRight', + x: { field: 'end', type: 'genomic', axis: 'top' }, + size: { value: 15 } + }, + { + dataTransform: [ + { type: 'filter', field: 'type', oneOf: ['gene'] }, + { type: 'filter', field: 'strand', oneOf: ['-'] } + ], + mark: 'triangleLeft', + x: { field: 'start', type: 'genomic', axis: 'top' }, + size: { value: 15 }, + style: { align: 'right' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['exon'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + size: { value: 10 }, + xe: { field: 'end', type: 'genomic' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'rule', + x: { field: 'start', type: 'genomic', axis: 'top' }, + strokeWidth: { value: 3 }, + xe: { field: 'end', type: 'genomic' } + } + ], + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + color: { + field: 'strand', + type: 'nominal', + domain: ['+', '-'], + range: ['blue', 'red'] + }, + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + width: 350, + height: 100 + }, + { + alignment: 'overlay', + data: { + url: 'https://server.gosling-lang.org/api/v1/tileset_info/?d=gene-annotation', + type: 'beddb', + genomicFields: [ + { index: 1, name: 'start' }, + { index: 2, name: 'end' } + ], + valueFields: [ + { index: 5, name: 'strand', type: 'nominal' }, + { index: 3, name: 'name', type: 'nominal' } + ], + exonIntervalFields: [ + { index: 12, name: 'start' }, + { index: 13, name: 'end' } + ] + }, + tracks: [ + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'text', + text: { field: 'name', type: 'nominal' }, + x: { field: 'start', type: 'genomic', axis: 'top' }, + color: { value: 'black' }, + xe: { field: 'end', type: 'genomic' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['gene'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: '#666666' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['exon'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: '#FF6666' } + }, + { + dataTransform: [{ type: 'filter', field: 'type', oneOf: ['intron'] }], + mark: 'rect', + x: { field: 'start', type: 'genomic', axis: 'top' }, + xe: { field: 'end', type: 'genomic' }, + color: { value: '#99FEFF' } + } + ], + size: { value: 30 }, + row: { field: 'strand', type: 'nominal', domain: ['+', '-'] }, + stroke: { value: '#777777' }, + strokeWidth: { value: 1 }, + visibility: [ + { + operation: 'less-than', + measure: 'width', + threshold: '|xe-x|', + transitionPadding: 10, + target: 'mark' + } + ], + width: 350, + height: 100 + } + ] + } + ] +}; diff --git a/editor/example/doc-examples/index.ts b/editor/example/doc-examples/index.ts index a2a7d5455..3a9094ac0 100644 --- a/editor/example/doc-examples/index.ts +++ b/editor/example/doc-examples/index.ts @@ -1,5 +1,6 @@ export { AREA, LINE, POINT, BAR } from './single-mark'; export { BRUSH } from './brush'; +export { DUMMY_TRACK } from './dummy-track'; export { LINK } from './link'; export { LINKING_TRACKS } from './linking-tracks'; export { OVERLAY_TRACKS_BAR_POINT } from './overlay-tracks-bar-point'; diff --git a/editor/example/index.ts b/editor/example/index.ts index 9a937cba3..e8b979687 100644 --- a/editor/example/index.ts +++ b/editor/example/index.ts @@ -219,6 +219,12 @@ const docExampleObj: { spec: docExamples.SEMANTIC_ZOOM_CYTO, hidden: true }, + doc_dummy_track: { + group: 'Doc', + name: 'Dummy track', + spec: docExamples.DUMMY_TRACK, + hidden: true + }, doc_vcf_indels: { group: 'Doc', name: 'VCF insertions and deletions', diff --git a/schema/gosling.schema.json b/schema/gosling.schema.json index 1a299ac55..5fea4031e 100644 --- a/schema/gosling.schema.json +++ b/schema/gosling.schema.json @@ -880,6 +880,110 @@ ], "type": "object" }, + "DummyTrack": { + "additionalProperties": false, + "description": "A placeholder track. In contrast to other tracks, this track does not display any data. Instead it provides empty space for third party tools to display their data on top of.", + "properties": { + "_invalidTrack": { + "description": "internal", + "type": "boolean" + }, + "assembly": { + "const": "unknown", + "description": "No assemblies can be associated with a dummy track", + "type": "string" + }, + "height": { + "description": "Specify the track height in pixels.", + "type": "number" + }, + "id": { + "description": "Assigned to `uid` in a HiGlass view config, used for API and caching.", + "type": "string" + }, + "layout": { + "const": "linear", + "description": "Only linear layout are supported at this time", + "type": "string" + }, + "orientation": { + "$ref": "#/definitions/Orientation", + "description": "Specify the orientation." + }, + "overlayOnPreviousTrack": { + "type": "boolean" + }, + "static": { + "const": true, + "description": "Whether to disable [Zooming and Panning](http://gosling-lang.org/docs/user-interaction#zooming-and-panning), __Default:__ `false`.", + "type": "boolean" + }, + "style": { + "$ref": "#/definitions/DummyTrackStyle", + "description": "Defines how the track is styled" + }, + "title": { + "description": "Text that gets shown on the DummyTrack", + "type": "string" + }, + "type": { + "const": "dummy-track", + "description": "Used to specify the dummy track", + "type": "string" + }, + "width": { + "description": "Specify the track width in pixels.", + "type": "number" + }, + "zoomLimits": { + "description": "Unused property for DummyTrack", + "items": { + "type": "null" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "DummyTrackStyle": { + "additionalProperties": false, + "properties": { + "background": { + "description": "Background color of the track", + "type": "string" + }, + "outline": { + "description": "Color of the outline of the track", + "type": "string" + }, + "textFontSize": { + "description": "Specify the font size of the title", + "type": "number" + }, + "textFontWeight": { + "description": "Specify the font weight of the title.", + "enum": [ + "bold", + "normal" + ], + "type": "string" + }, + "textStroke": { + "description": "Specify the stroke color of title.", + "type": "string" + }, + "textStrokeWidth": { + "description": "Specify the stroke width of the title.", + "type": "number" + } + }, + "type": "object" + }, "EventStyle": { "additionalProperties": false, "description": "The styles defined here will be applied to the target marks of mouse events, such as a point mark after the user clicks on it.", @@ -4982,7 +5086,17 @@ "type": "string" }, "assembly": { - "$ref": "#/definitions/Assembly", + "anyOf": [ + { + "$ref": "#/definitions/Assembly", + "description": "A string that specifies the genome builds to use. Currently support `\"hg38\"`, `\"hg19\"`, `\"hg18\"`, `\"hg17\"`, `\"hg16\"`, `\"mm10\"`, `\"mm9\"`, and `\"unknown\"`.\n\n__Note:__: with `\"unknown\"` assembly, genomic axes do not show chrN: in labels." + }, + { + "const": "unknown", + "description": "No assemblies can be associated with a dummy track", + "type": "string" + } + ], "description": "A string that specifies the genome builds to use. Currently support `\"hg38\"`, `\"hg19\"`, `\"hg18\"`, `\"hg17\"`, `\"hg16\"`, `\"mm10\"`, `\"mm9\"`, and `\"unknown\"`.\n\n__Note:__: with `\"unknown\"` assembly, genomic axes do not show chrN: in labels." }, "baselineY": { @@ -5061,7 +5175,17 @@ "type": "number" }, "layout": { - "$ref": "#/definitions/Layout", + "anyOf": [ + { + "$ref": "#/definitions/Layout", + "description": "Specify the layout type of all tracks." + }, + { + "const": "linear", + "description": "Only linear layout are supported at this time", + "type": "string" + } + ], "description": "Specify the layout type of all tracks." }, "linkingId": { @@ -5462,8 +5586,18 @@ "type": "number" }, "static": { - "description": "Whether to disable [Zooming and Panning](http://gosling-lang.org/docs/user-interaction#zooming-and-panning), __Default:__ `false`.", - "type": "boolean" + "anyOf": [ + { + "description": "Whether to disable [Zooming and Panning](http://gosling-lang.org/docs/user-interaction#zooming-and-panning), __Default:__ `false`.", + "type": "boolean" + }, + { + "const": true, + "description": "Whether to disable [Zooming and Panning](http://gosling-lang.org/docs/user-interaction#zooming-and-panning), __Default:__ `false`.", + "type": "boolean" + } + ], + "description": "Whether to disable [Zooming and Panning](http://gosling-lang.org/docs/user-interaction#zooming-and-panning), __Default:__ `false`." }, "stretch": { "type": "boolean" @@ -5489,7 +5623,16 @@ ] }, "style": { - "$ref": "#/definitions/Style", + "anyOf": [ + { + "$ref": "#/definitions/Style", + "description": "Define the [style](http://gosling-lang.org/docs/visual-channel#style-related-properties) of multive views. Will be overwritten by the style of children elements (e.g., view, track)." + }, + { + "$ref": "#/definitions/DummyTrackStyle", + "description": "Defines how the track is styled" + } + ], "description": "Define the [style](http://gosling-lang.org/docs/visual-channel#style-related-properties) of multive views. Will be overwritten by the style of children elements (e.g., view, track)." }, "subtitle": { @@ -5518,6 +5661,11 @@ }, "type": "array" }, + "type": { + "const": "dummy-track", + "description": "Used to specify the dummy track", + "type": "string" + }, "visibility": { "items": { "$ref": "#/definitions/VisibilityCondition" @@ -5649,7 +5797,20 @@ ] }, "zoomLimits": { - "$ref": "#/definitions/ZoomLimits" + "anyOf": [ + { + "$ref": "#/definitions/ZoomLimits" + }, + { + "description": "Unused property for DummyTrack", + "items": { + "type": "null" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + } + ] } }, "type": "object" @@ -8876,6 +9037,9 @@ }, { "$ref": "#/definitions/TemplateTrack" + }, + { + "$ref": "#/definitions/DummyTrack" } ] }, diff --git a/schema/higlass.schema.json b/schema/higlass.schema.json index ee0ef83bf..346fa1603 100644 --- a/schema/higlass.schema.json +++ b/schema/higlass.schema.json @@ -226,7 +226,8 @@ "gosling-track", "gosling-2d-track", "axis-track", - "text" + "text", + "dummy-track" ], "type": "string" }, diff --git a/src/core/compile.test.ts b/src/core/compile.test.ts index 9b18d821f..dd58587e4 100644 --- a/src/core/compile.test.ts +++ b/src/core/compile.test.ts @@ -40,3 +40,81 @@ describe('gosling track.id => higlass view.uid', () => { ); }); }); + +describe('Dummy track', () => { + it('compiles when layout linear', () => { + const spec: GoslingSpec = { + tracks: [ + { + type: 'dummy-track', + id: 'my-dummy-track', + title: 'Placeholder', + style: { + background: '#000', + textFontSize: 10, + textStroke: 'normal', + textStrokeWidth: 0.2 + } + } + ], + layout: 'linear' + }; + compile( + spec, + hgSpec => { + expect(hgSpec.views[0].tracks.top).toMatchInlineSnapshot(` + [ + { + "height": 130, + "options": { + "background": "#000", + "height": 130, + "textFontSize": 10, + "textStroke": "normal", + "textStrokeWidth": 0.2, + "title": "Placeholder", + "width": 600, + }, + "type": "dummy-track", + "width": 600, + }, + ] + `); + }, + [], + getTheme(), + {} + ); + }); + it('gets filtered out when layout circular', () => { + const spec: GoslingSpec = { + tracks: [ + { + type: 'dummy-track', + id: 'dummy-1' + }, + { + id: 'track-id', + data: { + type: 'csv', + url: '' + }, + mark: 'rect', + width: 100, + height: 100 + } + ], + layout: 'circular' + }; + compile( + spec, + hgSpec => { + expect(hgSpec.views).toHaveLength(1); + expect(hgSpec.views[0].tracks).not.toBeUndefined(); + }, + [], + getTheme(), + {} + ); + }); +}); diff --git a/src/core/create-higlass-models.ts b/src/core/create-higlass-models.ts index d456ba8ae..3aeb4c705 100644 --- a/src/core/create-higlass-models.ts +++ b/src/core/create-higlass-models.ts @@ -13,6 +13,7 @@ import type { import type { CompleteThemeDeep } from './utils/theme'; import type { CompileCallback } from './compile'; import { getViewApiData } from './api-data'; +import { IsDummyTrack } from './gosling.schema.guards'; export function renderHiGlass( spec: GoslingSpec, @@ -81,7 +82,7 @@ export function renderHiGlass( id: d.track.id!, spec: d.track as SingleTrack | OverlaidTrack, shape: - d.track.layout === 'linear' + d.track.layout === 'linear' || IsDummyTrack(d.track) // Dummy track is always linear ? d.boundingBox : { ...d.boundingBox, diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 8cc048db9..2f063a58f 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -12,7 +12,8 @@ import { Is2DTrack, IsXAxis, IsHiGlassMatrix, - getHiGlassColorRange + getHiGlassColorRange, + IsDummyTrack } from './gosling.schema.guards'; import { DEWFAULT_TITLE_PADDING_ON_TOP_AND_BOTTOM } from './defaults'; import type { CompleteThemeDeep } from './utils/theme'; @@ -31,6 +32,11 @@ export function goslingToHiGlass( // TODO: check whether there are multiple track.data across superposed tracks // ... + // Adds the dummy track to the HiGlass spec + if (IsDummyTrack(gosTrack)) { + hgModel.addDefaultView(gosTrack.id!).setDummyTrack(gosTrack).setLayout(layout); + return hgModel; + } // we only look into the first resolved spec to get information, such as size of the track const firstResolvedSpec = resolveSuperposedTracks(gosTrack)[0]; diff --git a/src/core/gosling.schema.guards.ts b/src/core/gosling.schema.guards.ts index 034fa6b1d..851105fef 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/core/gosling.schema.guards.ts @@ -32,7 +32,8 @@ import type { Range, TemplateTrack, MouseEventsDeep, - DataTransform + DataTransform, + DummyTrack } from './gosling.schema'; import { SUPPORTED_CHANNELS } from './mark'; import { @@ -95,6 +96,9 @@ export function IsDataTrack(_: Track): _ is DataTrack { // !!! Track might not contain `mark` when it is superposed one return !IsOverlaidTrack(_) && 'data' in _ && !('mark' in _); } +export function IsDummyTrack(_: Track): _ is DummyTrack { + return 'type' in _ && _.type == 'dummy-track'; +} export function IsDataTemplate(_: Partial): boolean { return !!('data' in _ && 'overrideTemplate' in _ && _.overrideTemplate); diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index 397041ddb..e3f6bcd4c 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -154,7 +154,7 @@ export interface CommonViewDef { } /* ----------------------------- TRACK ----------------------------- */ -export type Track = SingleTrack | OverlaidTrack | DataTrack | TemplateTrack; +export type Track = SingleTrack | OverlaidTrack | DataTrack | TemplateTrack | DummyTrack; export interface CommonTrackDef extends CommonViewDef { /** Assigned to `uid` in a HiGlass view config, used for API and caching. */ @@ -209,6 +209,53 @@ export interface CommonTrackDef extends CommonViewDef { export interface DataTrack extends CommonTrackDef { data: DataDeep; } +/** + * A placeholder track. In contrast to other tracks, this track does not display any data. Instead it provides + * empty space for third party tools to display their data on top of. + */ +export interface DummyTrack + extends Pick< + CommonTrackDef, + | 'width' + | 'height' + | 'id' + | 'title' + | '_invalidTrack' + | 'overlayOnPreviousTrack' + | 'orientation' + | 'layout' + | 'static' + | 'assembly' + > { + /** Used to specify the dummy track */ + type: 'dummy-track'; + /** Text that gets shown on the DummyTrack */ + title?: string; + /** Defines how the track is styled */ + style?: DummyTrackStyle; + /** Only linear layout are supported at this time */ + layout?: 'linear'; + static?: true; + /** Unused property for DummyTrack */ + zoomLimits?: [null, null]; + /** No assemblies can be associated with a dummy track */ + assembly?: 'unknown'; +} + +export interface DummyTrackStyle { + /** Background color of the track */ + background?: string; + /** Color of the outline of the track */ + outline?: string; + /** Specify the font size of the title */ + textFontSize?: number; + /** Specify the font weight of the title. */ + textFontWeight?: 'bold' | 'normal'; + /** Specify the stroke color of title. */ + textStroke?: string; + /** Specify the stroke width of the title. */ + textStrokeWidth?: number; +} /* ----------------------------- MARK ----------------------------- */ export type Mark = diff --git a/src/core/higlass-model.ts b/src/core/higlass-model.ts index bf29ad4c1..31ab1220c 100644 --- a/src/core/higlass-model.ts +++ b/src/core/higlass-model.ts @@ -1,7 +1,7 @@ import * as uuid from 'uuid'; import type { HiGlassSpec, Track } from './higlass.schema'; import HiGlassSchema from '../../schema/higlass.schema.json'; -import type { Assembly, AxisPosition, Domain, Orientation, ZoomLimits } from './gosling.schema'; +import type { Assembly, AxisPosition, Domain, DummyTrack, Orientation, ZoomLimits } from './gosling.schema'; import { getNumericDomain } from './utils/scales'; import type { RelativePosition } from './utils/bounding-box'; import { validateSpec } from './utils/validate'; @@ -120,6 +120,27 @@ export class HiGlassModel { return this; } + /** + * Add a dummy track to the last view + * @param track + */ + public setDummyTrack(track: DummyTrack) { + if (this.getLastView()) { + this.getLastView().tracks.top?.push({ + type: 'dummy-track', + width: track.width, + height: track.height, + options: { + width: track.width, + height: track.height, + title: track.title, + ...track.style + } + }); + } + return this; + } + public addBrush( layout: 'circular' | 'linear', viewId: string, diff --git a/src/core/higlass.schema.ts b/src/core/higlass.schema.ts index 99a766469..42d787608 100644 --- a/src/core/higlass.schema.ts +++ b/src/core/higlass.schema.ts @@ -221,4 +221,5 @@ export type EnumTrackType = | 'gosling-track' | 'gosling-2d-track' | 'axis-track' - | 'text'; + | 'text' + | 'dummy-track'; diff --git a/src/core/init.ts b/src/core/init.ts index b42aef6aa..e3ca119f7 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -5,6 +5,7 @@ import { TextTrack } from 'higlass-text'; import { AxisTrack } from '@gosling-genomic-axis'; import { BrushTrack } from '@gosling-brush'; import { GoslingTrack } from '@gosling-track'; +import { DummyTrack } from '@gosling-lang/dummy-track'; import * as dataFetchers from '@data-fetchers'; let once = false; @@ -64,6 +65,15 @@ export function init() { config: BrushTrack.config }); + /** + * Register a dummy plugin track to HiGlassComponent + */ + higlassRegister({ + name: 'DummyTrack', + track: DummyTrack, + config: DummyTrack.config + }); + /** * Register data fetchers to HiGlassComponent */ diff --git a/src/core/utils/bounding-box.ts b/src/core/utils/bounding-box.ts index e58aa70bb..051c193b3 100644 --- a/src/core/utils/bounding-box.ts +++ b/src/core/utils/bounding-box.ts @@ -1,5 +1,5 @@ import type { MultipleViews, CommonViewDef, GoslingSpec, Track, SingleView } from '@gosling.schema'; -import { Is2DTrack, IsOverlaidTrack, IsXAxis, IsYAxis } from '../gosling.schema.guards'; +import { Is2DTrack, IsDummyTrack, IsOverlaidTrack, IsXAxis, IsYAxis } from '../gosling.schema.guards'; import { HIGLASS_AXIS_SIZE } from '../higlass-model'; import { DEFAULT_CIRCULAR_VIEW_PADDING, @@ -80,7 +80,6 @@ export function getRelativeTrackInfo( // Collect track information including spec, bounding boxes, and RGL' `layout`. traverseAndCollectTrackInfo(spec, trackInfos); // RGL parameter (`layout`) is not deteremined yet since we do not know the entire size of vis yet. - // Get the size of entire visualization. const size = getBoundingBox(trackInfos); @@ -129,8 +128,6 @@ export function getRelativeTrackInfo( _.layout.h = pixelPreciseMarginPadding ? _.boundingBox.height : (_.boundingBox.height / size.height) * 12; }); - // console.log(trackInfos); - return { trackInfos, size }; } @@ -223,8 +220,8 @@ function traverseAndCollectTrackInfo( if (getNumOfXAxes([track]) === 1) { track.height += HIGLASS_AXIS_SIZE; } - - if (Is2DTrack(resolveSuperposedTracks(track)[0]) && getNumOfYAxes([track]) === 1) { + const singleTrack = resolveSuperposedTracks(track); + if (singleTrack.length > 0 && Is2DTrack(singleTrack[0]) && getNumOfYAxes([track]) === 1) { // If this is a 2D track (e.g., matrix), we need to reserve a space for the y-axis track cumWidth += HIGLASS_AXIS_SIZE; } @@ -319,6 +316,10 @@ function traverseAndCollectTrackInfo( // const numXAxes = getNumOfXAxes(cTracks.map(info => info.track)); cTracks.forEach((t, i) => { + // at this time, circular dummy tracks are not supported, so we don't do anything here + if (IsDummyTrack(t.track)) { + return; + } t.track.layout = 'circular'; t.track.outerRadius = TOTAL_RADIUS - PADDING - ((t.boundingBox.y - dy) / cumHeight) * TOTAL_RING_SIZE; diff --git a/src/core/utils/overlay.ts b/src/core/utils/overlay.ts index 7f5993a69..4eac75734 100644 --- a/src/core/utils/overlay.ts +++ b/src/core/utils/overlay.ts @@ -1,12 +1,12 @@ import type { AxisPosition, SingleTrack, OverlaidTrack, Track, ChannelDeep, DataDeep } from '../gosling.schema'; -import { IsChannelDeep, IsDataTrack, IsOverlaidTrack, IsSingleTrack } from '../gosling.schema.guards'; +import { IsChannelDeep, IsDataTrack, IsOverlaidTrack, IsSingleTrack, IsDummyTrack } from '../gosling.schema.guards'; /** * Resolve superposed tracks into multiple track specifications. * Some options are corrected to ensure the resolved tracks use consistent visual properties, such as the existence of the axis for genomic coordinates. */ export function resolveSuperposedTracks(track: Track): SingleTrack[] { - if (IsDataTrack(track)) { + if (IsDataTrack(track) || IsDummyTrack(track)) { // no BasicSingleTrack to return return []; } diff --git a/src/core/utils/spec-preprocess.ts b/src/core/utils/spec-preprocess.ts index 51530c519..b40d6bf3e 100644 --- a/src/core/utils/spec-preprocess.ts +++ b/src/core/utils/spec-preprocess.ts @@ -19,7 +19,8 @@ import { IsOverlaidTrack, IsFlatTracks, IsStackedTracks, - Is2DTrack + Is2DTrack, + IsDummyTrack } from '../gosling.schema.guards'; import { DEFAULT_INNER_RADIUS_PROP, @@ -182,7 +183,6 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if ('tracks' in spec) { let tracks: Track[] = convertToFlatTracks(spec); - // !!! Be aware that this should be taken before fixing `overlayOnPreviousTrack` options. /** * Spread superposed tracks if they are assigned to different data spec. @@ -204,7 +204,6 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if (!track.height) { track.height = Is2DTrack(track) ? DEFAULT_TRACK_SIZE_2D : DEFAULT_TRACK_HEIGHT_LINEAR; } - /** * Process a stack option. */ @@ -245,7 +244,6 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare */ if (track.layout) track.layout = undefined; if (track.zoomLimits) track.zoomLimits = undefined; - /** * Override options received from the parent */ @@ -255,9 +253,21 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare if (track.static === undefined) track.static = spec.static !== undefined ? spec.static : false; if (!track.zoomLimits) track.zoomLimits = spec.zoomLimits; + /** + * Dummy track can't have a circular layout + */ + if (track.layout == 'circular' && IsDummyTrack(track)) { + track._invalidTrack = true; + return; + } + // Override styles track.style = getStyleOverridden(spec.style, track.style); if (IsOverlaidTrack(track)) { + // Remove the dummy tracks from an overlay track + track.overlay = track.overlay.filter(overlayTrack => { + return !('type' in overlayTrack && overlayTrack.type == 'dummy-track'); + }); track.overlay.forEach(o => { o.style = getStyleOverridden(track.style, o.style); }); @@ -442,6 +452,8 @@ export function traverseToFixSpecDownstream(spec: GoslingSpec | SingleView, pare track.assembly = array[i - 1].assembly; } }); + // Filter out any invalid tracks + tracks = tracks.filter(track => !track._invalidTrack); spec.tracks = tracks; } else { @@ -521,7 +533,7 @@ export function getMultivecTemplate( */ export function overrideDataTemplates(spec: GoslingSpec) { traverseTracks(spec, (t, i, ts) => { - if (!t.data || !IsDataDeepTileset(t.data)) { + if (!('data' in t) || !t.data || !IsDataDeepTileset(t.data)) { // if `data` is not specified, we can not provide a correct template. return; } diff --git a/src/dummy-track/dummy-track.ts b/src/dummy-track/dummy-track.ts new file mode 100644 index 000000000..691628112 --- /dev/null +++ b/src/dummy-track/dummy-track.ts @@ -0,0 +1,70 @@ +import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../core/utils/define-plugin-track'; +import { publish } from '../core/pubsub'; +import { type DummyTrackStyle } from '@gosling.schema'; + +interface DummyTrackOptions extends DummyTrackStyle { + title: string; + height: number; + width: number; +} + +const config: TrackConfig = { + type: 'dummy-track', + defaultOptions: { + height: 0, // default height gets set in when spec is preprocessed + width: 0, // default width gets set in when spec is preprocessed + title: '', + background: '#fff', + textFontSize: 12, + textFontWeight: 'normal', + textStroke: '#000', + textStrokeWidth: 0.1, + outline: '#fff' + } +}; + +const factory: PluginTrackFactory = (HGC, context, options) => { + // Services + const { SVGTrack } = HGC.tracks; + + class DummyTrackClass extends SVGTrack { + constructor() { + super(context, options); + this.#drawBackground(); + this.#drawText(); + publish('onNewTrack', { + id: context.viewUid + }); + } + + #drawBackground() { + this.gMain + .append('rect') + .attr('fill', options.background) + .attr('x', 0) + .attr('y', 0) + .attr('width', options.width) + .attr('height', options.height) + .style('stroke', options.outline); + } + /** + * Draws the title of the dummy track + */ + #drawText() { + this.gMain + .append('text') + .attr('x', options.width / 2) + .attr('y', (options.height + options.textFontSize!) / 2) + .style('text-anchor', 'middle') + .style('font-size', `${options.textFontSize}px`) + .style('font-weight', options.textFontWeight) + .style('stroke', options.textStroke) + .style('stroke-width', options.textStrokeWidth) + .text(options.title); + } + } + + return new DummyTrackClass(); +}; + +export default createPluginTrack(config, factory); diff --git a/src/dummy-track/index.ts b/src/dummy-track/index.ts new file mode 100644 index 000000000..bc2d89c7d --- /dev/null +++ b/src/dummy-track/index.ts @@ -0,0 +1 @@ +export { default as DummyTrack } from './dummy-track'; diff --git a/src/missing-types.d.ts b/src/missing-types.d.ts index c5d125743..aaae81967 100644 --- a/src/missing-types.d.ts +++ b/src/missing-types.d.ts @@ -123,6 +123,7 @@ declare module '@higlass/services' { declare module '@higlass/tracks' { import type * as d3 from 'd3'; import type * as PIXI from 'pixi.js'; + import type * as d3Selection from 'd3-selection'; import type { TilesetInfo, ColorRGBA } from '@higlass/services'; import type { ChromInfo } from '@higlass/utils'; @@ -565,6 +566,15 @@ declare module '@higlass/tracks' { exportSVG(): [HTMLElement, HTMLElement]; } + export class SVGTrack extends Track { + /* Properties */ + gMain: d3Selection.Selection; + clipUid: string; + clipRect: d3Selection.Selection; + /* Constructor */ + constructor(context: Context, options: Options); + } + /* eslint-disable-next-line @typescript-eslint/ban-types */ type LiteralUnion = T | (U & {}); diff --git a/tsconfig.json b/tsconfig.json index da6db8b11..147cc628b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ "@gosling-track": ["./src/gosling-track/index.ts"], "@gosling-genomic-axis": ["./src/gosling-genomic-axis/index.ts"], "@gosling-brush": ["./src/gosling-brush/index.ts"], + "@gosling-lang/dummy-track": ["./src/dummy-track/index.ts"], "@data-fetchers": ["./src/data-fetchers/index.ts"], "zlib": ["./src/alias/zlib.ts"] }, diff --git a/vite.config.js b/vite.config.js index 5114ff714..3e3b9ed34 100644 --- a/vite.config.js +++ b/vite.config.js @@ -61,6 +61,7 @@ const alias = { "@gosling-mouse-event": path.resolve(__dirname, "./src/gosling-mouse-event/index.ts"), "@gosling-genomic-axis": path.resolve(__dirname, "./src/gosling-genomic-axis/index.ts"), "@gosling-brush": path.resolve(__dirname, "./src/gosling-brush/index.ts"), + "@gosling-lang/dummy-track": path.resolve(__dirname, "./src/dummy-track/index.ts"), "@data-fetchers": path.resolve(__dirname, "./src/data-fetchers/index.ts"), zlib: path.resolve(__dirname, './src/alias/zlib.ts'), uuid: path.resolve(__dirname, './node_modules/uuid/dist/esm-browser/index.js') From 6654e0d315a40978ce26d97fdcf38168472a088b Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Tue, 1 Aug 2023 09:42:01 -0400 Subject: [PATCH 17/25] fix(core): do not override ID in tracks from views (#954) * fix: do not override ID in tracks from views * chore: minor edit to the code * test: add test * chore: cleanup --- src/core/utils/overlay.ts | 4 +--- src/core/utils/spec-preprocess.test.ts | 8 ++++++++ src/core/utils/spec-preprocess.ts | 6 ++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/core/utils/overlay.ts b/src/core/utils/overlay.ts index 4eac75734..58b38ed94 100644 --- a/src/core/utils/overlay.ts +++ b/src/core/utils/overlay.ts @@ -77,9 +77,7 @@ export function spreadTracksByData(tracks: Track[]): Track[] { return [t]; } - const base: SingleTrack = JSON.parse(JSON.stringify(t)); - delete (base as Partial).overlay; // remove `overlay` from the base spec - + const base: Partial = {...t, id: undefined, overlay: undefined }; const spread: Track[] = []; const original: OverlaidTrack = JSON.parse(JSON.stringify(base)); original.overlay = []; diff --git a/src/core/utils/spec-preprocess.test.ts b/src/core/utils/spec-preprocess.test.ts index 6710ef9f5..156a7fd16 100644 --- a/src/core/utils/spec-preprocess.test.ts +++ b/src/core/utils/spec-preprocess.test.ts @@ -297,6 +297,14 @@ describe('Spec Preprocess', () => { }); expect(flat).toHaveLength(0); } + { + const flat = convertToFlatTracks({ + id: 'view-id', + tracks: [{}] + }); + expect(flat).toHaveLength(1); + expect(flat[0].id).toBeUndefined(); + } { const flat = convertToFlatTracks({ tracks: [{ ...dummySpec, title: 'A' }] diff --git a/src/core/utils/spec-preprocess.ts b/src/core/utils/spec-preprocess.ts index b40d6bf3e..0dcb524a7 100644 --- a/src/core/utils/spec-preprocess.ts +++ b/src/core/utils/spec-preprocess.ts @@ -96,8 +96,7 @@ export function traverseViewArrangements(spec: GoslingSpec, callback: (tv: Multi export function convertToFlatTracks(spec: SingleView): Track[] { if (IsFlatTracks(spec)) { // This is already `FlatTracks`, so just override the view definition - const base = JSON.parse(JSON.stringify(spec)); - delete (base as any).tracks; + const base = {...spec, tracks: undefined, id: undefined }; return spec.tracks .filter(track => !track._invalidTrack) .map(track => Object.assign(JSON.parse(JSON.stringify(base)), track) as SingleTrack); @@ -118,8 +117,7 @@ export function convertToFlatTracks(spec: SingleView): Track[] { } as Track); } else { // Override track definitions from views - const base = JSON.parse(JSON.stringify(spec)); - delete (base as any).tracks; + const base = {...spec, tracks: undefined, id: undefined }; const newSpec = Object.assign(JSON.parse(JSON.stringify(base)), track) as SingleTrack; newTracks.push(newSpec); } From 1c2b7669660856cae98f88ba38d9326b788c75e1 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Tue, 1 Aug 2023 09:42:57 -0400 Subject: [PATCH 18/25] feat(api): consistent track/view IDs for JS API (#944) * fix: rename parameter/value names regarding view ids * docs: add comment statement * chore: remove comment * feat: support mapping between IDs * chore: add tests and fix type import * chore: format * chore: more test * chore: format * chore: call publish * fix: allow overwritting ids if identical combination * chore: add root level ID as well * chore: use id in the lowest track * chore: use structuredClone Co-authored-by: etowahadams --------- Co-authored-by: etowahadams --- src/core/api.ts | 58 ++++++++----- src/core/compile.test.ts | 126 +++++++++++++++++++++++++++- src/core/compile.ts | 9 +- src/core/create-higlass-models.ts | 20 +++-- src/core/gosling-component.tsx | 8 +- src/core/gosling-embed.ts | 4 +- src/core/gosling-to-higlass.test.ts | 7 +- src/core/gosling-to-higlass.ts | 33 ++++++-- src/core/gosling.schema.ts | 1 - src/core/track-and-view-ids.ts | 43 ++++++++++ src/core/utils/linking.test.ts | 4 +- src/core/utils/linking.ts | 8 +- src/gosling-track/gosling-track.ts | 22 +++-- 13 files changed, 288 insertions(+), 55 deletions(-) diff --git a/src/core/api.ts b/src/core/api.ts index 2b561cb15..a06c31eaf 100644 --- a/src/core/api.ts +++ b/src/core/api.ts @@ -5,7 +5,7 @@ import type { HiGlassSpec } from '@higlass.schema'; import { subscribe, unsubscribe } from './pubsub'; import { computeChromSizes, GenomicPositionHelper } from './utils/assembly'; import type { CompleteThemeDeep } from './utils/theme'; -import { traverseViewsInViewConfig } from './utils/view-config'; +import type { IdTable } from './track-and-view-ids'; /** * Information of suggested genes. @@ -26,7 +26,11 @@ export interface GoslingApi { zoomToGene(viewId: string, gene: string, padding?: number, duration?: number): void; suggestGene(viewId: string, keyword: string, callback: (suggestions: GeneSuggestion[]) => void): void; getTracksAndViews(): VisUnitApiData[]; - getViewIds(): string[]; + /** + * Get an array of all available track IDs that are either specified by users or auto-generated by the compiler. + * This can be used to call other API functions, e.g., `getTrack('track-1')`. + */ + getTrackIds(): string[]; getTracks(): TrackApiData[]; getTrack(trackId: string): TrackApiData | undefined; getViews(): ViewApiData[]; @@ -45,8 +49,23 @@ export function createApi( hg: Readonly, hgSpec: HiGlassSpec | undefined, tracksAndViews: readonly VisUnitApiData[], - theme: Required + theme: Required, + idTable: Readonly ): GoslingApi { + const idTableCopy = structuredClone(idTable); + /** + * Get the HiGlass view ID from the Gosling track ID. + */ + const getHgViewId = (trackId: string) => { + const viewId = idTableCopy[trackId]; + if (!viewId) { + console.warn(`Unable to find the track ID, named ${trackId}.`); + } + return viewId ?? trackId; + }; + const getTrackIds = () => { + return Object.keys(idTableCopy); + }; const getTracksAndViews = () => { return [...tracksAndViews]; }; @@ -108,32 +127,29 @@ export function createApi( return { subscribe, unsubscribe, - zoomTo: (viewId, position, padding = 0, duration = 1000) => { + zoomTo: (trackId, position, padding = 0, duration = 1000) => { // Accepted input: 'chr1' or 'chr1:1-1000' - const assembly = getTrack(viewId)?.spec.assembly; + const assembly = getTrack(trackId)?.spec.assembly; const manager = GenomicPositionHelper.fromString(position); const absCoordinates = manager.toAbsoluteCoordinates(assembly, padding); - hg.api.zoomTo(viewId, ...absCoordinates, ...absCoordinates, duration); + const hgViewId = getHgViewId(trackId); + hg.api.zoomTo(hgViewId, ...absCoordinates, ...absCoordinates, duration); }, - zoomToExtent: (viewId, duration = 1000) => { - const assembly = getTrack(viewId)?.spec.assembly; + zoomToExtent: (trackId, duration = 1000) => { + const assembly = getTrack(trackId)?.spec.assembly; const [start, end] = [0, computeChromSizes(assembly).total]; - hg.api.zoomTo(viewId, start, end, start, end, duration); + const hgViewId = getHgViewId(trackId); + hg.api.zoomTo(hgViewId, start, end, start, end, duration); }, - zoomToGene: (viewId, gene, padding = 0, duration = 1000) => { - hg.api.zoomToGene(viewId, gene, padding, duration); + zoomToGene: (trackId, gene, padding = 0, duration = 1000) => { + const hgViewId = getHgViewId(trackId); + hg.api.zoomToGene(hgViewId, gene, padding, duration); }, - suggestGene: (viewId: string, keyword: string, callback: (suggestions: GeneSuggestion[]) => void) => { - hg.api.suggestGene(viewId, keyword, callback); - }, - getViewIds: () => { - if (!hgSpec) return []; - const ids: string[] = []; - traverseViewsInViewConfig(hgSpec, view => { - if (view.uid) ids.push(view.uid); - }); - return ids; + suggestGene: (trackId: string, keyword: string, callback: (suggestions: GeneSuggestion[]) => void) => { + const hgViewId = getHgViewId(trackId); + hg.api.suggestGene(hgViewId, keyword, callback); }, + getTrackIds, getTracksAndViews, getTracks, getTrack, diff --git a/src/core/compile.test.ts b/src/core/compile.test.ts index dd58587e4..16c5bf418 100644 --- a/src/core/compile.test.ts +++ b/src/core/compile.test.ts @@ -11,7 +11,7 @@ describe('compile', () => { }); }); -describe('gosling track.id => higlass view.uid', () => { +describe('Create correct mapping table between Gosling track IDs and HiGlass view IDs', () => { it('track.id === view.uid', () => { const spec: GoslingSpec = { tracks: [ @@ -39,6 +39,130 @@ describe('gosling track.id => higlass view.uid', () => { {} ); }); + it('Track IDs should not be lost in overlaid tracks', () => { + const spec: GoslingSpec = { + views: [ + { + tracks: [{ id: 's1' }, { id: 's2' }, { id: 's3' }] + }, + { + id: 'o-root', + alignment: 'overlay', + tracks: [{ id: 'o1' }, { id: 'o2' }, { id: 'o3' }] + } + ] + }; + compile( + spec, + (h, s, g, t, table) => { + expect(table).toMatchInlineSnapshot(` + { + "o1": "o1", + "o2": "o1", + "o3": "o1", + "s1": "s1", + "s2": "s2", + "s3": "s3", + } + `); + }, + [], + getTheme(), + {} + ); + }); + it('Used the root level ID in overlaid tracks when IDs are missing in children', () => { + const spec: GoslingSpec = { + views: [ + { + tracks: [{ id: 's1' }, { id: 's2' }, { id: 's3' }] + }, + { + id: 'o-root', + alignment: 'overlay', + tracks: [{}, {}, {}] + } + ] + }; + compile( + spec, + (h, s, g, t, table) => { + expect(table).toMatchInlineSnapshot(` + { + "o-root": "o-root", + "s1": "s1", + "s2": "s2", + "s3": "s3", + } + `); + }, + [], + getTheme(), + {} + ); + }); + const nestedSpec: GoslingSpec = { + views: [ + { + tracks: [ + { id: 's1' }, + { id: 's2' }, + { + alignment: 'overlay', + tracks: [{ id: 'o1' }, { id: 'o2' }, { id: 'o3' }] + } + ] + }, + { + alignment: 'overlay', + tracks: [{ id: 'o4' }, { id: 'o5' }, { id: 'o6' }] + } + ] + }; + it('Track IDs should not be lost in nested tracks', () => { + compile( + nestedSpec, + (h, s, g, t, table) => { + expect(table).toMatchInlineSnapshot(` + { + "o1": "o1", + "o2": "o1", + "o3": "o1", + "o4": "o4", + "o5": "o4", + "o6": "o4", + "s1": "s1", + "s2": "s2", + } + `); + }, + [], + getTheme(), + {} + ); + }); + it('Track IDs should not be lost in circular views', () => { + compile( + { ...nestedSpec, layout: 'circular' }, + (h, s, g, t, table) => { + expect(table).toMatchInlineSnapshot(` + { + "o1": "o1", + "o2": "o1", + "o3": "o1", + "o4": "o4", + "o5": "o4", + "o6": "o4", + "s1": "s1", + "s2": "s2", + } + `); + }, + [], + getTheme(), + {} + ); + }); }); describe('Dummy track', () => { diff --git a/src/core/compile.ts b/src/core/compile.ts index af2278fa0..d306d15e1 100644 --- a/src/core/compile.ts +++ b/src/core/compile.ts @@ -6,9 +6,16 @@ import { getRelativeTrackInfo, type Size } from './utils/bounding-box'; import type { CompleteThemeDeep } from './utils/theme'; import { renderHiGlass as createHiGlassModels } from './create-higlass-models'; import { manageResponsiveSpecs } from './responsive'; +import type { IdTable } from './track-and-view-ids'; /** The callback function called everytime after the spec has been compiled */ -export type CompileCallback = (hg: HiGlassSpec, size: Size, gs: GoslingSpec, tracksAndViews: VisUnitApiData[]) => void; +export type CompileCallback = ( + hg: HiGlassSpec, + size: Size, + gs: GoslingSpec, + tracksAndViews: VisUnitApiData[], + idTable: IdTable +) => void; export function compile( spec: GoslingSpec, diff --git a/src/core/create-higlass-models.ts b/src/core/create-higlass-models.ts index 3aeb4c705..8d38a3126 100644 --- a/src/core/create-higlass-models.ts +++ b/src/core/create-higlass-models.ts @@ -13,6 +13,7 @@ import type { import type { CompleteThemeDeep } from './utils/theme'; import type { CompileCallback } from './compile'; import { getViewApiData } from './api-data'; +import { GoslingToHiGlassIdMapper } from './track-and-view-ids'; import { IsDummyTrack } from './gosling.schema.guards'; export function renderHiGlass( @@ -29,10 +30,13 @@ export function renderHiGlass( // HiGlass model const hgModel = new HiGlassModel(); + // A mapping table between Gosling track IDs and HiGlass view IDs + const idMapper = new GoslingToHiGlassIdMapper(); + /* Update the HiGlass model by iterating tracks */ trackInfos.forEach(tb => { const { track, boundingBox: bb, layout } = tb; - goslingToHiGlass(hgModel, track, bb, layout, theme); + goslingToHiGlass(hgModel, track, bb, layout, theme, idMapper); }); /* Add linking information to the HiGlass model */ @@ -45,9 +49,9 @@ export function renderHiGlass( .forEach(info => { hgModel.addBrush( info.layout, - info.viewId, + info.hgViewId, theme, - linkingInfos.find(d => !d.isBrush && d.linkId === info.linkId)?.viewId, + linkingInfos.find(d => !d.isBrush && d.linkId === info.linkId)?.hgViewId, info.style ); }); @@ -57,8 +61,8 @@ export function renderHiGlass( linkingInfos .filter(d => !d.isBrush) .forEach(d => { - hgModel.spec().zoomLocks.locksByViewUid[d.viewId] = d.linkId; - hgModel.spec().locationLocks.locksByViewUid[d.viewId] = d.linkId; + hgModel.spec().zoomLocks.locksByViewUid[d.hgViewId] = d.linkId; + hgModel.spec().locationLocks.locksByViewUid[d.hgViewId] = d.linkId; }); // fill `locksDict` @@ -72,8 +76,8 @@ export function renderHiGlass( .filter(d => !d.isBrush) .filter(d => d.linkId === linkId) .forEach(d => { - hgModel.spec().zoomLocks.locksDict[linkId][d.viewId] = [124625310.5, 124625310.5, 249250.621]; - hgModel.spec().locationLocks.locksDict[linkId][d.viewId] = [124625310.5, 124625310.5, 249250.621]; + hgModel.spec().zoomLocks.locksDict[linkId][d.hgViewId] = [124625310.5, 124625310.5, 249250.621]; + hgModel.spec().locationLocks.locksDict[linkId][d.hgViewId] = [124625310.5, 124625310.5, 249250.621]; }); }); @@ -105,5 +109,5 @@ export function renderHiGlass( ...views.map(d => ({ ...d, type: 'view' } as VisUnitApiData)) ]; - callback(hgModel.spec(), getBoundingBox(trackInfos), spec, tracksAndViews); + callback(hgModel.spec(), getBoundingBox(trackInfos), spec, tracksAndViews, idMapper.getTable()); } diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index 85fa2b874..89aa3b08b 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -11,6 +11,7 @@ import { omitDeep } from './utils/omit-deep'; import { isEqual } from 'lodash-es'; import * as uuid from 'uuid'; import { publish } from './pubsub'; +import type { IdTable } from './track-and-view-ids'; // Before rerendering, wait for a few time so that HiGlass container is resized already. // If HiGlass is rendered and then the container resizes, the viewport position changes, unmatching `xDomain` specified by users. @@ -43,6 +44,8 @@ export const GoslingComponent = forwardRef((props, const wrapperParentSize = useRef(); const prevSpec = useRef(); const tracksAndViews = useRef([]); + /** A mapping table that connects between Gosling track IDs to corresponding HiGlas view IDs */ + const idTable = useRef({}); // HiGlass API // https://dev.to/wojciechmatuszewski/mutable-and-immutable-useref-semantics-with-react-typescript-30c9 @@ -73,7 +76,7 @@ export const GoslingComponent = forwardRef((props, () => { const hgApi = refAsReadonlyProxy(hgRef); const visUnits = refAsReadonlyProxy(tracksAndViews); - const api = createApi(hgApi, viewConfig, visUnits, theme); + const api = createApi(hgApi, viewConfig, visUnits, theme, idTable.current); return { api, hgApi }; }, [viewConfig, theme] @@ -91,7 +94,7 @@ export const GoslingComponent = forwardRef((props, gosling.compile( props.spec, - (newHiGlassSpec, newSize, newGoslingSpec, newTracksAndViews) => { + (newHiGlassSpec, newSize, newGoslingSpec, newTracksAndViews, newIdTable) => { // TODO: `linkingId` should be updated // We may not want to re-render this if ( @@ -121,6 +124,7 @@ export const GoslingComponent = forwardRef((props, publishOnNewView(newTracksAndViews); prevSpec.current = newGoslingSpec; tracksAndViews.current = newTracksAndViews; + idTable.current = newIdTable; }, [...GoslingTemplates], // TODO: allow user definitions theme, diff --git a/src/core/gosling-embed.ts b/src/core/gosling-embed.ts index ed9ed1243..261fb4aff 100644 --- a/src/core/gosling-embed.ts +++ b/src/core/gosling-embed.ts @@ -85,9 +85,9 @@ export function embed(element: HTMLElement, spec: GoslingSpec, opts: GoslingEmbe compile( spec, - async (hsSpec, size, _, trackInfos) => { + async (hsSpec, size, _, trackInfos, idTable) => { const hg = await launchHiglass(element, hsSpec, size, options); - const api = createApi(hg, hsSpec, trackInfos, theme); + const api = createApi(hg, hsSpec, trackInfos, theme, idTable); resolve(api); }, [...GoslingTemplates], diff --git a/src/core/gosling-to-higlass.test.ts b/src/core/gosling-to-higlass.test.ts index c1cb1d3e2..e68db93b9 100644 --- a/src/core/gosling-to-higlass.test.ts +++ b/src/core/gosling-to-higlass.test.ts @@ -5,6 +5,7 @@ import { convertToFlatTracks } from './utils/spec-preprocess'; import { getTheme } from './utils/theme'; import type { SingleTrack } from './gosling.schema'; +import { GoslingToHiGlassIdMapper } from './track-and-view-ids'; describe('Should convert gosling spec to higlass view config.', () => { it('Should return a generated higlass view config correctly', () => { @@ -24,7 +25,8 @@ describe('Should convert gosling spec to higlass view config.', () => { w: 12, h: 12 }, - getTheme() + getTheme(), + new GoslingToHiGlassIdMapper() ).spec(); expect(Object.keys(higlass)).not.toHaveLength(0); }); @@ -47,7 +49,8 @@ describe('Should convert gosling spec to higlass view config.', () => { w: 12, h: 12 }, - getTheme() + getTheme(), + new GoslingToHiGlassIdMapper() ).spec(); expect(higlass.views).toHaveLength(0); }); diff --git a/src/core/gosling-to-higlass.ts b/src/core/gosling-to-higlass.ts index 2f063a58f..45e83b38f 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/core/gosling-to-higlass.ts @@ -1,3 +1,4 @@ +import * as uuid from 'uuid'; import type { Track as HiGlassTrack } from './higlass.schema'; import { HiGlassModel, HIGLASS_AXIS_SIZE } from './higlass-model'; import { parseServerAndTilesetUidFromUrl } from './utils'; @@ -18,6 +19,7 @@ import { import { DEWFAULT_TITLE_PADDING_ON_TOP_AND_BOTTOM } from './defaults'; import type { CompleteThemeDeep } from './utils/theme'; import { DEFAULT_TEXT_STYLE } from './utils/text-style'; +import type { GoslingToHiGlassIdMapper } from './track-and-view-ids'; /** * Convert a gosling track into a HiGlass view and add it into a higlass model. @@ -27,7 +29,8 @@ export function goslingToHiGlass( gosTrack: Track, bb: BoundingBox, layout: RelativePosition, - theme: Required + theme: Required, + idMapper: GoslingToHiGlassIdMapper ): HiGlassModel { // TODO: check whether there are multiple track.data across superposed tracks // ... @@ -38,7 +41,23 @@ export function goslingToHiGlass( return hgModel; } // we only look into the first resolved spec to get information, such as size of the track - const firstResolvedSpec = resolveSuperposedTracks(gosTrack)[0]; + const resolvedSpecs = resolveSuperposedTracks(gosTrack); + const firstResolvedSpec = resolvedSpecs[0]; + + // If missing, create a unique track ID that will be used as HiGlass view ID for caching + const trackId = firstResolvedSpec.id ?? uuid.v4(); + if (!firstResolvedSpec.id) { + firstResolvedSpec.id = trackId; + } + + // Store the mapping between Gosling track ID and HiGlass view ID so that any lost track IDs + // can be recovered and used for JS APIs. + resolvedSpecs.forEach(spec => { + // if `id` is not defined, no need to store it in the table + if (spec.id) { + idMapper.addMapping(spec.id, trackId); + } + }); const assembly = firstResolvedSpec.assembly; @@ -74,13 +93,15 @@ export function goslingToHiGlass( ? HIGLASS_AXIS_SIZE : 0); const hgTrack: HiGlassTrack = { - uid: `${firstResolvedSpec.id}-track`, // This is being used to cache the visualization + uid: `${trackId}-track`, // This is being used to cache the visualization type: Is2DTrack(firstResolvedSpec) ? 'gosling-2d-track' : 'gosling-track', server, tilesetUid, width, height, options: { + id: trackId, + siblingIds: idMapper.getSiblingGoslingIds(trackId), /* Mouse hover position */ showMousePosition: firstResolvedSpec.layout === 'circular' ? false : theme.root.showMousePosition, // show mouse position only for linear tracks // TODO: or vertical mousePositionColor: theme.root.mousePositionColor, @@ -173,7 +194,7 @@ export function goslingToHiGlass( hgModel .setViewOrientation(firstResolvedSpec.orientation) // TODO: Orientation should be assigned to 'individual' views .setAssembly(assembly) // TODO: Assembly should be assigned to 'individual' views - .addDefaultView(firstResolvedSpec.id!, assembly) + .addDefaultView(trackId, assembly) .setDomain(xDomain, yDomain ?? xDomain) .adjustDomain(firstResolvedSpec.orientation, width, height) .setMainTrack(hgTrack) @@ -230,7 +251,7 @@ export function goslingToHiGlass( ) { const narrowType = getAxisNarrowType(c as any, gosTrack.orientation, bb.width, bb.height); hgModel.setAxisTrack(channel.axis, narrowType, { - id: `${firstResolvedSpec.id}-${channel.axis}-axis`, + id: `${trackId}-${channel.axis}-axis`, layout: firstResolvedSpec.layout, innerRadius: channel.axis === 'top' @@ -252,7 +273,7 @@ export function goslingToHiGlass( hgModel.validateSpec(true); } else if (firstResolvedSpec.mark === 'header') { // `text` tracks are used to show title and subtitle of the views - hgModel.addDefaultView(`${firstResolvedSpec.id}-title`).setLayout(layout); + hgModel.addDefaultView(`${trackId}-title`).setLayout(layout); if (typeof firstResolvedSpec.title === 'string') { hgModel.setTextTrack( bb.width, diff --git a/src/core/gosling.schema.ts b/src/core/gosling.schema.ts index e3f6bcd4c..dac259bc4 100644 --- a/src/core/gosling.schema.ts +++ b/src/core/gosling.schema.ts @@ -271,7 +271,6 @@ export type Mark = | 'triangleLeft' | 'triangleRight' | 'triangleBottom' - // experimental | 'brush' // TODO: perhaps need to make this invisible to users // being used to show title/subtitle internally diff --git a/src/core/track-and-view-ids.ts b/src/core/track-and-view-ids.ts index e263fca19..3bf459765 100644 --- a/src/core/track-and-view-ids.ts +++ b/src/core/track-and-view-ids.ts @@ -1,6 +1,49 @@ import type { CommonTrackDef, CommonViewDef, GoslingSpec, PartialTrack, View } from '@gosling.schema'; import { traverseTracksAndViews } from './utils/spec-preprocess'; +/** + * A table that maps Gosling track IDs to HiGlass view IDs. + */ +export type IdTable = Record; + +/** + * Manage IDs of Gosling tracks and compiled HiGlass views. + * The HiGlass view IDs correspond to the "UIDs" of HiGlass *views*, + * which are used for calling HiGlass APIs internally in Gosling.js. + * It is 1:1 or N:1 mapping between Gosling tracks IDs and HiGlass views IDs. + * https://docs.higlass.io/view_config.html#uids + */ +export class GoslingToHiGlassIdMapper { + /** A mapping table between Gosling track IDs to HiGlass view IDs */ + #table: IdTable = {}; + + addMapping(gtId: string, hvId: string) { + if (this.#table[gtId] && this.#table[gtId] !== hvId) { + console.warn(`The track ID ${gtId} already exists but overwriting with a different ID.`); + } + this.#table[gtId] = hvId; + } + getTable() { + return this.#table; + } + getGoslingIds() { + return Object.keys(this.#table); + } + getHiGlassId(gtId: string) { + return this.#table[gtId]; + } + /** + * Get IDs of Gosling tracks that became the same HiGlass view. + * @param HiGlassId + * @returns + */ + getSiblingGoslingIds(HiGlassId: string) { + return Object.entries(this.#table) + .filter(([, hvId]) => hvId === HiGlassId) + .map(([gtId]) => gtId); + } +} + /** * Find all unique IDs of 'views' in a Gosling spec and return them as an array. * @param spec diff --git a/src/core/utils/linking.test.ts b/src/core/utils/linking.test.ts index fc29d8199..dfcbe882a 100644 --- a/src/core/utils/linking.test.ts +++ b/src/core/utils/linking.test.ts @@ -1,5 +1,6 @@ import { goslingToHiGlass } from '../gosling-to-higlass'; import { HiGlassModel } from '../higlass-model'; +import { GoslingToHiGlassIdMapper } from '../track-and-view-ids'; import { getLinkingInfo } from './linking'; import { getTheme } from './theme'; @@ -35,7 +36,8 @@ describe('Should get linking information correctly', () => { w: 12, h: 12 }, - getTheme() + getTheme(), + new GoslingToHiGlassIdMapper() ); const info = getLinkingInfo(higlass); expect(info).toHaveLength(2); diff --git a/src/core/utils/linking.ts b/src/core/utils/linking.ts index 05a1ee11e..07ece6800 100644 --- a/src/core/utils/linking.ts +++ b/src/core/utils/linking.ts @@ -9,14 +9,14 @@ import { resolveSuperposedTracks } from './overlay'; export function getLinkingInfo(hgModel: HiGlassModel) { const linkingInfo: { layout: 'circular' | 'linear'; - viewId: string; + hgViewId: string; linkId: string; isBrush: boolean; style: any; }[] = []; hgModel.spec().views.forEach(v => { - const viewId = v.uid; + const hgViewId = v.uid; // TODO: Better way to get view specifications? // Get spec of a view @@ -31,7 +31,7 @@ export function getLinkingInfo(hgModel: HiGlassModel) { } } - if (!viewId || !spec) return; + if (!hgViewId || !spec) return; const resolved = resolveSuperposedTracks(spec); @@ -42,7 +42,7 @@ export function getLinkingInfo(hgModel: HiGlassModel) { if (IsChannelDeep(channel) && 'linkingId' in channel && channel.linkingId) { linkingInfo.push({ layout: spec.layout === 'circular' ? 'circular' : 'linear', - viewId, + hgViewId, linkId: channel.linkingId, isBrush: spec.mark === 'brush', style: { diff --git a/src/gosling-track/gosling-track.ts b/src/gosling-track/gosling-track.ts index 6f58e79cb..0936e83df 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/gosling-track/gosling-track.ts @@ -71,6 +71,14 @@ const DEFAULT_MOUSE_EVENT_STYLE: Required = { }; interface GoslingTrackOptions { + /** + * Track ID specified by users + */ + id: string; + /** + * Track IDs that are superposed with this track, containing the id of this track itself + */ + siblingIds: string[]; spec: SingleTrack | OverlaidTrack; theme: CompleteThemeDeep; showMousePosition?: boolean; @@ -502,7 +510,7 @@ const factory: PluginTrackFactory = (HGC, context, op const flatTileData = ([] as Datum[]).concat(...models.map(d => d.data())); if (flatTileData.length !== 0) { - publish('rawData', { id: context.viewUid, data: flatTileData }); + this.options.siblingIds.forEach(id => publish('rawData', { id, data: flatTileData })); } } @@ -1020,11 +1028,13 @@ const factory: PluginTrackFactory = (HGC, context, op const capturedElements = this.#getElementsWithinMouse(mouseX, mouseY); if (capturedElements.length !== 0) { - publish('click', { - id: context.viewUid, - genomicPosition, - data: capturedElements.map(d => d.value) - }); + this.options.siblingIds.forEach(id => + publish('click', { + id, + genomicPosition, + data: capturedElements.map(d => d.value) + }) + ); } } } From 9498a88e7064cfc32edfabbef822066f63e98ae7 Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Tue, 1 Aug 2023 09:50:37 -0400 Subject: [PATCH 19/25] chore: consistent badge styles in README.md (#955) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13df2a10d..5c9f1acb7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gosling.js -[![npm version](https://img.shields.io/npm/v/gosling.js.svg?style=flat-square)](https://www.npmjs.com/package/gosling.js) [![editor status](https://github.com/gosling-lang/gosling.js/actions/workflows/deploy-editor.yml/badge.svg)](https://github.com/gosling-lang/gosling.js/actions/workflows/deploy-editor.yml) [![build status](https://github.com/gosling-lang/gosling.js/actions/workflows/ci.yml/badge.svg)](https://github.com/gosling-lang/gosling.js/actions/workflows/ci.yml) [![codecov](https://img.shields.io/codecov/c/github/gosling-lang/gosling.js/master.svg?style=flat-square&?cacheSeconds=60)](https://codecov.io/gh/gosling-lang/gosling.js) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![online editor](https://img.shields.io/badge/demo-online_editor-E08243.svg?style=flat-square)](https://gosling.js.org/) [![docs](https://img.shields.io/badge/docs-📖-57B4E9.svg?style=flat-square)](http://gosling-lang.org/docs/) +[![npm version](https://img.shields.io/npm/v/gosling.js.svg)](https://www.npmjs.com/package/gosling.js) [![editor status](https://github.com/gosling-lang/gosling.js/actions/workflows/deploy-editor.yml/badge.svg)](https://github.com/gosling-lang/gosling.js/actions/workflows/deploy-editor.yml) [![build status](https://github.com/gosling-lang/gosling.js/actions/workflows/ci.yml/badge.svg)](https://github.com/gosling-lang/gosling.js/actions/workflows/ci.yml) [![codecov](https://img.shields.io/codecov/c/github/gosling-lang/gosling.js/master.svg?cacheSeconds=60)](https://codecov.io/gh/gosling-lang/gosling.js) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![online editor](https://img.shields.io/badge/demo-online_editor-E08243.svg)](https://gosling.js.org/) [![docs](https://img.shields.io/badge/docs-📖-57B4E9.svg)](http://gosling-lang.org/docs/) **Gosling.js is a declarative grammar for interactive (epi)genomics visualization on the Web.** From ab394acfa29e3135a83b17e4207c8ad21204fdb7 Mon Sep 17 00:00:00 2001 From: sehilyi Date: Tue, 1 Aug 2023 10:00:48 -0400 Subject: [PATCH 20/25] v0.10.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bdef48b..d35b7003e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# [0.10.0](https://github.com/gosling-lang/gosling.js/compare/v0.9.33...v0.10.0) (2023-08-01) + + +### Bug Fixes + +* **core:** do not override ID in tracks from views ([#954](https://github.com/gosling-lang/gosling.js/issues/954)) ([6654e0d](https://github.com/gosling-lang/gosling.js/commit/6654e0d315a40978ce26d97fdcf38168472a088b)) +* **data-fetcher:** correctly calculate the distance to previous mutation in VCF (`DISTPREV`) ([#949](https://github.com/gosling-lang/gosling.js/issues/949)) ([ef5f58f](https://github.com/gosling-lang/gosling.js/commit/ef5f58f43ee33ca2de068a547f21ed1767426437)) +* **track:** remove private properties from draw() ([#952](https://github.com/gosling-lang/gosling.js/issues/952)) ([529e9a3](https://github.com/gosling-lang/gosling.js/commit/529e9a38484fe81b624292a582870ae0cc144d28)) + + +### Features + +* **api:** consistent track/view IDs for JS API ([#944](https://github.com/gosling-lang/gosling.js/issues/944)) ([1c2b766](https://github.com/gosling-lang/gosling.js/commit/1c2b7669660856cae98f88ba38d9326b788c75e1)) +* **api:** onNewTrack, onNewView ([#943](https://github.com/gosling-lang/gosling.js/issues/943)) ([a98ee69](https://github.com/gosling-lang/gosling.js/commit/a98ee69367273943becbeae8696b1511b35f04e4)) +* **api:** subscription for genomic axis changes ([#935](https://github.com/gosling-lang/gosling.js/issues/935)) ([a2c36f5](https://github.com/gosling-lang/gosling.js/commit/a2c36f500f0d8e1181c9c72a970eb385af4fe3b5)) +* **core, api, editor:** support using view IDs ([#939](https://github.com/gosling-lang/gosling.js/issues/939)) ([cd8d300](https://github.com/gosling-lang/gosling.js/commit/cd8d3000f7e1e86422ec89f765585f6056d64a9f)) +* **data-fetcher:** GFF3 with tabix ([#923](https://github.com/gosling-lang/gosling.js/issues/923)) ([526882f](https://github.com/gosling-lang/gosling.js/commit/526882fb82463ad4262568d9569840431c709fc9)) +* **track:** dummy-track ([#946](https://github.com/gosling-lang/gosling.js/issues/946)) ([ee02d38](https://github.com/gosling-lang/gosling.js/commit/ee02d38312ae7431ccd460a6d5e1904013488427)) + + + ## [0.9.33](https://github.com/gosling-lang/gosling.js/compare/v0.9.32...v0.9.33) (2023-07-11) diff --git a/package.json b/package.json index 2a435460f..151b421af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gosling.js", "author": "Sehi L'Yi", - "version": "0.9.33", + "version": "0.10.0", "license": "MIT", "repository": { "type": "git", @@ -45,8 +45,8 @@ "dependencies": { "@gmod/bam": "^1.1.18", "@gmod/bbi": "^3.0.1", - "@gmod/gff": "^1.3.0", "@gmod/bed": "^2.1.2", + "@gmod/gff": "^1.3.0", "@gmod/tabix": "^1.5.6", "@gmod/vcf": "^5.0.10", "@types/bezier-js": "^4.1.0", From c6987855c73e82a44a0ea10d7f822f121d480943 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Wed, 2 Aug 2023 10:40:43 -0400 Subject: [PATCH 21/25] fix(data-fetcher): gmod/gff stream issue (#957) fix: stream issue --- package.json | 1 + vite.config.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 151b421af..1f1c0f4a6 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "quick-lru": "^6.1.1", "rbush": "^3.0.1", "react-grid-layout": "^1.2.5", + "stream-browserify": "^3.0.0", "threads": "^1.6.4", "uuid": "^8.3.2" }, diff --git a/vite.config.js b/vite.config.js index 3e3b9ed34..608396615 100644 --- a/vite.config.js +++ b/vite.config.js @@ -64,7 +64,8 @@ const alias = { "@gosling-lang/dummy-track": path.resolve(__dirname, "./src/dummy-track/index.ts"), "@data-fetchers": path.resolve(__dirname, "./src/data-fetchers/index.ts"), zlib: path.resolve(__dirname, './src/alias/zlib.ts'), - uuid: path.resolve(__dirname, './node_modules/uuid/dist/esm-browser/index.js') + uuid: path.resolve(__dirname, './node_modules/uuid/dist/esm-browser/index.js'), + stream: path.resolve(__dirname, './node_modules/stream-browserify') }; const skipExt = new Set(['@gmod/bbi', 'uuid']); From 8b9b0655060fef06f5818f2ca1584069be56a9df Mon Sep 17 00:00:00 2001 From: sehilyi Date: Wed, 2 Aug 2023 11:01:08 -0400 Subject: [PATCH 22/25] v0.10.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d35b7003e..d3f7e3940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.10.1](https://github.com/gosling-lang/gosling.js/compare/v0.10.0...v0.10.1) (2023-08-02) + + +### Bug Fixes + +* **data-fetcher:** gmod/gff stream issue ([#957](https://github.com/gosling-lang/gosling.js/issues/957)) ([c698785](https://github.com/gosling-lang/gosling.js/commit/c6987855c73e82a44a0ea10d7f822f121d480943)) + + + # [0.10.0](https://github.com/gosling-lang/gosling.js/compare/v0.9.33...v0.10.0) (2023-08-01) diff --git a/package.json b/package.json index 1f1c0f4a6..9956d5c26 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gosling.js", "author": "Sehi L'Yi", - "version": "0.10.0", + "version": "0.10.1", "license": "MIT", "repository": { "type": "git", From bee6a4776b7c15d45eb7929659ac777e914dd08d Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Thu, 10 Aug 2023 10:55:06 -0400 Subject: [PATCH 23/25] chore(editor): use Google Analytics 4 tag (#920) Update index.html --- index.html | 39 ++++++--------------------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/index.html b/index.html index 4cf2982d8..8e8b18537 100644 --- a/index.html +++ b/index.html @@ -5,40 +5,13 @@ + Gosling.js Editor From 9b36b6c473c9707907c4cc86a8df7a07b150b1be Mon Sep 17 00:00:00 2001 From: Sehi L'Yi Date: Wed, 16 Aug 2023 09:50:12 -0400 Subject: [PATCH 24/25] refactor: file structure; separate folder for compiler, tracks, api, and schema (#959) * refactor: file structure; separate folder for compiler, tracks, api, and schema * fix: fix build and test * chore: more import adjust * chore: more import clean up * fix: fix import of schema * chore: more refactor --- editor/Editor.tsx | 6 ++-- editor/EditorPanel.tsx | 2 +- editor/example/doc-examples/single-mark.ts | 2 +- editor/example/doc-examples/triangle.ts | 2 +- editor/example/index.test.ts | 4 +-- editor/example/json-spec/circos.ts | 2 +- editor/example/json-spec/gene-annotation.ts | 2 +- editor/example/json-spec/glyph.ts | 2 +- editor/example/json-spec/ideograms.ts | 2 +- .../json-spec/layout-and-arrangement.ts | 4 +-- editor/example/json-spec/matrix.ts | 2 +- editor/example/json-spec/mouse-event.ts | 2 +- editor/example/json-spec/pileup.ts | 2 +- .../example/json-spec/responsive-alignment.ts | 2 +- .../responsive-track-wise-comparison.ts | 2 +- editor/example/json-spec/responsive.ts | 2 +- editor/example/json-spec/sars-cov-2.ts | 2 +- editor/example/json-spec/sashimi.ts | 2 +- editor/example/json-spec/semantic-zoom.ts | 2 +- editor/example/json-spec/theme.ts | 2 +- editor/example/json-spec/visual-encoding.ts | 2 +- editor/example/json-spec/visual-linking.ts | 2 +- editor/example/spec/basic-semantic-zoom.ts | 2 +- editor/example/spec/corces.ts | 2 +- editor/example/spec/islandviewer.ts | 2 +- editor/example/spec/rule.ts | 2 +- editor/example/spec/sashimi.ts | 2 +- editor/example/spec/vertical-band.ts | 2 +- .../example/spec/visual-encoding-circular.ts | 2 +- editor/example/spec/visual-encoding.ts | 2 +- embed/index.ts | 2 +- scripts/generate-schemas.mjs | 12 +++---- src/api/README.md | 3 ++ src/{core => api}/api-data.ts | 4 +-- src/{core => api}/api.ts | 10 +++--- src/{core => api}/pubsub.ts | 2 +- src/{core => api}/track-and-view-ids.test.ts | 2 +- src/{core => api}/track-and-view-ids.ts | 4 +-- src/compiler/README.md | 3 ++ .../utils => compiler}/bounding-box.test.ts | 8 ++--- src/{core/utils => compiler}/bounding-box.ts | 12 +++---- src/{core => compiler}/compile.test.ts | 2 +- src/{core => compiler}/compile.ts | 14 ++++---- .../create-higlass-models.ts | 14 ++++---- src/{core => compiler}/defaults.ts | 0 .../gosling-to-higlass.test.ts | 8 ++--- src/{core => compiler}/gosling-to-higlass.ts | 20 +++++------ src/{core => compiler}/higlass-model.test.ts | 4 +-- src/{core => compiler}/higlass-model.ts | 20 +++++------ src/{core => compiler}/responsive.test.ts | 2 +- src/{core => compiler}/responsive.ts | 4 +-- .../spec-preprocess.test.ts | 4 +-- .../utils => compiler}/spec-preprocess.ts | 10 +++--- src/core/README.md | 3 ++ src/core/gosling-component.tsx | 8 ++--- src/core/gosling-embed.ts | 10 +++--- src/core/higlass-component-wrapper.tsx | 2 +- src/core/init.ts | 6 ++-- src/core/mark/area.ts | 8 ++--- src/core/mark/axis.test.ts | 4 +-- src/core/mark/axis.ts | 6 ++-- src/core/mark/background.ts | 4 +-- src/core/mark/bar.test.ts | 4 +-- src/core/mark/bar.ts | 8 ++--- src/core/mark/betweenLink.ts | 6 ++-- src/core/mark/grid.test.ts | 4 +-- src/core/mark/grid.ts | 4 +-- src/core/mark/index.test.ts | 4 +-- src/core/mark/index.ts | 8 ++--- src/core/mark/legend.test.ts | 4 +-- src/core/mark/legend.ts | 6 ++-- src/core/mark/line.test.ts | 4 +-- src/core/mark/line.ts | 6 ++-- src/core/mark/outline-circular.ts | 4 +-- src/core/mark/outline.ts | 4 +-- src/core/mark/point.test.ts | 8 ++--- src/core/mark/point.ts | 6 ++-- src/core/mark/rect.ts | 6 ++-- src/core/mark/rule.ts | 8 ++--- src/core/mark/text.ts | 8 ++--- src/core/mark/title.ts | 4 +-- src/core/mark/triangle.test.ts | 4 +-- src/core/mark/triangle.ts | 6 ++-- src/core/mark/withinLink.test.ts | 4 +-- src/core/mark/withinLink.ts | 6 ++-- src/core/utils/assembly.test.ts | 2 +- src/core/utils/assembly.ts | 2 +- src/core/utils/color-to-hex.test.ts | 2 +- src/core/utils/color-to-hex.ts | 2 +- src/core/utils/data-transform.ts | 4 +-- src/core/utils/linking.test.ts | 6 ++-- src/core/utils/linking.ts | 4 +-- src/core/utils/omit-deep.ts | 4 +-- src/core/utils/overlay.test.ts | 4 +-- src/core/utils/overlay.ts | 4 +-- src/core/utils/scales.test.ts | 6 ++-- src/core/utils/scales.ts | 6 ++-- src/core/utils/semantic-zoom.ts | 2 +- src/core/utils/style.ts | 2 +- src/core/utils/template.ts | 6 ++-- src/core/utils/view-config.ts | 2 +- src/data-fetchers/README.md | 3 ++ src/data-fetchers/bam/bam-data-fetcher.ts | 2 +- src/data-fetchers/bam/bam-worker.ts | 2 +- src/data-fetchers/bed/bed-data-fetcher.ts | 2 +- src/data-fetchers/bed/bed-worker.ts | 2 +- .../bigwig/bigwig-data-fetcher.ts | 2 +- src/data-fetchers/csv/csv-data-fetcher.ts | 2 +- src/data-fetchers/gff/gff-data-fetcher.ts | 2 +- src/data-fetchers/gff/gff-worker.ts | 2 +- src/data-fetchers/json/json-data-fetcher.ts | 2 +- src/data-fetchers/utils.test.ts | 2 +- src/data-fetchers/utils.ts | 2 +- src/data-fetchers/vcf/vcf-data-fetcher.ts | 2 +- src/data-fetchers/vcf/vcf-worker.ts | 2 +- src/gosling-schema/README.md | 3 ++ .../gosling.schema.guards.test.ts | 0 .../gosling.schema.guards.ts | 4 +-- .../gosling-schema}/gosling.schema.json | 0 .../gosling.schema.test.ts | 0 .../gosling.schema.ts | 0 src/gosling-schema/index.ts | 5 +++ .../gosling-schema}/template.schema.json | 0 .../gosling-schema}/theme.schema.json | 0 .../utils => gosling-schema}/validate.test.ts | 4 +-- .../utils => gosling-schema}/validate.ts | 8 ++--- .../higlass-schema}/higlass.schema.json | 0 .../higlass.schema.ts | 2 +- src/higlass-schema/index.ts | 2 ++ src/index.ts | 15 ++++---- src/main/README.md | 5 +++ src/{ => tracks}/dummy-track/dummy-track.ts | 6 ++-- src/{ => tracks}/dummy-track/index.ts | 0 src/{ => tracks}/gosling-brush/brush-track.ts | 2 +- src/{ => tracks}/gosling-brush/index.ts | 0 .../gosling-brush/linear-brush-model.ts | 2 +- .../gosling-genomic-axis/axis-track.ts | 16 ++++----- .../gosling-genomic-axis/index.ts | 0 .../gosling-track/data-abstraction.ts | 4 +-- .../gosling-mouse-event/index.ts | 0 .../mouse-event-model.test.ts | 0 .../gosling-mouse-event/mouse-event-model.ts | 2 +- .../gosling-mouse-event/polygon.ts | 2 +- .../gosling-track-model.test.ts | 6 ++-- .../gosling-track}/gosling-track-model.ts | 30 ++++++++-------- .../gosling-track/gosling-track.test.ts | 0 .../gosling-track/gosling-track.ts | 36 +++++++++---------- src/{ => tracks}/gosling-track/index.ts | 0 tsconfig.json | 13 ++++--- vite.config.js | 13 ++++--- 150 files changed, 350 insertions(+), 326 deletions(-) create mode 100644 src/api/README.md rename src/{core => api}/api-data.ts (92%) rename src/{core => api}/api.ts (95%) rename src/{core => api}/pubsub.ts (87%) rename src/{core => api}/track-and-view-ids.test.ts (97%) rename src/{core => api}/track-and-view-ids.ts (96%) create mode 100644 src/compiler/README.md rename src/{core/utils => compiler}/bounding-box.test.ts (98%) rename src/{core/utils => compiler}/bounding-box.ts (98%) rename src/{core => compiler}/compile.test.ts (99%) rename src/{core => compiler}/compile.ts (88%) rename src/{core => compiler}/create-higlass-models.ts (90%) rename src/{core => compiler}/defaults.ts (100%) rename src/{core => compiler}/gosling-to-higlass.test.ts (86%) rename src/{core => compiler}/gosling-to-higlass.ts (95%) rename src/{core => compiler}/higlass-model.test.ts (93%) rename src/{core => compiler}/higlass-model.ts (95%) rename src/{core => compiler}/responsive.test.ts (97%) rename src/{core => compiler}/responsive.ts (96%) rename src/{core/utils => compiler}/spec-preprocess.test.ts (99%) rename src/{core/utils => compiler}/spec-preprocess.ts (99%) create mode 100644 src/core/README.md create mode 100644 src/data-fetchers/README.md create mode 100644 src/gosling-schema/README.md rename src/{core => gosling-schema}/gosling.schema.guards.test.ts (100%) rename src/{core => gosling-schema}/gosling.schema.guards.ts (98%) rename {schema => src/gosling-schema}/gosling.schema.json (100%) rename src/{core => gosling-schema}/gosling.schema.test.ts (100%) rename src/{core => gosling-schema}/gosling.schema.ts (100%) create mode 100644 src/gosling-schema/index.ts rename {schema => src/gosling-schema}/template.schema.json (100%) rename {schema => src/gosling-schema}/theme.schema.json (100%) rename src/{core/utils => gosling-schema}/validate.test.ts (64%) rename src/{core/utils => gosling-schema}/validate.ts (94%) rename {schema => src/higlass-schema}/higlass.schema.json (100%) rename src/{core => higlass-schema}/higlass.schema.ts (98%) create mode 100644 src/higlass-schema/index.ts create mode 100644 src/main/README.md rename src/{ => tracks}/dummy-track/dummy-track.ts (92%) rename src/{ => tracks}/dummy-track/index.ts (100%) rename src/{ => tracks}/gosling-brush/brush-track.ts (99%) rename src/{ => tracks}/gosling-brush/index.ts (100%) rename src/{ => tracks}/gosling-brush/linear-brush-model.ts (99%) rename src/{ => tracks}/gosling-genomic-axis/axis-track.ts (98%) rename src/{ => tracks}/gosling-genomic-axis/index.ts (100%) rename src/{ => tracks}/gosling-track/data-abstraction.ts (99%) rename src/{ => tracks/gosling-track}/gosling-mouse-event/index.ts (100%) rename src/{ => tracks/gosling-track}/gosling-mouse-event/mouse-event-model.test.ts (100%) rename src/{ => tracks/gosling-track}/gosling-mouse-event/mouse-event-model.ts (98%) rename src/{ => tracks/gosling-track}/gosling-mouse-event/polygon.ts (98%) rename src/{core => tracks/gosling-track}/gosling-track-model.test.ts (97%) rename src/{core => tracks/gosling-track}/gosling-track-model.ts (97%) rename src/{ => tracks}/gosling-track/gosling-track.test.ts (100%) rename src/{ => tracks}/gosling-track/gosling-track.ts (98%) rename src/{ => tracks}/gosling-track/index.ts (100%) diff --git a/editor/Editor.tsx b/editor/Editor.tsx index e7a7a79d1..f2c4a369c 100644 --- a/editor/Editor.tsx +++ b/editor/Editor.tsx @@ -14,14 +14,14 @@ import 'allotment/dist/style.css'; import { debounce, isEqual } from 'lodash-es'; import stripJsonComments from 'strip-json-comments'; import JSONCrush from 'jsoncrush'; -import type { HiGlassSpec } from '@higlass.schema'; -import type { Datum } from '@gosling.schema'; +import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; +import type { Datum } from '@gosling-lang/gosling-schema'; import { Themes } from 'gosling-theme'; import { ICONS, type ICON_INFO } from './icon'; import { getHtmlTemplate } from './html-template'; import ErrorBoundary from './error-boundary'; -import { traverseTracksAndViews } from '../src/core/utils/spec-preprocess'; +import { traverseTracksAndViews } from '../src/compiler/spec-preprocess'; import { examples, type Example } from './example'; import EditorPanel, { type EditorLangauge } from './EditorPanel'; import EditorExamples from './EditorExamples'; diff --git a/editor/EditorPanel.tsx b/editor/EditorPanel.tsx index de3660106..c40fbce10 100644 --- a/editor/EditorPanel.tsx +++ b/editor/EditorPanel.tsx @@ -3,7 +3,7 @@ import MonacoEditor from 'react-monaco-editor'; import ReactResizeDetector from 'react-resize-detector'; import { GoslingSchema } from 'gosling.js'; -import goslingSpec from '../src/core/gosling.schema?raw'; +import goslingSpec from '../src/gosling-schema/gosling.schema?raw'; export * from './monaco_worker'; import * as Monaco from 'monaco-editor'; diff --git a/editor/example/doc-examples/single-mark.ts b/editor/example/doc-examples/single-mark.ts index 734bed906..4712dbdae 100644 --- a/editor/example/doc-examples/single-mark.ts +++ b/editor/example/doc-examples/single-mark.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, Mark, DataDeep } from '@gosling.schema'; +import type { GoslingSpec, Mark, DataDeep } from '@gosling-lang/gosling-schema'; export const data: DataDeep = { url: 'https://resgen.io/api/v1/tileset_info/?d=UvVPeLHuRDiYA3qwFlm7xQ', diff --git a/editor/example/doc-examples/triangle.ts b/editor/example/doc-examples/triangle.ts index bdd287b05..62df337e0 100644 --- a/editor/example/doc-examples/triangle.ts +++ b/editor/example/doc-examples/triangle.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, SingleTrack } from '@gosling.schema'; +import type { GoslingSpec, SingleTrack } from '@gosling-lang/gosling-schema'; import { HiGlass } from '../json-spec/gene-annotation'; diff --git a/editor/example/index.test.ts b/editor/example/index.test.ts index cf1b073b6..24be7f09a 100644 --- a/editor/example/index.test.ts +++ b/editor/example/index.test.ts @@ -1,6 +1,6 @@ -import { GoslingTrackModel } from '../../src/core/gosling-track-model'; +import { GoslingTrackModel } from '../../src/tracks/gosling-track/gosling-track-model'; import { resolveSuperposedTracks } from '../../src/core/utils/overlay'; -import { convertToFlatTracks } from '../../src/core/utils/spec-preprocess'; +import { convertToFlatTracks } from '../../src/compiler/spec-preprocess'; import { getTheme } from '../../src/core/utils/theme'; import { EX_TRACK_SEMANTIC_ZOOM } from './json-spec/semantic-zoom'; diff --git a/editor/example/json-spec/circos.ts b/editor/example/json-spec/circos.ts index 8bc270b0b..9e8e92441 100644 --- a/editor/example/json-spec/circos.ts +++ b/editor/example/json-spec/circos.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const EX_SPEC_CIRCULR_RANGE: GoslingSpec = { diff --git a/editor/example/json-spec/gene-annotation.ts b/editor/example/json-spec/gene-annotation.ts index 4ac94d97c..af1054593 100644 --- a/editor/example/json-spec/gene-annotation.ts +++ b/editor/example/json-spec/gene-annotation.ts @@ -1,4 +1,4 @@ -import type { DataDeep, GoslingSpec, OverlaidTracks } from '@gosling.schema'; +import type { DataDeep, GoslingSpec, OverlaidTracks } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; const width = 350; diff --git a/editor/example/json-spec/glyph.ts b/editor/example/json-spec/glyph.ts index 336def3d1..181e5a207 100644 --- a/editor/example/json-spec/glyph.ts +++ b/editor/example/json-spec/glyph.ts @@ -1,4 +1,4 @@ -import type { Mark } from '@gosling.schema'; +import type { Mark } from '@gosling-lang/gosling-schema'; export const GLYPH_PRESETS: { name: GLYPH_LOCAL_PRESET_TYPE; diff --git a/editor/example/json-spec/ideograms.ts b/editor/example/json-spec/ideograms.ts index de047a97a..eda3637ca 100644 --- a/editor/example/json-spec/ideograms.ts +++ b/editor/example/json-spec/ideograms.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, OverlaidTracks, Track } from '@gosling.schema'; +import type { GoslingSpec, OverlaidTracks, Track } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const CytoBands: OverlaidTracks = { diff --git a/editor/example/json-spec/layout-and-arrangement.ts b/editor/example/json-spec/layout-and-arrangement.ts index 106d6fe49..a67bf7fbd 100644 --- a/editor/example/json-spec/layout-and-arrangement.ts +++ b/editor/example/json-spec/layout-and-arrangement.ts @@ -1,6 +1,6 @@ import type { GoslingSpec } from 'gosling.js'; -import type { SingleView } from '@gosling.schema'; -import { DEFAULT_VIEW_SPACING } from '../../../src/core/defaults'; +import type { SingleView } from '@gosling-lang/gosling-schema'; +import { DEFAULT_VIEW_SPACING } from '../../../src/compiler/defaults'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; const COLORS = ['#D45E00', '#029F73', '#0072B2', '#CB7AA7', '#E79F00']; diff --git a/editor/example/json-spec/matrix.ts b/editor/example/json-spec/matrix.ts index 6e555789a..bd261adbd 100644 --- a/editor/example/json-spec/matrix.ts +++ b/editor/example/json-spec/matrix.ts @@ -1,4 +1,4 @@ -import type { PredefinedColors, SingleTrack } from '@gosling.schema'; +import type { PredefinedColors, SingleTrack } from '@gosling-lang/gosling-schema'; import type { GoslingSpec } from 'gosling.js'; import { random } from 'lodash-es'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; diff --git a/editor/example/json-spec/mouse-event.ts b/editor/example/json-spec/mouse-event.ts index b4bf3d52e..f3a4da126 100644 --- a/editor/example/json-spec/mouse-event.ts +++ b/editor/example/json-spec/mouse-event.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, SingleTrack } from '@gosling.schema'; +import type { GoslingSpec, SingleTrack } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; import { CytoBands } from './ideograms'; diff --git a/editor/example/json-spec/pileup.ts b/editor/example/json-spec/pileup.ts index 119f5aae7..001cbc4c8 100644 --- a/editor/example/json-spec/pileup.ts +++ b/editor/example/json-spec/pileup.ts @@ -1,4 +1,4 @@ -import type { Domain, GoslingSpec, View } from '@gosling.schema'; +import type { Domain, GoslingSpec, View } from '@gosling-lang/gosling-schema'; export function EX_SPEC_VIEW_PILEUP( id: string, diff --git a/editor/example/json-spec/responsive-alignment.ts b/editor/example/json-spec/responsive-alignment.ts index 2397bc5a9..ef89e828f 100644 --- a/editor/example/json-spec/responsive-alignment.ts +++ b/editor/example/json-spec/responsive-alignment.ts @@ -1,4 +1,4 @@ -import type { OverlaidTracks, SingleTrack, SingleView } from '@gosling.schema'; +import type { OverlaidTracks, SingleTrack, SingleView } from '@gosling-lang/gosling-schema'; import type { GoslingSpec } from 'gosling.js'; import { CHANNEL_DEFAULTS } from '../../../src/core/channel'; diff --git a/editor/example/json-spec/responsive-track-wise-comparison.ts b/editor/example/json-spec/responsive-track-wise-comparison.ts index 5dd84c11b..a8412b0ab 100644 --- a/editor/example/json-spec/responsive-track-wise-comparison.ts +++ b/editor/example/json-spec/responsive-track-wise-comparison.ts @@ -1,4 +1,4 @@ -import type { DomainChrInterval, GoslingSpec, OverlaidTracks, SingleTrack, TemplateTrack } from '@gosling.schema'; +import type { DomainChrInterval, GoslingSpec, OverlaidTracks, SingleTrack, TemplateTrack } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; const trackColor = { diff --git a/editor/example/json-spec/responsive.ts b/editor/example/json-spec/responsive.ts index ee088fad0..2a6ec7b96 100644 --- a/editor/example/json-spec/responsive.ts +++ b/editor/example/json-spec/responsive.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; export const EX_SPEC_RESPONSIVE_SEGREGATED_AREA_CHART: GoslingSpec = { // title: 'Responsive Visualization', diff --git a/editor/example/json-spec/sars-cov-2.ts b/editor/example/json-spec/sars-cov-2.ts index ec9f80c38..8f50115df 100644 --- a/editor/example/json-spec/sars-cov-2.ts +++ b/editor/example/json-spec/sars-cov-2.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, MultivecData, OverlaidTracks } from '@gosling.schema'; +import type { GoslingSpec, MultivecData, OverlaidTracks } from '@gosling-lang/gosling-schema'; import { EX_TRACK_SEMANTIC_ZOOM } from './semantic-zoom'; export const EX_TRACK_SARS_COV_2_GENES: OverlaidTracks = { diff --git a/editor/example/json-spec/sashimi.ts b/editor/example/json-spec/sashimi.ts index 4461b946d..ebcbed8b7 100644 --- a/editor/example/json-spec/sashimi.ts +++ b/editor/example/json-spec/sashimi.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; export const EX_SPEC_SASHIMI: GoslingSpec = { title: 'Sashimi Plot', diff --git a/editor/example/json-spec/semantic-zoom.ts b/editor/example/json-spec/semantic-zoom.ts index 443aee139..84f87aa2d 100644 --- a/editor/example/json-spec/semantic-zoom.ts +++ b/editor/example/json-spec/semantic-zoom.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, OverlaidTracks } from '@gosling.schema'; +import type { GoslingSpec, OverlaidTracks } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; import { EX_SPEC_PATHOGENIC } from './pathogenic'; diff --git a/editor/example/json-spec/theme.ts b/editor/example/json-spec/theme.ts index ae818ef88..9d86caaa1 100644 --- a/editor/example/json-spec/theme.ts +++ b/editor/example/json-spec/theme.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const EX_SPEC_CUSTOM_THEME: GoslingSpec = { diff --git a/editor/example/json-spec/visual-encoding.ts b/editor/example/json-spec/visual-encoding.ts index 441004bd4..cb7855e64 100644 --- a/editor/example/json-spec/visual-encoding.ts +++ b/editor/example/json-spec/visual-encoding.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const EX_SPEC_VISUAL_ENCODING: GoslingSpec = { diff --git a/editor/example/json-spec/visual-linking.ts b/editor/example/json-spec/visual-linking.ts index 60308f4a8..4253f9c42 100644 --- a/editor/example/json-spec/visual-linking.ts +++ b/editor/example/json-spec/visual-linking.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { GOSLING_PUBLIC_DATA } from './gosling-data'; export const EX_SPEC_LINKING: GoslingSpec = { diff --git a/editor/example/spec/basic-semantic-zoom.ts b/editor/example/spec/basic-semantic-zoom.ts index 72a823513..173a568a2 100644 --- a/editor/example/spec/basic-semantic-zoom.ts +++ b/editor/example/spec/basic-semantic-zoom.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, VisibilityCondition, MultivecData } from '@gosling.schema'; +import type { GoslingSpec, VisibilityCondition, MultivecData } from '@gosling-lang/gosling-schema'; const data: MultivecData = { type: 'multivec', diff --git a/editor/example/spec/corces.ts b/editor/example/spec/corces.ts index a2598a1dc..f798e00ee 100644 --- a/editor/example/spec/corces.ts +++ b/editor/example/spec/corces.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, PartialTrack, View, BigWigData, BeddbData } from '@gosling.schema'; +import type { GoslingSpec, PartialTrack, View, BigWigData, BeddbData } from '@gosling-lang/gosling-schema'; const width = 400; diff --git a/editor/example/spec/islandviewer.ts b/editor/example/spec/islandviewer.ts index 77c432bab..e1619c9e7 100644 --- a/editor/example/spec/islandviewer.ts +++ b/editor/example/spec/islandviewer.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; const circularRadius = 250; const centerRadius = 0.5; diff --git a/editor/example/spec/rule.ts b/editor/example/spec/rule.ts index 0e0198ee5..69f386107 100644 --- a/editor/example/spec/rule.ts +++ b/editor/example/spec/rule.ts @@ -1,4 +1,4 @@ -import type { PartialTrack, GoslingSpec, JsonData } from '@gosling.schema'; +import type { PartialTrack, GoslingSpec, JsonData } from '@gosling-lang/gosling-schema'; const barTrack: PartialTrack = { data: { diff --git a/editor/example/spec/sashimi.ts b/editor/example/spec/sashimi.ts index f01532281..8c2fb63ad 100644 --- a/editor/example/spec/sashimi.ts +++ b/editor/example/spec/sashimi.ts @@ -1,4 +1,4 @@ -import type { BamData, GoslingSpec } from '@gosling.schema'; +import type { BamData, GoslingSpec } from '@gosling-lang/gosling-schema'; const bamData: BamData = { type: 'bam', diff --git a/editor/example/spec/vertical-band.ts b/editor/example/spec/vertical-band.ts index 9ca115163..381118ffd 100644 --- a/editor/example/spec/vertical-band.ts +++ b/editor/example/spec/vertical-band.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, CsvData, Track } from '@gosling.schema'; +import type { GoslingSpec, CsvData, Track } from '@gosling-lang/gosling-schema'; const data: CsvData = { type: 'csv', url: 'https://raw.githubusercontent.com/sehilyi/gemini-datasets/master/data/circos-segdup-edited.txt', diff --git a/editor/example/spec/visual-encoding-circular.ts b/editor/example/spec/visual-encoding-circular.ts index 689b17c5c..312bbe9e3 100644 --- a/editor/example/spec/visual-encoding-circular.ts +++ b/editor/example/spec/visual-encoding-circular.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, View, MultivecData, Tooltip } from '@gosling.schema'; +import type { GoslingSpec, View, MultivecData, Tooltip } from '@gosling-lang/gosling-schema'; const size = { width: 350, height: 130 }; const data: MultivecData = { diff --git a/editor/example/spec/visual-encoding.ts b/editor/example/spec/visual-encoding.ts index 598543c62..10938403b 100644 --- a/editor/example/spec/visual-encoding.ts +++ b/editor/example/spec/visual-encoding.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec, MultivecData, View, Tooltip } from '@gosling.schema'; +import type { GoslingSpec, MultivecData, View, Tooltip } from '@gosling-lang/gosling-schema'; const size = { width: 600, height: 130 }; diff --git a/embed/index.ts b/embed/index.ts index 0b4095482..b0153ec4d 100644 --- a/embed/index.ts +++ b/embed/index.ts @@ -1,5 +1,5 @@ import { embed as goslingEmbed } from 'gosling.js'; -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import 'higlass/dist/hglib.css'; // https://github.com/vega/vega-embed/blob/master/src/container.ts diff --git a/scripts/generate-schemas.mjs b/scripts/generate-schemas.mjs index 215352f1d..84029437c 100644 --- a/scripts/generate-schemas.mjs +++ b/scripts/generate-schemas.mjs @@ -12,10 +12,10 @@ import stableStringify from "safe-stable-stringify"; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); const SCHEMAS = [ - ["GoslingSpec", "gosling.schema.json"], - ["HiGlassSpec", "higlass.schema.json"], - ["Theme", "theme.schema.json"], - ["TemplateTrackDef", "template.schema.json"], + ["GoslingSpec", "gosling.schema.json", "../src/gosling-schema/"], + ["HiGlassSpec", "higlass.schema.json", "../src/higlass-schema/"], + ["Theme", "theme.schema.json", "../src/gosling-schema/"], + ["TemplateTrackDef", "template.schema.json", "../src/gosling-schema/"], ]; const generator = tsj.createGenerator({ @@ -25,10 +25,10 @@ const generator = tsj.createGenerator({ encodeRefs: false, }); -for (const [type, filename] of SCHEMAS) { +for (const [type, filename, dir] of SCHEMAS) { const schema = generator.createSchema(type); fs.promises.writeFile( - path.resolve(__dirname, `../schema/${filename}`), + path.resolve(__dirname, `${dir}/${filename}`), stableStringify(schema, null, 2) + "\n", ); } diff --git a/src/api/README.md b/src/api/README.md new file mode 100644 index 000000000..1706b0174 --- /dev/null +++ b/src/api/README.md @@ -0,0 +1,3 @@ +# @gosling-lang/gosling-api + +Code related to JavaScript API. \ No newline at end of file diff --git a/src/core/api-data.ts b/src/api/api-data.ts similarity index 92% rename from src/core/api-data.ts rename to src/api/api-data.ts index 0c3580f04..a1f3a99a7 100644 --- a/src/core/api-data.ts +++ b/src/api/api-data.ts @@ -1,5 +1,5 @@ -import type { GoslingSpec, PartialTrack, TrackApiData, View } from '@gosling.schema'; -import { getInternalSpecById, getTrackIds, getViewIds } from './track-and-view-ids'; +import type { GoslingSpec, PartialTrack, TrackApiData, View } from '@gosling-lang/gosling-schema'; +import { getInternalSpecById, getTrackIds, getViewIds } from '../api/track-and-view-ids'; /** * This collect information of views by referring to the track information. diff --git a/src/core/api.ts b/src/api/api.ts similarity index 95% rename from src/core/api.ts rename to src/api/api.ts index a06c31eaf..e720fed1f 100644 --- a/src/core/api.ts +++ b/src/api/api.ts @@ -1,10 +1,10 @@ import * as PIXI from 'pixi.js'; -import type { TrackApiData, VisUnitApiData, ViewApiData } from '@gosling.schema'; -import type { HiGlassApi } from './higlass-component-wrapper'; -import type { HiGlassSpec } from '@higlass.schema'; +import type { TrackApiData, VisUnitApiData, ViewApiData } from '@gosling-lang/gosling-schema'; +import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; +import type { HiGlassApi } from '../core/higlass-component-wrapper'; import { subscribe, unsubscribe } from './pubsub'; -import { computeChromSizes, GenomicPositionHelper } from './utils/assembly'; -import type { CompleteThemeDeep } from './utils/theme'; +import { computeChromSizes, GenomicPositionHelper } from '../core/utils/assembly'; +import type { CompleteThemeDeep } from '../core/utils/theme'; import type { IdTable } from './track-and-view-ids'; /** diff --git a/src/core/pubsub.ts b/src/api/pubsub.ts similarity index 87% rename from src/core/pubsub.ts rename to src/api/pubsub.ts index a791314e1..52c09422e 100644 --- a/src/core/pubsub.ts +++ b/src/api/pubsub.ts @@ -1,4 +1,4 @@ -import type { _EventMap } from './gosling.schema'; +import type { _EventMap } from '@gosling-lang/gosling-schema'; import PubSub from 'pubsub-js'; type EventName = keyof _EventMap; diff --git a/src/core/track-and-view-ids.test.ts b/src/api/track-and-view-ids.test.ts similarity index 97% rename from src/core/track-and-view-ids.test.ts rename to src/api/track-and-view-ids.test.ts index d551641e8..895c07940 100644 --- a/src/core/track-and-view-ids.test.ts +++ b/src/api/track-and-view-ids.test.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { getInternalSpecById, getTrackIds, getViewIds } from './track-and-view-ids'; describe('Retrieve IDs and Sub-spec', () => { diff --git a/src/core/track-and-view-ids.ts b/src/api/track-and-view-ids.ts similarity index 96% rename from src/core/track-and-view-ids.ts rename to src/api/track-and-view-ids.ts index 3bf459765..24c24ddab 100644 --- a/src/core/track-and-view-ids.ts +++ b/src/api/track-and-view-ids.ts @@ -1,5 +1,5 @@ -import type { CommonTrackDef, CommonViewDef, GoslingSpec, PartialTrack, View } from '@gosling.schema'; -import { traverseTracksAndViews } from './utils/spec-preprocess'; +import type { CommonTrackDef, CommonViewDef, GoslingSpec, PartialTrack, View } from '@gosling-lang/gosling-schema'; +import { traverseTracksAndViews } from '../compiler/spec-preprocess'; /** * A table that maps Gosling track IDs to HiGlass view IDs. diff --git a/src/compiler/README.md b/src/compiler/README.md new file mode 100644 index 000000000..b1a29cfa2 --- /dev/null +++ b/src/compiler/README.md @@ -0,0 +1,3 @@ +# @gosling-lang/compiler + +Related to spec processing and converting a gosling spec into HiGlass spec. \ No newline at end of file diff --git a/src/core/utils/bounding-box.test.ts b/src/compiler/bounding-box.test.ts similarity index 98% rename from src/core/utils/bounding-box.test.ts rename to src/compiler/bounding-box.test.ts index 29b3c1f0f..41f99f6a3 100644 --- a/src/core/utils/bounding-box.test.ts +++ b/src/compiler/bounding-box.test.ts @@ -1,8 +1,8 @@ -import type { GoslingSpec, Track } from '../gosling.schema'; -import { DEFAULT_CIRCULAR_VIEW_PADDING, DEFAULT_VIEW_SPACING } from '../defaults'; +import type { GoslingSpec, Track } from '@gosling-lang/gosling-schema'; +import { DEFAULT_CIRCULAR_VIEW_PADDING, DEFAULT_VIEW_SPACING } from './defaults'; import { getBoundingBox, getRelativeTrackInfo } from './bounding-box'; -import { traverseToFixSpecDownstream } from './spec-preprocess'; -import { getTheme } from './theme'; +import { traverseToFixSpecDownstream } from '../compiler/spec-preprocess'; +import { getTheme } from '../core/utils/theme'; describe('Arrangement', () => { it('1 View, 1 Track', () => { diff --git a/src/core/utils/bounding-box.ts b/src/compiler/bounding-box.ts similarity index 98% rename from src/core/utils/bounding-box.ts rename to src/compiler/bounding-box.ts index 051c193b3..9844c5603 100644 --- a/src/core/utils/bounding-box.ts +++ b/src/compiler/bounding-box.ts @@ -1,15 +1,15 @@ -import type { MultipleViews, CommonViewDef, GoslingSpec, Track, SingleView } from '@gosling.schema'; -import { Is2DTrack, IsDummyTrack, IsOverlaidTrack, IsXAxis, IsYAxis } from '../gosling.schema.guards'; -import { HIGLASS_AXIS_SIZE } from '../higlass-model'; +import type { MultipleViews, CommonViewDef, GoslingSpec, Track, SingleView } from '@gosling-lang/gosling-schema'; +import { Is2DTrack, IsDummyTrack, IsOverlaidTrack, IsXAxis, IsYAxis } from '@gosling-lang/gosling-schema'; +import { HIGLASS_AXIS_SIZE } from './higlass-model'; import { DEFAULT_CIRCULAR_VIEW_PADDING, DEFAULT_INNER_RADIUS_PROP, DEFAULT_VIEW_SPACING, DEWFAULT_TITLE_PADDING_ON_TOP_AND_BOTTOM -} from '../defaults'; -import { resolveSuperposedTracks } from './overlay'; +} from './defaults'; +import { resolveSuperposedTracks } from '../core/utils/overlay'; import { traverseTracksAndViews, traverseViewArrangements } from './spec-preprocess'; -import type { CompleteThemeDeep } from './theme'; +import type { CompleteThemeDeep } from '../core/utils/theme'; export interface Size { width: number; diff --git a/src/core/compile.test.ts b/src/compiler/compile.test.ts similarity index 99% rename from src/core/compile.test.ts rename to src/compiler/compile.test.ts index 16c5bf418..9552858aa 100644 --- a/src/core/compile.test.ts +++ b/src/compiler/compile.test.ts @@ -1,6 +1,6 @@ import { EX_SPEC_VISUAL_ENCODING } from '../../editor/example/json-spec/visual-encoding'; import { compile } from './compile'; -import { getTheme } from './utils/theme'; +import { getTheme } from '../core/utils/theme'; import type { GoslingSpec } from '../index'; describe('compile', () => { diff --git a/src/core/compile.ts b/src/compiler/compile.ts similarity index 88% rename from src/core/compile.ts rename to src/compiler/compile.ts index d306d15e1..004ad8c65 100644 --- a/src/core/compile.ts +++ b/src/compiler/compile.ts @@ -1,12 +1,12 @@ -import type { GoslingSpec, TemplateTrackDef, VisUnitApiData } from './gosling.schema'; -import type { HiGlassSpec } from './higlass.schema'; -import { traverseToFixSpecDownstream, overrideDataTemplates } from './utils/spec-preprocess'; -import { replaceTrackTemplates } from './utils/template'; -import { getRelativeTrackInfo, type Size } from './utils/bounding-box'; -import type { CompleteThemeDeep } from './utils/theme'; +import type { GoslingSpec, TemplateTrackDef, VisUnitApiData } from '@gosling-lang/gosling-schema'; +import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; +import { traverseToFixSpecDownstream, overrideDataTemplates } from './spec-preprocess'; +import { replaceTrackTemplates } from '../core/utils/template'; +import { getRelativeTrackInfo, type Size } from './bounding-box'; +import type { CompleteThemeDeep } from '../core/utils/theme'; import { renderHiGlass as createHiGlassModels } from './create-higlass-models'; import { manageResponsiveSpecs } from './responsive'; -import type { IdTable } from './track-and-view-ids'; +import type { IdTable } from '../api/track-and-view-ids'; /** The callback function called everytime after the spec has been compiled */ export type CompileCallback = ( diff --git a/src/core/create-higlass-models.ts b/src/compiler/create-higlass-models.ts similarity index 90% rename from src/core/create-higlass-models.ts rename to src/compiler/create-higlass-models.ts index 8d38a3126..0e761eb15 100644 --- a/src/core/create-higlass-models.ts +++ b/src/compiler/create-higlass-models.ts @@ -1,7 +1,7 @@ -import { getBoundingBox, type TrackInfo } from './utils/bounding-box'; +import { getBoundingBox, type TrackInfo } from './bounding-box'; import { goslingToHiGlass } from './gosling-to-higlass'; import { HiGlassModel } from './higlass-model'; -import { getLinkingInfo } from './utils/linking'; +import { getLinkingInfo } from '../core/utils/linking'; import type { GoslingSpec, OverlaidTrack, @@ -9,12 +9,12 @@ import type { TrackApiData, VisUnitApiData, ViewApiData -} from '@gosling.schema'; -import type { CompleteThemeDeep } from './utils/theme'; +} from '@gosling-lang/gosling-schema'; +import type { CompleteThemeDeep } from '../core/utils/theme'; import type { CompileCallback } from './compile'; -import { getViewApiData } from './api-data'; -import { GoslingToHiGlassIdMapper } from './track-and-view-ids'; -import { IsDummyTrack } from './gosling.schema.guards'; +import { getViewApiData } from '../api/api-data'; +import { GoslingToHiGlassIdMapper } from '../api/track-and-view-ids'; +import { IsDummyTrack } from '@gosling-lang/gosling-schema'; export function renderHiGlass( spec: GoslingSpec, diff --git a/src/core/defaults.ts b/src/compiler/defaults.ts similarity index 100% rename from src/core/defaults.ts rename to src/compiler/defaults.ts diff --git a/src/core/gosling-to-higlass.test.ts b/src/compiler/gosling-to-higlass.test.ts similarity index 86% rename from src/core/gosling-to-higlass.test.ts rename to src/compiler/gosling-to-higlass.test.ts index e68db93b9..ab7543d38 100644 --- a/src/core/gosling-to-higlass.test.ts +++ b/src/compiler/gosling-to-higlass.test.ts @@ -1,11 +1,11 @@ import { goslingToHiGlass } from './gosling-to-higlass'; import { HiGlassModel } from './higlass-model'; import { EX_TRACK_SEMANTIC_ZOOM } from '../../editor/example/json-spec/semantic-zoom'; -import { convertToFlatTracks } from './utils/spec-preprocess'; -import { getTheme } from './utils/theme'; +import { convertToFlatTracks } from './spec-preprocess'; +import { getTheme } from '../core/utils/theme'; -import type { SingleTrack } from './gosling.schema'; -import { GoslingToHiGlassIdMapper } from './track-and-view-ids'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; +import { GoslingToHiGlassIdMapper } from '../api/track-and-view-ids'; describe('Should convert gosling spec to higlass view config.', () => { it('Should return a generated higlass view config correctly', () => { diff --git a/src/core/gosling-to-higlass.ts b/src/compiler/gosling-to-higlass.ts similarity index 95% rename from src/core/gosling-to-higlass.ts rename to src/compiler/gosling-to-higlass.ts index 45e83b38f..a0dd6ab00 100644 --- a/src/core/gosling-to-higlass.ts +++ b/src/compiler/gosling-to-higlass.ts @@ -1,11 +1,11 @@ import * as uuid from 'uuid'; -import type { Track as HiGlassTrack } from './higlass.schema'; +import type { Track as HiGlassTrack } from '@gosling-lang/higlass-schema'; import { HiGlassModel, HIGLASS_AXIS_SIZE } from './higlass-model'; -import { parseServerAndTilesetUidFromUrl } from './utils'; -import type { Track, Domain } from './gosling.schema'; -import type { BoundingBox, RelativePosition } from './utils/bounding-box'; -import { resolveSuperposedTracks } from './utils/overlay'; -import { getGenomicChannelKeyFromTrack, getGenomicChannelFromTrack } from './utils/validate'; +import { parseServerAndTilesetUidFromUrl } from '../core/utils'; +import type { Track, Domain } from '@gosling-lang/gosling-schema'; +import type { BoundingBox, RelativePosition } from './bounding-box'; +import { resolveSuperposedTracks } from '../core/utils/overlay'; +import { getGenomicChannelKeyFromTrack, getGenomicChannelFromTrack } from '../gosling-schema/validate'; import { IsDataDeep, IsChannelDeep, @@ -15,11 +15,11 @@ import { IsHiGlassMatrix, getHiGlassColorRange, IsDummyTrack -} from './gosling.schema.guards'; +} from '@gosling-lang/gosling-schema'; import { DEWFAULT_TITLE_PADDING_ON_TOP_AND_BOTTOM } from './defaults'; -import type { CompleteThemeDeep } from './utils/theme'; -import { DEFAULT_TEXT_STYLE } from './utils/text-style'; -import type { GoslingToHiGlassIdMapper } from './track-and-view-ids'; +import type { CompleteThemeDeep } from '../core/utils/theme'; +import { DEFAULT_TEXT_STYLE } from '../core/utils/text-style'; +import type { GoslingToHiGlassIdMapper } from '../api/track-and-view-ids'; /** * Convert a gosling track into a HiGlass view and add it into a higlass model. diff --git a/src/core/higlass-model.test.ts b/src/compiler/higlass-model.test.ts similarity index 93% rename from src/core/higlass-model.test.ts rename to src/compiler/higlass-model.test.ts index 85478c757..2feba9994 100644 --- a/src/core/higlass-model.test.ts +++ b/src/compiler/higlass-model.test.ts @@ -1,7 +1,7 @@ import * as uuid from 'uuid'; import { HiGlassModel } from './higlass-model'; -import { computeChromSizes } from './utils/assembly'; -import { getTheme } from './utils/theme'; +import { computeChromSizes } from '../core/utils/assembly'; +import { getTheme } from '../core/utils/theme'; describe('Should produce higlass model correctly', () => { it('Should set default values correctly', () => { diff --git a/src/core/higlass-model.ts b/src/compiler/higlass-model.ts similarity index 95% rename from src/core/higlass-model.ts rename to src/compiler/higlass-model.ts index 31ab1220c..d3c818027 100644 --- a/src/core/higlass-model.ts +++ b/src/compiler/higlass-model.ts @@ -1,14 +1,14 @@ import * as uuid from 'uuid'; -import type { HiGlassSpec, Track } from './higlass.schema'; -import HiGlassSchema from '../../schema/higlass.schema.json'; -import type { Assembly, AxisPosition, Domain, DummyTrack, Orientation, ZoomLimits } from './gosling.schema'; -import { getNumericDomain } from './utils/scales'; -import type { RelativePosition } from './utils/bounding-box'; -import { validateSpec } from './utils/validate'; -import { getAutoCompleteId, computeChromSizes } from './utils/assembly'; -import type { CompleteThemeDeep } from './utils/theme'; -import exampleHg from './example/hg-view-config-1'; -import { insertItemToArray } from './utils/array'; +import type { HiGlassSpec, Track } from '@gosling-lang/higlass-schema'; +import { HiGlassSchema } from '@gosling-lang/higlass-schema'; +import type { Assembly, AxisPosition, Domain, DummyTrack, Orientation, ZoomLimits } from '@gosling-lang/gosling-schema'; +import { getNumericDomain } from '../core/utils/scales'; +import type { RelativePosition } from './bounding-box'; +import { validateSpec } from '@gosling-lang/gosling-schema'; +import { getAutoCompleteId, computeChromSizes } from '../core/utils/assembly'; +import type { CompleteThemeDeep } from '../core/utils/theme'; +import exampleHg from '../core/example/hg-view-config-1'; +import { insertItemToArray } from '../core/utils/array'; export const HIGLASS_AXIS_SIZE = 30; diff --git a/src/core/responsive.test.ts b/src/compiler/responsive.test.ts similarity index 97% rename from src/core/responsive.test.ts rename to src/compiler/responsive.test.ts index e69ca0079..924197471 100644 --- a/src/core/responsive.test.ts +++ b/src/compiler/responsive.test.ts @@ -1,4 +1,4 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { manageResponsiveSpecs } from './responsive'; describe('ResponsiveSpec', () => { diff --git a/src/core/responsive.ts b/src/compiler/responsive.ts similarity index 96% rename from src/core/responsive.ts rename to src/compiler/responsive.ts index 0307dbfa7..0b2cbbbfb 100644 --- a/src/core/responsive.ts +++ b/src/compiler/responsive.ts @@ -1,5 +1,5 @@ -import type { GoslingSpec, SelectivityCondition, SingleView } from '@gosling.schema'; -import { logicalComparison } from './utils/semantic-zoom'; +import type { GoslingSpec, SelectivityCondition, SingleView } from '@gosling-lang/gosling-schema'; +import { logicalComparison } from '../core/utils/semantic-zoom'; export function manageResponsiveSpecs( spec: GoslingSpec | SingleView, diff --git a/src/core/utils/spec-preprocess.test.ts b/src/compiler/spec-preprocess.test.ts similarity index 99% rename from src/core/utils/spec-preprocess.test.ts rename to src/compiler/spec-preprocess.test.ts index 156a7fd16..ba24cd658 100644 --- a/src/core/utils/spec-preprocess.test.ts +++ b/src/compiler/spec-preprocess.test.ts @@ -1,7 +1,7 @@ -import type { GoslingSpec, SingleView, Track } from '../gosling.schema'; +import type { GoslingSpec, SingleView, Track } from '@gosling-lang/gosling-schema'; import { getBoundingBox, getRelativeTrackInfo } from './bounding-box'; import { traverseToFixSpecDownstream, overrideDataTemplates, convertToFlatTracks } from './spec-preprocess'; -import { getTheme } from './theme'; +import { getTheme } from '../core/utils/theme'; describe('Fix Spec Downstream', () => { it('Empty Views', () => { diff --git a/src/core/utils/spec-preprocess.ts b/src/compiler/spec-preprocess.ts similarity index 99% rename from src/core/utils/spec-preprocess.ts rename to src/compiler/spec-preprocess.ts index 0dcb524a7..74da6e528 100644 --- a/src/core/utils/spec-preprocess.ts +++ b/src/compiler/spec-preprocess.ts @@ -10,7 +10,7 @@ import type { CommonViewDef, MultipleViews, DisplaceTransform -} from '../gosling.schema'; +} from '@gosling-lang/gosling-schema'; import { IsDataTemplate, IsDataDeepTileset, @@ -21,16 +21,16 @@ import { IsStackedTracks, Is2DTrack, IsDummyTrack -} from '../gosling.schema.guards'; +} from '@gosling-lang/gosling-schema'; import { DEFAULT_INNER_RADIUS_PROP, DEFAULT_TRACK_HEIGHT_LINEAR, DEFAULT_TRACK_SIZE_2D, DEFAULT_TRACK_WIDTH_LINEAR, DEFAULT_VIEW_SPACING -} from '../defaults'; -import { spreadTracksByData } from './overlay'; -import { getStyleOverridden } from '../utils/style'; +} from './defaults'; +import { spreadTracksByData } from '../core/utils/overlay'; +import { getStyleOverridden } from '../core/utils/style'; /** * Traverse individual tracks and call the callback function to read and/or update the track definition. diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 000000000..1d7be5375 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,3 @@ +# @gosling-lang/core + +Rendering of graphics and React component of Gosling and a HiGlass wrapper. diff --git a/src/core/gosling-component.tsx b/src/core/gosling-component.tsx index 89aa3b08b..39be93c19 100644 --- a/src/core/gosling-component.tsx +++ b/src/core/gosling-component.tsx @@ -1,17 +1,17 @@ /* eslint-disable react/prop-types */ import { type HiGlassApi, HiGlassComponentWrapper } from './higlass-component-wrapper'; -import type { TemplateTrackDef, VisUnitApiData } from './gosling.schema'; +import type { TemplateTrackDef, VisUnitApiData } from '@gosling-lang/gosling-schema'; import React, { useState, useEffect, useMemo, useRef, forwardRef, useCallback, useImperativeHandle } from 'react'; import ResizeSensor from 'css-element-queries/src/ResizeSensor'; import * as gosling from '..'; import { getTheme, type Theme } from './utils/theme'; -import { createApi, type GoslingApi } from './api'; +import { createApi, type GoslingApi } from '../api/api'; import { GoslingTemplates } from '..'; import { omitDeep } from './utils/omit-deep'; import { isEqual } from 'lodash-es'; import * as uuid from 'uuid'; -import { publish } from './pubsub'; -import type { IdTable } from './track-and-view-ids'; +import { publish } from '../api/pubsub'; +import type { IdTable } from '../api/track-and-view-ids'; // Before rerendering, wait for a few time so that HiGlass container is resized already. // If HiGlass is rendered and then the container resizes, the viewport position changes, unmatching `xDomain` specified by users. diff --git a/src/core/gosling-embed.ts b/src/core/gosling-embed.ts index 261fb4aff..883e191db 100644 --- a/src/core/gosling-embed.ts +++ b/src/core/gosling-embed.ts @@ -1,14 +1,14 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import type { GoslingSpec } from './gosling.schema'; -import type { HiGlassSpec } from './higlass.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; +import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; -import { validateGoslingSpec } from './utils/validate'; -import { compile } from './compile'; +import { validateGoslingSpec } from '@gosling-lang/gosling-schema'; +import { compile } from '../compiler/compile'; import { getTheme, type Theme } from './utils/theme'; import { GoslingTemplates } from './utils/template'; -import { type GoslingApi, createApi } from './api'; +import { type GoslingApi, createApi } from '../api/api'; import { type HiGlassApi, diff --git a/src/core/higlass-component-wrapper.tsx b/src/core/higlass-component-wrapper.tsx index 26c4e1381..c864c2e9f 100644 --- a/src/core/higlass-component-wrapper.tsx +++ b/src/core/higlass-component-wrapper.tsx @@ -6,7 +6,7 @@ import * as uuid from 'uuid'; import * as gosling from '..'; // @ts-ignore import { HiGlassComponent } from 'higlass'; -import type { HiGlassSpec } from './higlass.schema'; +import type { HiGlassSpec } from '@gosling-lang/higlass-schema'; /** * Register plugin tracks and data fetchers to HiGlass. This is necessary for the first time before using Gosling. diff --git a/src/core/init.ts b/src/core/init.ts index e3ca119f7..03a39087e 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -2,9 +2,9 @@ import higlassRegister from 'higlass-register'; // @ts-ignore import { TextTrack } from 'higlass-text'; -import { AxisTrack } from '@gosling-genomic-axis'; -import { BrushTrack } from '@gosling-brush'; -import { GoslingTrack } from '@gosling-track'; +import { AxisTrack } from '@gosling-lang/gosling-genomic-axis'; +import { BrushTrack } from '@gosling-lang/gosling-brush'; +import { GoslingTrack } from '@gosling-lang/gosling-track'; import { DummyTrack } from '@gosling-lang/dummy-track'; import * as dataFetchers from '@data-fetchers'; diff --git a/src/core/mark/area.ts b/src/core/mark/area.ts index 154783cc2..e60a95ead 100644 --- a/src/core/mark/area.ts +++ b/src/core/mark/area.ts @@ -1,8 +1,8 @@ import { min as d3min, max as d3max, group } from 'd3-array'; -import type { Channel, Datum } from '@gosling.schema'; -import type { Tile } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsStackedMark, getValueUsingChannel } from '../gosling.schema.guards'; +import type { Channel, Datum } from '@gosling-lang/gosling-schema'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsStackedMark, getValueUsingChannel } from '@gosling-lang/gosling-schema'; import { cartesianToPolar } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; diff --git a/src/core/mark/axis.test.ts b/src/core/mark/axis.test.ts index 0d0fee884..eace01147 100644 --- a/src/core/mark/axis.test.ts +++ b/src/core/mark/axis.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawLinearYAxis } from './axis'; diff --git a/src/core/mark/axis.ts b/src/core/mark/axis.ts index fa59d8696..4980eb158 100644 --- a/src/core/mark/axis.ts +++ b/src/core/mark/axis.ts @@ -1,7 +1,7 @@ import { scaleLinear } from 'd3-scale'; -import type { Tile } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; diff --git a/src/core/mark/background.ts b/src/core/mark/background.ts index 347e32efc..64eb25199 100644 --- a/src/core/mark/background.ts +++ b/src/core/mark/background.ts @@ -1,6 +1,6 @@ import { isUndefined } from 'lodash-es'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; diff --git a/src/core/mark/bar.test.ts b/src/core/mark/bar.test.ts index da335cc1d..89cb12113 100644 --- a/src/core/mark/bar.test.ts +++ b/src/core/mark/bar.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import type { SingleTrack } from '@gosling.schema'; -import { GoslingTrackModel } from '../gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { getTheme } from '../utils/theme'; import { drawBar } from './bar'; import type { Tile } from '@higlass/services'; diff --git a/src/core/mark/bar.ts b/src/core/mark/bar.ts index 3b4aa8d3f..bda93e4d9 100644 --- a/src/core/mark/bar.ts +++ b/src/core/mark/bar.ts @@ -1,9 +1,9 @@ -import type { Tile } from '@gosling-track'; -import type { Channel } from '@gosling.schema'; -import type { GoslingTrackModel } from '../gosling-track-model'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { group } from 'd3-array'; import type { PIXIVisualProperty } from '../visual-property.schema'; -import { IsChannelDeep, IsStackedMark, getValueUsingChannel } from '../gosling.schema.guards'; +import { IsChannelDeep, IsStackedMark, getValueUsingChannel } from '@gosling-lang/gosling-schema'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; diff --git a/src/core/mark/betweenLink.ts b/src/core/mark/betweenLink.ts index fd31d1ae0..d50df8816 100644 --- a/src/core/mark/betweenLink.ts +++ b/src/core/mark/betweenLink.ts @@ -1,7 +1,7 @@ import type * as PIXI from 'pixi.js'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Channel } from '../gosling.schema'; -import { getValueUsingChannel, Is2DTrack } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import { getValueUsingChannel, Is2DTrack } from '@gosling-lang/gosling-schema'; import { cartesianToPolar, positionToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; diff --git a/src/core/mark/grid.test.ts b/src/core/mark/grid.test.ts index 2a00cf541..2bee9dfe8 100644 --- a/src/core/mark/grid.test.ts +++ b/src/core/mark/grid.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawGrid } from './grid'; diff --git a/src/core/mark/grid.ts b/src/core/mark/grid.ts index ee1ae9454..5da425a0f 100644 --- a/src/core/mark/grid.ts +++ b/src/core/mark/grid.ts @@ -1,6 +1,6 @@ import type { ScaleLinear } from 'd3-scale'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import { isNumberArray, isStringArray } from '../utils/array'; diff --git a/src/core/mark/index.test.ts b/src/core/mark/index.test.ts index 51ede0334..31d5e7cc4 100644 --- a/src/core/mark/index.test.ts +++ b/src/core/mark/index.test.ts @@ -1,6 +1,6 @@ import { drawMark } from '.'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; describe('Should draw marks correctly', () => { diff --git a/src/core/mark/index.ts b/src/core/mark/index.ts index 2385963e7..dcddb4b3d 100644 --- a/src/core/mark/index.ts +++ b/src/core/mark/index.ts @@ -1,11 +1,11 @@ -import type { Tile } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { drawPoint } from './point'; import { drawLine } from './line'; import { drawBar } from './bar'; import { drawArea } from './area'; import { drawRect } from './rect'; -import type { ChannelTypes } from '../gosling.schema'; +import type { ChannelTypes } from '@gosling-lang/gosling-schema'; import { drawTriangle } from './triangle'; import { drawText } from './text'; import { drawRule } from './rule'; @@ -18,7 +18,7 @@ import { drawCircularYAxis, drawLinearYAxis } from './axis'; import { drawCircularOutlines } from './outline-circular'; import { drawBackground } from './background'; import type { CompleteThemeDeep } from '../utils/theme'; -import { Is2DTrack, IsVerticalRule } from '../gosling.schema.guards'; +import { Is2DTrack, IsVerticalRule } from '@gosling-lang/gosling-schema'; import { drawBetweenLink } from './betweenLink'; /** diff --git a/src/core/mark/legend.test.ts b/src/core/mark/legend.test.ts index c17124a2a..6226f6748 100644 --- a/src/core/mark/legend.test.ts +++ b/src/core/mark/legend.test.ts @@ -2,8 +2,8 @@ import * as PIXI from 'pixi.js'; import * as d3Selection from 'd3-selection'; import * as d3Drag from 'd3-drag'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawColorLegend } from './legend'; diff --git a/src/core/mark/legend.ts b/src/core/mark/legend.ts index e0494a0ca..a718b14f7 100644 --- a/src/core/mark/legend.ts +++ b/src/core/mark/legend.ts @@ -1,6 +1,6 @@ -import type { DisplayedLegend } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { DisplayedLegend } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; import type { Dimension } from '../utils/position'; diff --git a/src/core/mark/line.test.ts b/src/core/mark/line.test.ts index b70ed36b9..414eb2901 100644 --- a/src/core/mark/line.test.ts +++ b/src/core/mark/line.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawLine } from './line'; diff --git a/src/core/mark/line.ts b/src/core/mark/line.ts index 8291a333f..19543201c 100644 --- a/src/core/mark/line.ts +++ b/src/core/mark/line.ts @@ -1,7 +1,7 @@ import type * as PIXI from 'pixi.js'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Channel } from '../gosling.schema'; -import { getValueUsingChannel } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import { getValueUsingChannel } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import { cartesianToPolar } from '../utils/polar'; diff --git a/src/core/mark/outline-circular.ts b/src/core/mark/outline-circular.ts index 7da9943eb..2ad22ed5e 100644 --- a/src/core/mark/outline-circular.ts +++ b/src/core/mark/outline-circular.ts @@ -1,5 +1,5 @@ -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; diff --git a/src/core/mark/outline.ts b/src/core/mark/outline.ts index 1e8d8721d..7802e5cf0 100644 --- a/src/core/mark/outline.ts +++ b/src/core/mark/outline.ts @@ -1,5 +1,5 @@ -import type { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; diff --git a/src/core/mark/point.test.ts b/src/core/mark/point.test.ts index df1c8a53e..7c9b70b1f 100644 --- a/src/core/mark/point.test.ts +++ b/src/core/mark/point.test.ts @@ -1,9 +1,9 @@ import * as PIXI from 'pixi.js'; import { CHANNEL_DEFAULTS } from '../channel'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { Track } from '../gosling.schema'; -import { HIGLASS_AXIS_SIZE } from '../higlass-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Track } from '@gosling-lang/gosling-schema'; +import { HIGLASS_AXIS_SIZE } from '../../compiler/higlass-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { drawPoint } from './point'; import { getTheme } from '../utils/theme'; diff --git a/src/core/mark/point.ts b/src/core/mark/point.ts index cbca3f334..f270d42b7 100644 --- a/src/core/mark/point.ts +++ b/src/core/mark/point.ts @@ -1,7 +1,7 @@ import type * as PIXI from 'pixi.js'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Channel } from '../gosling.schema'; -import { getValueUsingChannel } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import { getValueUsingChannel } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import { cartesianToPolar } from '../utils/polar'; import type { PIXIVisualProperty } from '../visual-property.schema'; diff --git a/src/core/mark/rect.ts b/src/core/mark/rect.ts index d7ab86647..fedffcde5 100644 --- a/src/core/mark/rect.ts +++ b/src/core/mark/rect.ts @@ -1,9 +1,9 @@ -import type { Tile } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import type { PIXIVisualProperty } from '../visual-property.schema'; import colorToHex from '../utils/color-to-hex'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; export function drawRect(HGC: import('@higlass/types').HGC, track: any, tile: Tile, model: GoslingTrackModel) { /* track spec */ diff --git a/src/core/mark/rule.ts b/src/core/mark/rule.ts index ef18622d6..836def76b 100644 --- a/src/core/mark/rule.ts +++ b/src/core/mark/rule.ts @@ -1,7 +1,7 @@ -import type { Tile } from '@gosling-track'; -import type { Channel } from '@gosling.schema'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import { getValueUsingChannel } from '../gosling.schema.guards'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { getValueUsingChannel } from '@gosling-lang/gosling-schema'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; diff --git a/src/core/mark/text.ts b/src/core/mark/text.ts index e69bc96ef..99abc256f 100644 --- a/src/core/mark/text.ts +++ b/src/core/mark/text.ts @@ -1,8 +1,8 @@ -import type { Tile } from '@gosling-track'; -import type { Channel } from '@gosling.schema'; -import type { GoslingTrackModel } from '../gosling-track-model'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { group } from 'd3-array'; -import { getValueUsingChannel, IsStackedMark } from '../gosling.schema.guards'; +import { getValueUsingChannel, IsStackedMark } from '@gosling-lang/gosling-schema'; import { cartesianToPolar } from '../utils/polar'; // Merge with the one in the `utils/text-style.ts` diff --git a/src/core/mark/title.ts b/src/core/mark/title.ts index 43356bef7..7d11861b8 100644 --- a/src/core/mark/title.ts +++ b/src/core/mark/title.ts @@ -1,6 +1,6 @@ import type * as PIXI from 'pixi.js'; -import type { Tile } from '@gosling-track'; -import type { GoslingTrackModel } from '../gosling-track-model'; +import type { Tile } from '@gosling-lang/gosling-track'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; import { cartesianToPolar, valueToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; import type { CompleteThemeDeep } from '../utils/theme'; diff --git a/src/core/mark/triangle.test.ts b/src/core/mark/triangle.test.ts index d4d77ed84..2f0c2955b 100644 --- a/src/core/mark/triangle.test.ts +++ b/src/core/mark/triangle.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawTriangle } from './triangle'; diff --git a/src/core/mark/triangle.ts b/src/core/mark/triangle.ts index 42b728a89..4f981e375 100644 --- a/src/core/mark/triangle.ts +++ b/src/core/mark/triangle.ts @@ -1,7 +1,7 @@ import type * as PIXI from 'pixi.js'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Channel, Mark } from '../gosling.schema'; -import { getValueUsingChannel } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Channel, Mark } from '@gosling-lang/gosling-schema'; +import { getValueUsingChannel } from '@gosling-lang/gosling-schema'; import colorToHex from '../utils/color-to-hex'; import { cartesianToPolar } from '../utils/polar'; diff --git a/src/core/mark/withinLink.test.ts b/src/core/mark/withinLink.test.ts index 174b31006..29d4ece15 100644 --- a/src/core/mark/withinLink.test.ts +++ b/src/core/mark/withinLink.test.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; -import { GoslingTrackModel } from '../gosling-track-model'; -import type { SingleTrack } from '../gosling.schema'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { SingleTrack } from '@gosling-lang/gosling-schema'; import { getTheme } from '../utils/theme'; import { drawWithinLink } from './withinLink'; diff --git a/src/core/mark/withinLink.ts b/src/core/mark/withinLink.ts index 3ebc17c17..9c3ec4e4b 100644 --- a/src/core/mark/withinLink.ts +++ b/src/core/mark/withinLink.ts @@ -1,7 +1,7 @@ import type * as PIXI from 'pixi.js'; -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Channel } from '../gosling.schema'; -import { IsChannelDeep, getValueUsingChannel, Is2DTrack } from '../gosling.schema.guards'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Channel } from '@gosling-lang/gosling-schema'; +import { IsChannelDeep, getValueUsingChannel, Is2DTrack } from '@gosling-lang/gosling-schema'; import { cartesianToPolar, positionToRadian } from '../utils/polar'; import colorToHex from '../utils/color-to-hex'; import { Bezier } from 'bezier-js'; diff --git a/src/core/utils/assembly.test.ts b/src/core/utils/assembly.test.ts index c6963104f..a08cc5a74 100644 --- a/src/core/utils/assembly.test.ts +++ b/src/core/utils/assembly.test.ts @@ -1,4 +1,4 @@ -import type { Assembly } from '@gosling.schema'; +import type { Assembly } from '@gosling-lang/gosling-schema'; import { getChromInterval, getChromTotalSize, diff --git a/src/core/utils/assembly.ts b/src/core/utils/assembly.ts index 25a4830de..6c78fc1f7 100644 --- a/src/core/utils/assembly.ts +++ b/src/core/utils/assembly.ts @@ -1,4 +1,4 @@ -import type { Assembly, ChromSizes, GenomicPosition } from '@gosling.schema'; +import type { Assembly, ChromSizes, GenomicPosition } from '@gosling-lang/gosling-schema'; import { CHROM_SIZE_HG16, CHROM_SIZE_HG17, diff --git a/src/core/utils/color-to-hex.test.ts b/src/core/utils/color-to-hex.test.ts index cf1421c37..915d3d676 100644 --- a/src/core/utils/color-to-hex.test.ts +++ b/src/core/utils/color-to-hex.test.ts @@ -1,4 +1,4 @@ -import { DEFAULT_BACKUP_COLOR } from '../defaults'; +import { DEFAULT_BACKUP_COLOR } from '../../compiler/defaults'; import colorToHex from './color-to-hex'; describe('Color-To-Hex', () => { diff --git a/src/core/utils/color-to-hex.ts b/src/core/utils/color-to-hex.ts index fc64a8743..d34939f4b 100644 --- a/src/core/utils/color-to-hex.ts +++ b/src/core/utils/color-to-hex.ts @@ -1,6 +1,6 @@ import * as PIXI from 'pixi.js'; import { color } from 'd3-color'; -import { DEFAULT_BACKUP_COLOR } from '../defaults'; +import { DEFAULT_BACKUP_COLOR } from '../../compiler/defaults'; /** * Convert a regular color value (e.g. 'red', '#FF0000', 'rgb(255,0,0)') to a hex value which is legible by PIXI. diff --git a/src/core/utils/data-transform.ts b/src/core/utils/data-transform.ts index e935232af..a1ed4220d 100644 --- a/src/core/utils/data-transform.ts +++ b/src/core/utils/data-transform.ts @@ -13,7 +13,7 @@ import type { CoverageTransform, DisplaceTransform, JsonParseTransform -} from '../gosling.schema'; +} from '@gosling-lang/gosling-schema'; import { getChannelKeysByAggregateFnc, getChannelKeysByType, @@ -21,7 +21,7 @@ import { IsIncludeFilter, IsOneOfFilter, IsRangeFilter -} from '../gosling.schema.guards'; +} from '@gosling-lang/gosling-schema'; import { computeChromSizes } from './assembly'; // import Logging from './log'; diff --git a/src/core/utils/linking.test.ts b/src/core/utils/linking.test.ts index dfcbe882a..b753f399e 100644 --- a/src/core/utils/linking.test.ts +++ b/src/core/utils/linking.test.ts @@ -1,6 +1,6 @@ -import { goslingToHiGlass } from '../gosling-to-higlass'; -import { HiGlassModel } from '../higlass-model'; -import { GoslingToHiGlassIdMapper } from '../track-and-view-ids'; +import { goslingToHiGlass } from '../../compiler/gosling-to-higlass'; +import { HiGlassModel } from '../../compiler/higlass-model'; +import { GoslingToHiGlassIdMapper } from '../../api/track-and-view-ids'; import { getLinkingInfo } from './linking'; import { getTheme } from './theme'; diff --git a/src/core/utils/linking.ts b/src/core/utils/linking.ts index 07ece6800..49df909df 100644 --- a/src/core/utils/linking.ts +++ b/src/core/utils/linking.ts @@ -1,5 +1,5 @@ -import { IsChannelDeep } from '../gosling.schema.guards'; -import type { HiGlassModel } from '../higlass-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; +import type { HiGlassModel } from '../../compiler/higlass-model'; import { SUPPORTED_CHANNELS } from '../mark'; import { resolveSuperposedTracks } from './overlay'; diff --git a/src/core/utils/omit-deep.ts b/src/core/utils/omit-deep.ts index cbf9a978d..f44948691 100644 --- a/src/core/utils/omit-deep.ts +++ b/src/core/utils/omit-deep.ts @@ -1,6 +1,6 @@ -import type { GoslingSpec } from '@gosling.schema'; +import type { GoslingSpec } from '@gosling-lang/gosling-schema'; import { cloneDeepWith } from 'lodash-es'; -import { isObject } from '../gosling.schema.guards'; +import { isObject } from '@gosling-lang/gosling-schema'; export function omitDeep(spec: GoslingSpec, omitKeys: string[]) { return cloneDeepWith(spec, (value: unknown) => { diff --git a/src/core/utils/overlay.test.ts b/src/core/utils/overlay.test.ts index 49a5f24dd..1061a766f 100644 --- a/src/core/utils/overlay.test.ts +++ b/src/core/utils/overlay.test.ts @@ -1,5 +1,5 @@ -import type { OverlaidTrack } from '../gosling.schema'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import type { OverlaidTrack } from '@gosling-lang/gosling-schema'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import { resolveSuperposedTracks, spreadTracksByData } from './overlay'; describe('Should handle superposition options correctly', () => { diff --git a/src/core/utils/overlay.ts b/src/core/utils/overlay.ts index 58b38ed94..703fb1ee7 100644 --- a/src/core/utils/overlay.ts +++ b/src/core/utils/overlay.ts @@ -1,5 +1,5 @@ -import type { AxisPosition, SingleTrack, OverlaidTrack, Track, ChannelDeep, DataDeep } from '../gosling.schema'; -import { IsChannelDeep, IsDataTrack, IsOverlaidTrack, IsSingleTrack, IsDummyTrack } from '../gosling.schema.guards'; +import type { AxisPosition, SingleTrack, OverlaidTrack, Track, ChannelDeep, DataDeep } from '@gosling-lang/gosling-schema'; +import { IsChannelDeep, IsDataTrack, IsOverlaidTrack, IsSingleTrack, IsDummyTrack } from '@gosling-lang/gosling-schema'; /** * Resolve superposed tracks into multiple track specifications. diff --git a/src/core/utils/scales.test.ts b/src/core/utils/scales.test.ts index b7523b456..2c29a884e 100644 --- a/src/core/utils/scales.test.ts +++ b/src/core/utils/scales.test.ts @@ -1,8 +1,8 @@ import { getNumericDomain, shareScaleAcrossTracks } from './scales'; -import { GoslingTrackModel } from '../gosling-track-model'; -import { IsChannelDeep } from '../gosling.schema.guards'; +import { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import { IsChannelDeep } from '@gosling-lang/gosling-schema'; import { getTheme } from './theme'; -import type { ChromSizes } from '@gosling.schema'; +import type { ChromSizes } from '@gosling-lang/gosling-schema'; describe('Genomic domain', () => { it('With Chromosome', () => { diff --git a/src/core/utils/scales.ts b/src/core/utils/scales.ts index fa6e8508f..fa8b858b7 100644 --- a/src/core/utils/scales.ts +++ b/src/core/utils/scales.ts @@ -1,7 +1,7 @@ -import type { GoslingTrackModel } from '../gosling-track-model'; -import type { Assembly, Domain } from '../gosling.schema'; +import type { GoslingTrackModel } from '../../tracks/gosling-track/gosling-track-model'; +import type { Assembly, Domain } from '@gosling-lang/gosling-schema'; import { SUPPORTED_CHANNELS } from '../mark'; -import { IsDomainChr, IsDomainInterval, IsDomainChrInterval, IsChannelDeep } from '../gosling.schema.guards'; +import { IsDomainChr, IsDomainInterval, IsDomainChrInterval, IsChannelDeep } from '@gosling-lang/gosling-schema'; import { computeChromSizes } from './assembly'; /** diff --git a/src/core/utils/semantic-zoom.ts b/src/core/utils/semantic-zoom.ts index c8be89a32..f087e0b25 100644 --- a/src/core/utils/semantic-zoom.ts +++ b/src/core/utils/semantic-zoom.ts @@ -1,4 +1,4 @@ -import type { LogicalOperation } from '../gosling.schema'; +import type { LogicalOperation } from '@gosling-lang/gosling-schema'; export function getMaxZoomLevel() { // TODO: How to correctly calculate maxZoomLevel? diff --git a/src/core/utils/style.ts b/src/core/utils/style.ts index e3e47f747..d8221ee03 100644 --- a/src/core/utils/style.ts +++ b/src/core/utils/style.ts @@ -1,4 +1,4 @@ -import type { Style } from '../gosling.schema'; +import type { Style } from '@gosling-lang/gosling-schema'; export function getStyleOverridden(parent: Style | undefined, child: Style | undefined) { // Deep overriding instead of replacing. diff --git a/src/core/utils/template.ts b/src/core/utils/template.ts index b9af63666..85adffce7 100644 --- a/src/core/utils/template.ts +++ b/src/core/utils/template.ts @@ -7,9 +7,9 @@ import type { TemplateTrackDef, TemplateTrackMappingDef, Track -} from '../gosling.schema'; -import { IsTemplateTrack } from '../gosling.schema.guards'; -import { traverseTracks } from './spec-preprocess'; +} from '@gosling-lang/gosling-schema'; +import { IsTemplateTrack } from '@gosling-lang/gosling-schema'; +import { traverseTracks } from '../../compiler/spec-preprocess'; /** * Track templates officially supported by Gosling.js. diff --git a/src/core/utils/view-config.ts b/src/core/utils/view-config.ts index 2154456f7..08e175671 100644 --- a/src/core/utils/view-config.ts +++ b/src/core/utils/view-config.ts @@ -1,4 +1,4 @@ -import type { HiGlassSpec, View } from '../higlass.schema'; +import type { HiGlassSpec, View } from '@gosling-lang/higlass-schema'; /** * Traverse all views in a HiGlass viewConfig. diff --git a/src/data-fetchers/README.md b/src/data-fetchers/README.md new file mode 100644 index 000000000..56a25650c --- /dev/null +++ b/src/data-fetchers/README.md @@ -0,0 +1,3 @@ +# @gosling-lang/data-fetchers + +All data fetchers in Gosling. \ No newline at end of file diff --git a/src/data-fetchers/bam/bam-data-fetcher.ts b/src/data-fetchers/bam/bam-data-fetcher.ts index 6da27ba42..16cc608d1 100644 --- a/src/data-fetchers/bam/bam-data-fetcher.ts +++ b/src/data-fetchers/bam/bam-data-fetcher.ts @@ -5,7 +5,7 @@ import { spawn } from 'threads'; import Worker from './bam-worker.ts?worker&inline'; -import type { BamData, Assembly } from '@gosling.schema'; +import type { BamData, Assembly } from '@gosling-lang/gosling-schema'; import type { ModuleThread } from 'threads'; import type { WorkerApi, TilesetInfo, Tiles, Segment, Junction, SegmentWithMate } from './bam-worker'; import { computeChromSizes } from '../../core/utils/assembly'; diff --git a/src/data-fetchers/bam/bam-worker.ts b/src/data-fetchers/bam/bam-worker.ts index 221086e02..1b71de3ef 100644 --- a/src/data-fetchers/bam/bam-worker.ts +++ b/src/data-fetchers/bam/bam-worker.ts @@ -7,7 +7,7 @@ import type { TilesetInfo } from '@higlass/types'; import type { BamRecord } from '@gmod/bam'; import { DataSource, RemoteFile } from '../utils'; -import type { ChromSizes } from '@gosling.schema'; +import type { ChromSizes } from '@gosling-lang/gosling-schema'; function parseMD(mdString: string, useCounts: true): { type: string; length: number }[]; function parseMD(mdString: string, useCounts: false): { pos: number; base: string; length: 1; bamSeqShift: number }[]; diff --git a/src/data-fetchers/bed/bed-data-fetcher.ts b/src/data-fetchers/bed/bed-data-fetcher.ts index 9d3d33231..2bcd74d1f 100644 --- a/src/data-fetchers/bed/bed-data-fetcher.ts +++ b/src/data-fetchers/bed/bed-data-fetcher.ts @@ -8,7 +8,7 @@ import Worker from './bed-worker.ts?worker&inline'; import { computeChromSizes } from '../../core/utils/assembly'; import type { ModuleThread } from 'threads'; -import type { Assembly, BedData } from '../../core/gosling.schema'; +import type { Assembly, BedData } from '@gosling-lang/gosling-schema'; import type { WorkerApi, TilesetInfo } from './bed-worker'; import type { BedTile, EmptyTile } from './bed-worker'; import type { TabularDataFetcher } from '../utils'; diff --git a/src/data-fetchers/bed/bed-worker.ts b/src/data-fetchers/bed/bed-worker.ts index dfc929641..0c84e6e69 100644 --- a/src/data-fetchers/bed/bed-worker.ts +++ b/src/data-fetchers/bed/bed-worker.ts @@ -7,7 +7,7 @@ import { TabixIndexedFile } from '@gmod/tabix'; import { expose, Transfer } from 'threads/worker'; import { sampleSize } from 'lodash-es'; import type { TilesetInfo } from '@higlass/types'; -import type { ChromSizes } from '@gosling.schema'; +import type { ChromSizes } from '@gosling-lang/gosling-schema'; import { DataSource, RemoteFile } from '../utils'; import BedParser from './bed-parser'; diff --git a/src/data-fetchers/bigwig/bigwig-data-fetcher.ts b/src/data-fetchers/bigwig/bigwig-data-fetcher.ts index 3b787e3c9..9038f10c7 100644 --- a/src/data-fetchers/bigwig/bigwig-data-fetcher.ts +++ b/src/data-fetchers/bigwig/bigwig-data-fetcher.ts @@ -3,7 +3,7 @@ * https://github.com/higlass/higlass-bigwig-datafetcher/blob/main/src/BigwigDataFetcher.js */ import { BigWig } from '@gmod/bbi'; -import type { Assembly, BigWigData } from '@gosling.schema'; +import type { Assembly, BigWigData } from '@gosling-lang/gosling-schema'; import { computeChromSizes } from '../../core/utils/assembly'; import { type CommonDataConfig, RemoteFile } from '../utils'; diff --git a/src/data-fetchers/csv/csv-data-fetcher.ts b/src/data-fetchers/csv/csv-data-fetcher.ts index 69ca71b66..e610928aa 100644 --- a/src/data-fetchers/csv/csv-data-fetcher.ts +++ b/src/data-fetchers/csv/csv-data-fetcher.ts @@ -1,7 +1,7 @@ import { dsvFormat as d3dsvFormat, type DSVRowString } from 'd3-dsv'; import { computeChromSizes } from '../../core/utils/assembly'; import { sampleSize } from 'lodash-es'; -import type { Assembly, CsvData, FilterTransform, Datum } from '@gosling.schema'; +import type { Assembly, CsvData, FilterTransform, Datum } from '@gosling-lang/gosling-schema'; import { filterData } from '../../core/utils/data-transform'; import { type CommonDataConfig, filterUsingGenoPos, sanitizeChrName } from '../utils'; diff --git a/src/data-fetchers/gff/gff-data-fetcher.ts b/src/data-fetchers/gff/gff-data-fetcher.ts index 09ade1333..89a01c9f9 100644 --- a/src/data-fetchers/gff/gff-data-fetcher.ts +++ b/src/data-fetchers/gff/gff-data-fetcher.ts @@ -7,7 +7,7 @@ import Worker from './gff-worker.ts?worker&inline'; import { computeChromSizes } from '../../core/utils/assembly'; import type { ModuleThread } from 'threads'; -import type { Assembly, GffData } from '../../core/gosling.schema'; +import type { Assembly, GffData } from '@gosling-lang/gosling-schema'; import type { WorkerApi, TilesetInfo, GffTile, EmptyTile } from './gff-worker'; import type { TabularDataFetcher } from '../utils'; diff --git a/src/data-fetchers/gff/gff-worker.ts b/src/data-fetchers/gff/gff-worker.ts index 3824e84eb..a02ac8674 100644 --- a/src/data-fetchers/gff/gff-worker.ts +++ b/src/data-fetchers/gff/gff-worker.ts @@ -4,7 +4,7 @@ import type { GFF3FeatureLineWithRefs, GFF3Feature, GFF3Sequence } from '@gmod/g import { expose, Transfer } from 'threads/worker'; import { sampleSize } from 'lodash-es'; import type { TilesetInfo } from '@higlass/types'; -import type { ChromSizes } from '@gosling.schema'; +import type { ChromSizes } from '@gosling-lang/gosling-schema'; import { DataSource, RemoteFile } from '../utils'; import { isGFF3Feature, makeRandomSortedArray } from './utils'; diff --git a/src/data-fetchers/json/json-data-fetcher.ts b/src/data-fetchers/json/json-data-fetcher.ts index 8975ddd32..f8279c98b 100644 --- a/src/data-fetchers/json/json-data-fetcher.ts +++ b/src/data-fetchers/json/json-data-fetcher.ts @@ -1,6 +1,6 @@ import { computeChromSizes } from '../../core/utils/assembly'; import { sampleSize } from 'lodash-es'; -import type { Assembly, JsonData } from '@gosling.schema'; +import type { Assembly, JsonData } from '@gosling-lang/gosling-schema'; import { type CommonDataConfig, filterUsingGenoPos, sanitizeChrName } from '../utils'; type CsvDataConfig = JsonData & CommonDataConfig; diff --git a/src/data-fetchers/utils.test.ts b/src/data-fetchers/utils.test.ts index be846fc0a..28fb89890 100644 --- a/src/data-fetchers/utils.test.ts +++ b/src/data-fetchers/utils.test.ts @@ -1,4 +1,4 @@ -import type { Datum } from '@gosling.schema'; +import type { Datum } from '@gosling-lang/gosling-schema'; import { filterUsingGenoPos } from './utils'; describe('Data Fetcher Utils', () => { diff --git a/src/data-fetchers/utils.ts b/src/data-fetchers/utils.ts index 8664d6b97..b63c72e79 100644 --- a/src/data-fetchers/utils.ts +++ b/src/data-fetchers/utils.ts @@ -2,7 +2,7 @@ import { bisector } from 'd3-array'; import { RemoteFile as _RemoteFile } from 'generic-filehandle'; import type * as HiGlass from '@higlass/types'; -import type { Assembly, ChromSizes, Datum } from '@gosling.schema'; +import type { Assembly, ChromSizes, Datum } from '@gosling-lang/gosling-schema'; export type CommonDataConfig = { assembly: Assembly; diff --git a/src/data-fetchers/vcf/vcf-data-fetcher.ts b/src/data-fetchers/vcf/vcf-data-fetcher.ts index c8a391525..8e8334114 100644 --- a/src/data-fetchers/vcf/vcf-data-fetcher.ts +++ b/src/data-fetchers/vcf/vcf-data-fetcher.ts @@ -8,7 +8,7 @@ import Worker from './vcf-worker.ts?worker&inline'; import { computeChromSizes } from '../../core/utils/assembly'; import type { ModuleThread } from 'threads'; -import type { Assembly, VcfData } from '@gosling.schema'; +import type { Assembly, VcfData } from '@gosling-lang/gosling-schema'; import type { WorkerApi, TilesetInfo } from './vcf-worker'; import type { TabularDataFetcher } from '../utils'; import { getSubstitutionType, getMutationType } from './utils'; diff --git a/src/data-fetchers/vcf/vcf-worker.ts b/src/data-fetchers/vcf/vcf-worker.ts index 68dbd01b3..6bb8ad960 100644 --- a/src/data-fetchers/vcf/vcf-worker.ts +++ b/src/data-fetchers/vcf/vcf-worker.ts @@ -10,7 +10,7 @@ import { sampleSize } from 'lodash-es'; import { DataSource, RemoteFile } from '../utils'; import type { TilesetInfo } from '@higlass/types'; -import type { ChromSizes } from '@gosling.schema'; +import type { ChromSizes } from '@gosling-lang/gosling-schema'; import type { VcfTile } from './vcf-data-fetcher'; import { recordToTile } from './utils'; diff --git a/src/gosling-schema/README.md b/src/gosling-schema/README.md new file mode 100644 index 000000000..21dae69d5 --- /dev/null +++ b/src/gosling-schema/README.md @@ -0,0 +1,3 @@ +# @gosling-lang/schema + +This includes Gosling schema, i.e., the grammar, in both JSON and TypeScript. \ No newline at end of file diff --git a/src/core/gosling.schema.guards.test.ts b/src/gosling-schema/gosling.schema.guards.test.ts similarity index 100% rename from src/core/gosling.schema.guards.test.ts rename to src/gosling-schema/gosling.schema.guards.test.ts diff --git a/src/core/gosling.schema.guards.ts b/src/gosling-schema/gosling.schema.guards.ts similarity index 98% rename from src/core/gosling.schema.guards.ts rename to src/gosling-schema/gosling.schema.guards.ts index 851105fef..43bdc24a2 100644 --- a/src/core/gosling.schema.guards.ts +++ b/src/gosling-schema/gosling.schema.guards.ts @@ -35,7 +35,7 @@ import type { DataTransform, DummyTrack } from './gosling.schema'; -import { SUPPORTED_CHANNELS } from './mark'; +import { SUPPORTED_CHANNELS } from '../core/mark'; import { interpolateGreys, interpolateWarm, @@ -47,7 +47,7 @@ import { interpolateYlOrBr, interpolateRdPu } from 'd3-scale-chromatic'; -import { resolveSuperposedTracks } from './utils/overlay'; +import { resolveSuperposedTracks } from '../core/utils/overlay'; import type { TabularDataFetcher } from '@data-fetchers'; export const PREDEFINED_COLOR_STR_MAP: { [k: string]: (t: number) => string } = { diff --git a/schema/gosling.schema.json b/src/gosling-schema/gosling.schema.json similarity index 100% rename from schema/gosling.schema.json rename to src/gosling-schema/gosling.schema.json diff --git a/src/core/gosling.schema.test.ts b/src/gosling-schema/gosling.schema.test.ts similarity index 100% rename from src/core/gosling.schema.test.ts rename to src/gosling-schema/gosling.schema.test.ts diff --git a/src/core/gosling.schema.ts b/src/gosling-schema/gosling.schema.ts similarity index 100% rename from src/core/gosling.schema.ts rename to src/gosling-schema/gosling.schema.ts diff --git a/src/gosling-schema/index.ts b/src/gosling-schema/index.ts new file mode 100644 index 000000000..e1f84590a --- /dev/null +++ b/src/gosling-schema/index.ts @@ -0,0 +1,5 @@ +export * from './gosling.schema'; +export * from './gosling.schema.guards'; +export * from './validate'; +export { default as ThemeSchema } from './theme.schema.json'; +export { default as GoslingSchema } from './gosling.schema.json'; \ No newline at end of file diff --git a/schema/template.schema.json b/src/gosling-schema/template.schema.json similarity index 100% rename from schema/template.schema.json rename to src/gosling-schema/template.schema.json diff --git a/schema/theme.schema.json b/src/gosling-schema/theme.schema.json similarity index 100% rename from schema/theme.schema.json rename to src/gosling-schema/theme.schema.json diff --git a/src/core/utils/validate.test.ts b/src/gosling-schema/validate.test.ts similarity index 64% rename from src/core/utils/validate.test.ts rename to src/gosling-schema/validate.test.ts index 5378ad39d..d7df22ebd 100644 --- a/src/core/utils/validate.test.ts +++ b/src/gosling-schema/validate.test.ts @@ -1,5 +1,5 @@ -import { validateGoslingSpec } from './validate'; -import { EX_SPEC_CYTOBANDS } from '../../../editor/example/json-spec/ideograms'; +import { validateGoslingSpec } from '@gosling-lang/gosling-schema'; +import { EX_SPEC_CYTOBANDS } from '../../editor/example/json-spec/ideograms'; describe('Validate Spec', () => { it('Example Specs', () => { diff --git a/src/core/utils/validate.ts b/src/gosling-schema/validate.ts similarity index 94% rename from src/core/utils/validate.ts rename to src/gosling-schema/validate.ts index 1b99e2ed9..5e8ae7f1d 100644 --- a/src/core/utils/validate.ts +++ b/src/gosling-schema/validate.ts @@ -1,8 +1,8 @@ import Ajv from 'ajv'; -import type { SingleTrack, ChannelDeep, ChannelTypes, OverlaidTrack, Track } from '../gosling.schema'; -import { IsChannelDeep } from '../gosling.schema.guards'; -import { resolveSuperposedTracks } from './overlay'; -import GoslingSchema from '../../../schema/gosling.schema.json'; +import type { SingleTrack, ChannelDeep, ChannelTypes, OverlaidTrack, Track } from './gosling.schema'; +import { IsChannelDeep } from './gosling.schema.guards'; +import { resolveSuperposedTracks } from '../core/utils/overlay'; +import GoslingSchema from './gosling.schema.json'; export interface Validity { message: string; diff --git a/schema/higlass.schema.json b/src/higlass-schema/higlass.schema.json similarity index 100% rename from schema/higlass.schema.json rename to src/higlass-schema/higlass.schema.json diff --git a/src/core/higlass.schema.ts b/src/higlass-schema/higlass.schema.ts similarity index 98% rename from src/core/higlass.schema.ts rename to src/higlass-schema/higlass.schema.ts index 42d787608..8f0cc9de6 100644 --- a/src/core/higlass.schema.ts +++ b/src/higlass-schema/higlass.schema.ts @@ -1,7 +1,7 @@ // HiGlass Specification Should be consistent to the following scheme: // https://github.com/higlass/higlass/blob/develop/app/schema.json (2ced037) -import type { Assembly, FilterTransform } from '@gosling.schema'; +import type { Assembly, FilterTransform } from '@gosling-lang/gosling-schema'; // The json schema is converted to TypeScript codes using: // https://github.com/quicktype/quicktype diff --git a/src/higlass-schema/index.ts b/src/higlass-schema/index.ts new file mode 100644 index 000000000..137ce2419 --- /dev/null +++ b/src/higlass-schema/index.ts @@ -0,0 +1,2 @@ +export * from './higlass.schema'; +export { default as HiGlassSchema } from './higlass.schema.json'; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 497fb4a9a..da3720a88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,15 @@ -import GoslingSchema from '../schema/gosling.schema.json'; -import ThemeSchema from '../schema/theme.schema.json'; - export { name, version } from '../package.json'; + +import { GoslingSchema, ThemeSchema } from '@gosling-lang/gosling-schema'; export { GoslingSchema, ThemeSchema }; -export type { GoslingSpec, TemplateTrackDef } from './core/gosling.schema'; -export type { HiGlassSpec } from './core/higlass.schema'; +export type { GoslingSpec, TemplateTrackDef } from '@gosling-lang/gosling-schema'; +export type { HiGlassSpec } from '@gosling-lang/higlass-schema'; export { GoslingTemplates } from './core/utils/template'; export type { Theme } from './core/utils/theme'; export { init } from './core/init'; -export { compile } from './core/compile'; -export { validateGoslingSpec } from './core/utils/validate'; +export { compile } from './compiler/compile'; +export { validateGoslingSpec } from '@gosling-lang/gosling-schema'; export { GoslingComponent } from './core/gosling-component'; export type { GoslingRef } from './core/gosling-component'; -export { embed } from './core/gosling-embed'; +export { embed } from './core/gosling-embed'; \ No newline at end of file diff --git a/src/main/README.md b/src/main/README.md new file mode 100644 index 000000000..06505215c --- /dev/null +++ b/src/main/README.md @@ -0,0 +1,5 @@ +# gosling.js + +This folder _will_ make a `gosling.js` npm package importing all subpackages in this repo for the backward compatibility. + +* To-Do: bring the `src/index.ts` under `src/main`. \ No newline at end of file diff --git a/src/dummy-track/dummy-track.ts b/src/tracks/dummy-track/dummy-track.ts similarity index 92% rename from src/dummy-track/dummy-track.ts rename to src/tracks/dummy-track/dummy-track.ts index 691628112..c8d62b7f5 100644 --- a/src/dummy-track/dummy-track.ts +++ b/src/tracks/dummy-track/dummy-track.ts @@ -1,6 +1,6 @@ -import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../core/utils/define-plugin-track'; -import { publish } from '../core/pubsub'; -import { type DummyTrackStyle } from '@gosling.schema'; +import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../../core/utils/define-plugin-track'; +import { publish } from '../../api/pubsub'; +import { type DummyTrackStyle } from '@gosling-lang/gosling-schema'; interface DummyTrackOptions extends DummyTrackStyle { title: string; diff --git a/src/dummy-track/index.ts b/src/tracks/dummy-track/index.ts similarity index 100% rename from src/dummy-track/index.ts rename to src/tracks/dummy-track/index.ts diff --git a/src/gosling-brush/brush-track.ts b/src/tracks/gosling-brush/brush-track.ts similarity index 99% rename from src/gosling-brush/brush-track.ts rename to src/tracks/gosling-brush/brush-track.ts index 9409fda3c..cfc5de50b 100644 --- a/src/gosling-brush/brush-track.ts +++ b/src/tracks/gosling-brush/brush-track.ts @@ -1,7 +1,7 @@ import { arc as d3arc } from 'd3-shape'; import type { SubjectPosition, D3DragEvent } from 'd3-drag'; import * as uuid from 'uuid'; -import { RADIAN_GAP, valueToRadian } from '../core/utils/polar'; +import { RADIAN_GAP, valueToRadian } from '../../core/utils/polar'; type CircularBrushData = { type: 'brush' | 'start' | 'end'; diff --git a/src/gosling-brush/index.ts b/src/tracks/gosling-brush/index.ts similarity index 100% rename from src/gosling-brush/index.ts rename to src/tracks/gosling-brush/index.ts diff --git a/src/gosling-brush/linear-brush-model.ts b/src/tracks/gosling-brush/linear-brush-model.ts similarity index 99% rename from src/gosling-brush/linear-brush-model.ts rename to src/tracks/gosling-brush/linear-brush-model.ts index 7515afe9a..7d0fe7fc6 100644 --- a/src/gosling-brush/linear-brush-model.ts +++ b/src/tracks/gosling-brush/linear-brush-model.ts @@ -1,7 +1,7 @@ import { createNanoEvents, Emitter } from 'nanoevents'; import type * as D3Selection from 'd3-selection'; import type * as D3Drag from 'd3-drag'; -import type { EventStyle } from '@gosling.schema'; +import type { EventStyle } from '@gosling-lang/gosling-schema'; const HIDDEN_BRUSH_EDGE_SIZE = 3; diff --git a/src/gosling-genomic-axis/axis-track.ts b/src/tracks/gosling-genomic-axis/axis-track.ts similarity index 98% rename from src/gosling-genomic-axis/axis-track.ts rename to src/tracks/gosling-genomic-axis/axis-track.ts index a36c731f6..f1798dac0 100644 --- a/src/gosling-genomic-axis/axis-track.ts +++ b/src/tracks/gosling-genomic-axis/axis-track.ts @@ -5,14 +5,14 @@ import type * as PIXI from 'pixi.js'; import RBush from 'rbush'; import { scaleLinear } from 'd3-scale'; import { format, precisionPrefix, formatPrefix } from 'd3-format'; -import { computeChromSizes } from '../core/utils/assembly'; -import { cartesianToPolar } from '../core/utils/polar'; -import { getTextStyle } from '../core/utils/text-style'; -import { createPluginTrack } from '../core/utils/define-plugin-track'; - -import type { TextStyle } from '../core/utils/text-style'; -import type { PluginTrackFactory, TrackConfig } from '../core/utils/define-plugin-track'; -import type { Assembly } from '@gosling.schema'; +import { computeChromSizes } from '../../core/utils/assembly'; +import { cartesianToPolar } from '../../core/utils/polar'; +import { getTextStyle } from '../../core/utils/text-style'; +import { createPluginTrack } from '../../core/utils/define-plugin-track'; + +import type { TextStyle } from '../../core/utils/text-style'; +import type { PluginTrackFactory, TrackConfig } from '../../core/utils/define-plugin-track'; +import type { Assembly } from '@gosling-lang/gosling-schema'; const TICK_WIDTH = 200; const TICK_HEIGHT = 6; diff --git a/src/gosling-genomic-axis/index.ts b/src/tracks/gosling-genomic-axis/index.ts similarity index 100% rename from src/gosling-genomic-axis/index.ts rename to src/tracks/gosling-genomic-axis/index.ts diff --git a/src/gosling-track/data-abstraction.ts b/src/tracks/gosling-track/data-abstraction.ts similarity index 99% rename from src/gosling-track/data-abstraction.ts rename to src/tracks/gosling-track/data-abstraction.ts index 4cc97206e..684123d55 100644 --- a/src/gosling-track/data-abstraction.ts +++ b/src/tracks/gosling-track/data-abstraction.ts @@ -1,6 +1,6 @@ import type { SparseTile, TileData } from '@higlass/services'; -import type { Datum, SingleTrack } from '../core/gosling.schema'; -import { IsDataDeepTileset } from '../core/gosling.schema.guards'; +import type { Datum, SingleTrack } from '@gosling-lang/gosling-schema'; +import { IsDataDeepTileset } from '@gosling-lang/gosling-schema'; export const GOSLING_DATA_ROW_UID_FIELD = 'gosling-data-row-uid'; diff --git a/src/gosling-mouse-event/index.ts b/src/tracks/gosling-track/gosling-mouse-event/index.ts similarity index 100% rename from src/gosling-mouse-event/index.ts rename to src/tracks/gosling-track/gosling-mouse-event/index.ts diff --git a/src/gosling-mouse-event/mouse-event-model.test.ts b/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.test.ts similarity index 100% rename from src/gosling-mouse-event/mouse-event-model.test.ts rename to src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.test.ts diff --git a/src/gosling-mouse-event/mouse-event-model.ts b/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts similarity index 98% rename from src/gosling-mouse-event/mouse-event-model.ts rename to src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts index 6c18cc5b1..4cca16122 100644 --- a/src/gosling-mouse-event/mouse-event-model.ts +++ b/src/tracks/gosling-track/gosling-mouse-event/mouse-event-model.ts @@ -1,4 +1,4 @@ -import type { Datum } from '@gosling.schema'; +import type { Datum } from '@gosling-lang/gosling-schema'; import { isAllPointsWithinRange, isPointInPolygon, diff --git a/src/gosling-mouse-event/polygon.ts b/src/tracks/gosling-track/gosling-mouse-event/polygon.ts similarity index 98% rename from src/gosling-mouse-event/polygon.ts rename to src/tracks/gosling-track/gosling-mouse-event/polygon.ts index 83f72ef70..5ee9c7e84 100644 --- a/src/gosling-mouse-event/polygon.ts +++ b/src/tracks/gosling-track/gosling-mouse-event/polygon.ts @@ -1,4 +1,4 @@ -import { pointsToDegree } from '../core/utils/polar'; +import { pointsToDegree } from '../../../core/utils/polar'; /** * @param point Tuple of the form `[x,y]` to be tested. diff --git a/src/core/gosling-track-model.test.ts b/src/tracks/gosling-track/gosling-track-model.test.ts similarity index 97% rename from src/core/gosling-track-model.test.ts rename to src/tracks/gosling-track/gosling-track-model.test.ts index 0bfa88b76..d5a701768 100644 --- a/src/core/gosling-track-model.test.ts +++ b/src/tracks/gosling-track/gosling-track-model.test.ts @@ -1,8 +1,8 @@ import { GoslingTrackModel } from './gosling-track-model'; -import type { Track } from './gosling.schema'; +import type { Track } from '@gosling-lang/gosling-schema'; import { isEqual } from 'lodash-es'; -import { IsChannelDeep, IsChannelValue } from './gosling.schema.guards'; -import { getTheme } from './utils/theme'; +import { IsChannelDeep, IsChannelValue } from '@gosling-lang/gosling-schema'; +import { getTheme } from '../../core/utils/theme'; const MINIMAL_TRACK_SPEC: Track = { data: { url: '', type: 'csv' }, diff --git a/src/core/gosling-track-model.ts b/src/tracks/gosling-track/gosling-track-model.ts similarity index 97% rename from src/core/gosling-track-model.ts rename to src/tracks/gosling-track/gosling-track-model.ts index cce305aa1..3c926d9c2 100644 --- a/src/core/gosling-track-model.ts +++ b/src/tracks/gosling-track/gosling-track-model.ts @@ -8,8 +8,8 @@ import type { Channel, Color, Stroke -} from './gosling.schema'; -import { validateTrack, getGenomicChannelFromTrack, getGenomicChannelKeyFromTrack } from './utils/validate'; +} from '@gosling-lang/gosling-schema'; +import { validateTrack, getGenomicChannelFromTrack, getGenomicChannelKeyFromTrack } from '@gosling-lang/gosling-schema'; import { type ScaleLinear, scaleLinear, @@ -22,15 +22,15 @@ import { } from 'd3-scale'; import { interpolateViridis } from 'd3-scale-chromatic'; import { min as d3min, max as d3max, sum as d3sum, group } from 'd3-array'; -import { HIGLASS_AXIS_SIZE } from './higlass-model'; -import { SUPPORTED_CHANNELS } from './mark'; -import type { PIXIVisualProperty } from './visual-property.schema'; -import { rectProperty } from './mark/rect'; -import { pointProperty } from './mark/point'; -import { barProperty } from './mark/bar'; -import { getNumericDomain } from './utils/scales'; -import { logicalComparison } from './utils/semantic-zoom'; -import { aggregateData } from './utils/data-transform'; +import { HIGLASS_AXIS_SIZE } from '../../compiler/higlass-model'; +import { SUPPORTED_CHANNELS } from '../../core/mark'; +import type { PIXIVisualProperty } from '../../core/visual-property.schema'; +import { rectProperty } from '../../core/mark/rect'; +import { pointProperty } from '../../core/mark/point'; +import { barProperty } from '../../core/mark/bar'; +import { getNumericDomain } from '../../core/utils/scales'; +import { logicalComparison } from '../../core/utils/semantic-zoom'; +import { aggregateData } from '../../core/utils/data-transform'; import { IsChannelDeep, IsChannelValue, @@ -39,10 +39,10 @@ import { IsDomainArray, PREDEFINED_COLOR_STR_MAP, IsRangeArray -} from './gosling.schema.guards'; -import { CHANNEL_DEFAULTS } from './channel'; -import { type CompleteThemeDeep, getTheme } from './utils/theme'; -import { MouseEventModel } from '@gosling-mouse-event'; +} from '@gosling-lang/gosling-schema'; +import { CHANNEL_DEFAULTS } from '../../core/channel'; +import { type CompleteThemeDeep, getTheme } from '../../core/utils/theme'; +import { MouseEventModel } from '../gosling-track/gosling-mouse-event'; export type ScaleType = | ScaleLinear diff --git a/src/gosling-track/gosling-track.test.ts b/src/tracks/gosling-track/gosling-track.test.ts similarity index 100% rename from src/gosling-track/gosling-track.test.ts rename to src/tracks/gosling-track/gosling-track.test.ts diff --git a/src/gosling-track/gosling-track.ts b/src/tracks/gosling-track/gosling-track.ts similarity index 98% rename from src/gosling-track/gosling-track.ts rename to src/tracks/gosling-track/gosling-track.ts index 0936e83df..1fd74c5da 100644 --- a/src/gosling-track/gosling-track.ts +++ b/src/tracks/gosling-track/gosling-track.ts @@ -11,21 +11,21 @@ import type { Assembly, ValueExtent, Range -} from '@gosling.schema'; -import { type MouseEventData, isPointInsideDonutSlice } from '@gosling-mouse-event'; +} from '@gosling-lang/gosling-schema'; +import { type MouseEventData, isPointInsideDonutSlice } from '../gosling-track/gosling-mouse-event'; import { BamDataFetcher, type TabularDataFetcher } from '@data-fetchers'; import type { Tile as _Tile, TileData, TileDataBase } from '@higlass/services'; -import { LinearBrushModel } from '@gosling-brush'; +import { LinearBrushModel } from '@gosling-lang/gosling-brush'; import { getTheme } from 'gosling-theme'; import { getTabularData } from './data-abstraction'; -import type { CompleteThemeDeep } from '../core/utils/theme'; -import { drawMark, drawPostEmbellishment, drawPreEmbellishment } from '../core/mark'; -import { GoslingTrackModel } from '../core/gosling-track-model'; -import { validateTrack } from '../core/utils/validate'; -import { shareScaleAcrossTracks } from '../core/utils/scales'; -import { resolveSuperposedTracks } from '../core/utils/overlay'; -import colorToHex from '../core/utils/color-to-hex'; +import type { CompleteThemeDeep } from '../../core/utils/theme'; +import { drawMark, drawPostEmbellishment, drawPreEmbellishment } from '../../core/mark'; +import { GoslingTrackModel } from './gosling-track-model'; +import { validateTrack } from '@gosling-lang/gosling-schema'; +import { shareScaleAcrossTracks } from '../../core/utils/scales'; +import { resolveSuperposedTracks } from '../../core/utils/overlay'; +import colorToHex from '../../core/utils/color-to-hex'; import { aggregateCoverage, calculateData, @@ -37,10 +37,10 @@ import { replaceString, splitExon, inferSvType -} from '../core/utils/data-transform'; -import { publish } from '../core/pubsub'; -import { getRelativeGenomicPosition } from '../core/utils/assembly'; -import { getTextStyle } from '../core/utils/text-style'; +} from '../../core/utils/data-transform'; +import { publish } from '../../api/pubsub'; +import { getRelativeGenomicPosition } from '../../core/utils/assembly'; +import { getTextStyle } from '../../core/utils/text-style'; import { Is2DTrack, IsChannelDeep, @@ -48,10 +48,10 @@ import { IsXAxis, isTabularDataFetcher, hasDataTransform -} from '../core/gosling.schema.guards'; -import { HIGLASS_AXIS_SIZE } from '../core/higlass-model'; -import { flatArrayToPairArray } from '../core/utils/array'; -import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../core/utils/define-plugin-track'; +} from '@gosling-lang/gosling-schema'; +import { HIGLASS_AXIS_SIZE } from '../../compiler/higlass-model'; +import { flatArrayToPairArray } from '../../core/utils/array'; +import { createPluginTrack, type PluginTrackFactory, type TrackConfig } from '../../core/utils/define-plugin-track'; // Set `true` to print in what order each function is called export const PRINT_RENDERING_CYCLE = false; diff --git a/src/gosling-track/index.ts b/src/tracks/gosling-track/index.ts similarity index 100% rename from src/gosling-track/index.ts rename to src/tracks/gosling-track/index.ts diff --git a/tsconfig.json b/tsconfig.json index 147cc628b..fc691b529 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,13 +24,12 @@ "jsx": "react", "paths": { "gosling.js": ["./src/index.ts"], - "@gosling.schema": ["./src/core/gosling.schema.ts"], - "@higlass.schema": ["./src/core/higlass.schema.ts"], - "@gosling-mouse-event": ["./src/gosling-mouse-event/index.ts"], - "@gosling-track": ["./src/gosling-track/index.ts"], - "@gosling-genomic-axis": ["./src/gosling-genomic-axis/index.ts"], - "@gosling-brush": ["./src/gosling-brush/index.ts"], - "@gosling-lang/dummy-track": ["./src/dummy-track/index.ts"], + "@gosling-lang/gosling-schema": ["src/gosling-schema/index.ts"], + "@gosling-lang/higlass-schema": ["src/higlass-schema/index.ts"], + "@gosling-lang/gosling-track": ["./src/tracks/gosling-track/index.ts"], + "@gosling-lang/gosling-genomic-axis": ["./src/tracks/gosling-genomic-axis/index.ts"], + "@gosling-lang/gosling-brush": ["./src/tracks/gosling-brush/index.ts"], + "@gosling-lang/dummy-track": ["./src/tracks/dummy-track/index.ts"], "@data-fetchers": ["./src/data-fetchers/index.ts"], "zlib": ["./src/alias/zlib.ts"] }, diff --git a/vite.config.js b/vite.config.js index 608396615..f4c136834 100644 --- a/vite.config.js +++ b/vite.config.js @@ -55,13 +55,12 @@ export default function() { const alias = { 'gosling.js': path.resolve(__dirname, './src/index.ts'), - '@gosling.schema': path.resolve(__dirname, './src/core/gosling.schema'), - '@higlass.schema': path.resolve(__dirname, './src/core/higlass.schema'), - "@gosling-track": path.resolve(__dirname, "./src/gosling-track/index.ts"), - "@gosling-mouse-event": path.resolve(__dirname, "./src/gosling-mouse-event/index.ts"), - "@gosling-genomic-axis": path.resolve(__dirname, "./src/gosling-genomic-axis/index.ts"), - "@gosling-brush": path.resolve(__dirname, "./src/gosling-brush/index.ts"), - "@gosling-lang/dummy-track": path.resolve(__dirname, "./src/dummy-track/index.ts"), + '@gosling-lang/gosling-schema': path.resolve(__dirname, './src/gosling-schema/index.ts'), + '@gosling-lang/higlass-schema': path.resolve(__dirname, './src/higlass-schema/index.ts'), + "@gosling-lang/gosling-track": path.resolve(__dirname, "./src/tracks/gosling-track/index.ts"), + "@gosling-lang/gosling-genomic-axis": path.resolve(__dirname, "./src/tracks/gosling-genomic-axis/index.ts"), + "@gosling-lang/gosling-brush": path.resolve(__dirname, "./src/tracks/gosling-brush/index.ts"), + "@gosling-lang/dummy-track": path.resolve(__dirname, "./src/tracks/dummy-track/index.ts"), "@data-fetchers": path.resolve(__dirname, "./src/data-fetchers/index.ts"), zlib: path.resolve(__dirname, './src/alias/zlib.ts'), uuid: path.resolve(__dirname, './node_modules/uuid/dist/esm-browser/index.js'), From 09a5207fb58c877484911b7158724deec6304f67 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Mon, 28 Aug 2023 12:57:54 -0400 Subject: [PATCH 25/25] fix(core): rule mark calculation in circular layout (#967) Fixes the alignment of rule marks in circular layouts --- src/core/mark/rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/mark/rule.ts b/src/core/mark/rule.ts index 836def76b..a47f5bd22 100644 --- a/src/core/mark/rule.ts +++ b/src/core/mark/rule.ts @@ -142,7 +142,7 @@ export function drawRule(HGC: import('@higlass/types').HGC, trackInfo: any, tile 0.5 // alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter) ); - const midR = trackOuterRadius - ((rowPosition + y) / trackHeight) * trackRingSize; + const midR = trackOuterRadius - ((rowPosition + rowHeight - y) / trackHeight) * trackRingSize; const farR = midR + strokeWidth / 2.0; const nearR = midR - strokeWidth / 2.0;