diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts index 95d301d3e4addf1bdd3406be35167d0b5b47d017..ecd059f0a8fd1411e9a5c7b211c7aac78cff91ae 100644 --- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts @@ -53,6 +53,7 @@ export class Autoradiography extends BaseReceptor implements OnChanges, AfterVie } rerender(){ + this.dataBlobAvailable = false if (!this.el || !this.renderBuffer) { this.pleaseRender = true return @@ -71,6 +72,11 @@ export class Autoradiography extends BaseReceptor implements OnChanges, AfterVie const imgData = ctx.createImageData(this.width, this.height) imgData.data.set(this.renderBuffer) ctx.putImageData(imgData, 0, 0) + + canvas.toBlob(blob => { + this.dataBlobAvailable = true + this.dataBlob$.next(blob) + }) this.pleaseRender = false } } diff --git a/src/atlasComponents/sapiViews/features/receptors/base.ts b/src/atlasComponents/sapiViews/features/receptors/base.ts index cbcd75fc2460c847e907d4c312eb0eb9ea19b081..2428c72f6e315c97f98a5914c867d7c6c7560af8 100644 --- a/src/atlasComponents/sapiViews/features/receptors/base.ts +++ b/src/atlasComponents/sapiViews/features/receptors/base.ts @@ -1,4 +1,5 @@ import { Directive, Input, SimpleChanges } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; @@ -83,6 +84,15 @@ export abstract class BaseReceptor{ abstract rerender(): void + /** + * flag to indicate that getDataBlob() can be called. + */ + dataBlobAvailable = false + /** + * blob object observable, representing the data of the component. This allows the data to be downloaded. + */ + dataBlob$ = new BehaviorSubject<Blob>(null) + constructor( protected sapi: SAPI ){ diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts index 25ee164f8e3f99cd9e2ee4944276224097e8dbc1..3f6a756c1b26a15d26a93c4900347072cd79be27 100644 --- a/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts @@ -1,6 +1,5 @@ -import { ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { ChangeDetectorRef, Component, OnChanges, SimpleChanges } from "@angular/core"; import { SAPI } from "src/atlasComponents/sapi"; -import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; import { BaseReceptor } from "../base"; @Component({ @@ -37,4 +36,8 @@ export class Entry extends BaseReceptor implements OnChanges { setSelectedSymbol(select: string){ this.selectedSymbol = select } + + getDataBlob(): Promise<Blob> { + throw new Error(`cannot get blob of entry component`) + } } diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html index 33636fa42ac00bee0b0e9e29a9caa54d200f8453..e88ce49a8da4549d21f77e950af2c9a1361a4fb1 100644 --- a/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html @@ -1,9 +1,17 @@ <mat-card> <spinner-cmp *ngIf="loading"></spinner-cmp> + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'fingerprint.tsv', + filename: 'fingerprint.tsv', + receptorCmp: fp + }"> + </ng-template> <sxplr-sapiviews-features-receptor-fingerprint [sxplr-sapiviews-features-receptor-data]="receptorData" (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="setSelectedSymbol($event)" + #fp="sxplrSapiViewsFeaturesReceptorFP" > </sxplr-sapiviews-features-receptor-fingerprint> @@ -22,16 +30,51 @@ <ng-template [ngIf]="selectedSymbol"> + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'profile.tsv', + filename: 'profile.tsv', + receptorCmp: profile + }"> + </ng-template> <sxplr-sapiviews-features-receptor-profile [sxplr-sapiviews-features-receptor-data]="receptorData" - [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol"> + [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol" + #profile="sxplrSapiViewsFeaturesReceptorProfile"> </sxplr-sapiviews-features-receptor-profile> + + <ng-template [ngTemplateOutlet]="downloadBtn" + [ngTemplateOutletContext]="{ + label: 'autoradiograph.png', + filename: 'autoradiograph.png', + receptorCmp: ar + }"> + </ng-template> <sxplr-sapiviews-features-receptor-autoradiograph [sxplr-sapiviews-features-receptor-data]="receptorData" [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol" + #ar="sxplrSapiViewsFeaturesReceptorAR" > </sxplr-sapiviews-features-receptor-autoradiograph> </ng-template> </mat-card> + + +<!-- download data button template --> +<ng-template #downloadBtn + let-label="label" + let-filename="filename" + let-receptorCmp="receptorCmp"> + <button mat-button + *ngIf="receptorCmp.dataBlobAvailable" + single-file-output + [single-file-output-blob]="receptorCmp.dataBlob$ | async" + [single-file-output-filename]="filename"> + <i class="fas fa-download"></i> + <span> + {{ label }} + </span> + </button> +</ng-template> diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts index 1a9bd2b0142aecbacb4cff82818e5df17faf5471..98c5b0076da0350cd0a30fdc01633ee57b6e3bab 100644 --- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts @@ -45,7 +45,8 @@ function transformRadar(input: SapiRegionalFeatureReceptorModel['data']['fingerp templateUrl: './fingerprint.template.html', styleUrls: [ './fingerprint.style.css' - ] + ], + exportAs: "sxplrSapiViewsFeaturesReceptorFP" }) export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewInit, OnDestroy{ @@ -105,6 +106,23 @@ export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewIni this.dumbRadarCmp.metaBs = this.receptorData.data.receptor_symbols this.dumbRadarCmp.radar= transformRadar(this.receptorData.data.fingerprints) - this.setDumbRadarPlease = false + this.dataBlob$.next(this.getDataBlob()) + this.dataBlobAvailable = true + this.setDumbRadarPlease = false + } + + private getDataBlob(): Blob { + if (!this.receptorData?.data?.fingerprints) throw new Error(`raw data unavailable. Try again later.`) + const fingerprints = this.receptorData.data.fingerprints + const output: string[] = [] + output.push( + ["name", "mean", "std"].join("\t") + ) + for (const key in fingerprints) { + output.push( + [key, fingerprints[key].mean, fingerprints[key].std].join("\t") + ) + } + return new Blob([output.join("\n")], { type: 'text/tab-separated-values' }) } } diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts index ca559de014f96f1e54dd38a6313ec1f6bcf3b69c..d5bd04dc6b11f71c1646b4c8b2892c6ddf372f6b 100644 --- a/src/atlasComponents/sapiViews/features/receptors/module.ts +++ b/src/atlasComponents/sapiViews/features/receptors/module.ts @@ -5,6 +5,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { SpinnerModule } from "src/components/spinner"; import { AngularMaterialModule } from "src/sharedModules"; import { APPEND_SCRIPT_TOKEN } from "src/util/constants"; +import { ZipFilesOutputModule } from "src/zipFilesOutput/module"; import { Autoradiography } from "./autoradiography/autoradiography.component"; import { Entry } from "./entry/entry.component"; import { Fingerprint } from "./fingerprint/fingerprint.component" @@ -17,6 +18,7 @@ import { Profile } from "./profile/profile.component" FormsModule, BrowserAnimationsModule, SpinnerModule, + ZipFilesOutputModule, ], declarations: [ Autoradiography, diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts index a50b2a5be99f761546ae90d22c9d55285496b44c..864584597568df263ed93be923f757bcd585f125 100644 --- a/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts @@ -10,7 +10,8 @@ import { BaseReceptor } from "../base"; templateUrl: './profile.template.html', styleUrls: [ './profile.style.css' - ] + ], + exportAs: "sxplrSapiViewsFeaturesReceptorProfile" }) export class Profile extends BaseReceptor implements AfterViewInit, OnChanges{ @@ -46,6 +47,7 @@ export class Profile extends BaseReceptor implements AfterViewInit, OnChanges{ } async rerender() { + this.dataBlobAvailable = false if (!this.dumbLineCmp) { this.pleaseRender = true return @@ -67,7 +69,23 @@ export class Profile extends BaseReceptor implements AfterViewInit, OnChanges{ for (const idx in prof) { this.dumbLineData[idx] = prof[idx] } + this.dataBlob$.next(this.getDataBlob()) + this.dataBlobAvailable = true this.dumbLineCmp.profileBs = this.dumbLineData } } + + private getDataBlob(): Blob { + if (!this.dumbLineData) throw new Error(`data has not been populated. Perhaps wait until render finishes?`) + const output: string[] = [] + output.push( + ["cortical depth (%)", "receptor density (fmol/mg)"].join("\t") + ) + for (const key in this.dumbLineData) { + output.push( + [key, this.dumbLineData[key]].join("\t") + ) + } + return new Blob([output.join("\n")], { type: 'text/tab-separated-values' }) + } } diff --git a/src/zipFilesOutput/downloadSingleFile.directive.ts b/src/zipFilesOutput/downloadSingleFile.directive.ts index 80d8eef4a2f05a32f068da4d15d9061dd5f45bf3..d23f0dad79766203bba582003d39208a609f5505 100644 --- a/src/zipFilesOutput/downloadSingleFile.directive.ts +++ b/src/zipFilesOutput/downloadSingleFile.directive.ts @@ -12,12 +12,18 @@ export class SingleFileOutput { @Input('single-file-output') singleFile: TZipFileConfig + @Input('single-file-output-filename') + singleFileFileName: string + + @Input('single-file-output-blob') + singleFileBlob: Blob + @HostListener('click') onClick(): void{ const anchor = this.doc.createElement('a') - const blob = new Blob([this.singleFile.filecontent], { type: 'text/plain' }) + const blob = this.singleFileBlob || new Blob([this.singleFile.filecontent], { type: 'text/plain' }) anchor.href = URL.createObjectURL(blob) - anchor.download = this.singleFile.filename + anchor.download = this.singleFileFileName || this.singleFile.filename this.doc.body.appendChild(anchor) anchor.click()