diff --git a/src/guiTests/6viewAlignCoverage.ts b/src/guiTests/6viewAlignCoverage.ts index d725ae2eb..4cb31704b 100644 --- a/src/guiTests/6viewAlignCoverage.ts +++ b/src/guiTests/6viewAlignCoverage.ts @@ -8,7 +8,7 @@ import {openCircularGenomeBuilderWindow} from "./req/circularGenomeBuilder/openC import {toggleFiguresOverlay} from "./req/circularGenomeBuilder/toggleFiguresOverlay"; import {createHPV16Figure} from "./req/circularGenomeBuilder/createHPV16Figure"; import {closeToolBar} from "./req/closeToolBar"; -import { expandHPV16FigureList } from './req/circularGenomeBuilder/expandHPV16FigureList'; +import {expandHPV16FigureList} from "./req/circularGenomeBuilder/expandHPV16FigureList"; async function runTest() : Promise { diff --git a/src/req/renderer/circularFigure/circularFigure.ts b/src/req/renderer/circularFigure/circularFigure.ts index 47cca7d78..767b6de68 100644 --- a/src/req/renderer/circularFigure/circularFigure.ts +++ b/src/req/renderer/circularFigure/circularFigure.ts @@ -1179,7 +1179,6 @@ export function renderSVGToCanvas(svg: string, ctx: CanvasRenderingContext2D): P { ctx.drawImage(img, 0, 0); window.URL.revokeObjectURL(url); - console.log("Drew image"); resolve(); }; img.src = url; diff --git a/src/req/renderer/containers/circularGenome/cachedPlasmid.ts b/src/req/renderer/containers/circularGenome/cachedPlasmid.ts new file mode 100644 index 000000000..9923b4fac --- /dev/null +++ b/src/req/renderer/containers/circularGenome/cachedPlasmid.ts @@ -0,0 +1,158 @@ +import {Plasmid} from "../../../ngplasmid/lib/plasmid"; +import {CircularFigure, buildBaseFigureTemplate, assembleCompilableTemplates, assembleCompilableCoverageTrack, makeMapScope} from "../../circularFigure/circularFigure"; +import {Node, loadFromString} from "../../../ngplasmid/lib/html"; + +export interface CachedPlasmid { + uuid: string; + plasmid: Plasmid | undefined; + oldStrScope: string; +} + +export class PlasmidCacheMgr +{ + protected plasmidCache: Array = []; + + public prunePlasmidCache(figure: CircularFigure): void + { + + for (let i = this.plasmidCache.length - 1; i >= 0; --i) + { + let found = false; + for (let k = 0; k != figure.visibleLayers.length; ++k) + { + if (figure.visibleLayers[k] == this.plasmidCache[i].uuid) + { + found = true; + } + } + + if (!found) + { + this.plasmidCache.splice(i, 1); + } + } + } + + + public findPlasmidInCache(target: string): CachedPlasmid | undefined + { + return this.plasmidCache.find((x) => x.uuid == target); + + } + + public setOldScope(target: string, figure: CircularFigure): void + { + + let entry = this.plasmidCache.find((x) => x.uuid == target); + + if (entry) + { + entry.oldStrScope = JSON.stringify(makeMapScope(figure)); + } + + } + + public async loadPlasmidCacheEntry(target: string, figure: CircularFigure): Promise + { + let scope = {genome: figure}; + + let oldStrScope = JSON.stringify(scope); + if (target == figure.uuid) + { + console.log("Loading base figure"); + console.log(figure.name); + + let plasmid: Plasmid = new Plasmid(); + plasmid.$scope = scope; + + let nodes: Array = await loadFromString( + assembleCompilableTemplates(figure, + buildBaseFigureTemplate(figure) + ) + ); + + for (let i = 0; i != nodes.length; ++i) + { + if (nodes[i].name == "div") + { + for (let k = 0; k != nodes[i].children.length; ++k) + { + if (nodes[i].children[k].name == "plasmid") + { + plasmid.fromNode(nodes[i].children[k]); + return { + uuid: figure.uuid, + plasmid: plasmid, + oldStrScope: oldStrScope + }; + } + } + } + } + } + + else + { + let plasmid = await this.maybeLoadPlasmid(target, figure); + + if (plasmid) + { + this.plasmidCache = [ + ...this.plasmidCache, + { + uuid: target, + plasmid: plasmid, + oldStrScope: oldStrScope + } + ]; + + return {uuid: target, plasmid: plasmid, oldStrScope: oldStrScope}; + } + return undefined; + } + return undefined; + } + + protected async maybeLoadPlasmid(target: string, figure: CircularFigure): Promise + { + let scope = {genome: figure}; + + let coverageTrack = figure.renderedCoverageTracks.find((x) => x.uuid == target); + if (coverageTrack) + { + let plasmid: Plasmid = new Plasmid(); + plasmid.$scope = scope; + + let nodes: Array = await loadFromString(assembleCompilableCoverageTrack(figure, coverageTrack)); + + for (let i = 0; i != nodes.length; ++i) + { + if (nodes[i].name == "div") + { + for (let k = 0; k != nodes[i].children.length; ++k) + { + if (nodes[i].children[k].name == "plasmid") + { + plasmid.fromNode(nodes[i].children[k]); + + setTimeout(() => + { + let cacheEntry = this.plasmidCache.find((x) => x.uuid == target); + if (cacheEntry) + { + cacheEntry.plasmid = undefined; + console.log("Unloaded ", target); + } + }, 5000); + + console.log("Loaded ", target); + return plasmid; + } + } + } + } + } + return undefined; + } +} + diff --git a/src/req/renderer/containers/circularGenome/circularGenome.tsx b/src/req/renderer/containers/circularGenome/circularGenome.tsx index 6fa2df286..57d486883 100644 --- a/src/req/renderer/containers/circularGenome/circularGenome.tsx +++ b/src/req/renderer/containers/circularGenome/circularGenome.tsx @@ -1,29 +1,27 @@ import * as React from "react"; -import {CircularFigure, assembleCompilableTemplates, buildBaseFigureTemplate, assembleCompilableCoverageTrack, renderSVGToCanvas} from "../../circularFigure/circularFigure"; -import {Plasmid} from "../../../ngplasmid/lib/plasmid"; -import {Node, loadFromString} from "../../../ngplasmid/lib/html"; +import {CircularFigure, renderSVGToCanvas, CoverageTrackLayer, SNPTrackLayer, MapScope} from "../../circularFigure/circularFigure"; + +import {PlasmidCacheMgr} from "./cachedPlasmid"; + +export interface CircularGenomeState { -export interface CircularGenomeState -{ - } -export interface CircularGenomeProps -{ - shouldUpateCanvas : boolean | undefined; - figure : CircularFigure; - width : number; - height : number; - x : number; - y : number; +export interface CircularGenomeProps { + shouldUpateCanvas: boolean | undefined; + figure: CircularFigure; + width: number; + height: number; + x: number; + y: number; } -export class CircularGenome extends React.Component +export class CircularGenome extends React.Component { private ref = React.createRef(); - private plasmidCache : Array<{uuid : string,plasmid : Plasmid}>; - public constructor(props : CircularGenomeProps) + private plasmidCache : PlasmidCacheMgr = new PlasmidCacheMgr(); + public constructor(props: CircularGenomeProps) { super(props); @@ -31,201 +29,180 @@ export class CircularGenome extends React.Component + public async updateCanvas() { - let scope = {genome : this.props.figure}; - if(target == this.props.figure.uuid) + for (let i = 0; i != this.props.figure.visibleLayers.length; ++i) { - console.log("Loading base figure"); - console.log(this.props.figure.name); - - let plasmid : Plasmid = new Plasmid(); - plasmid.$scope = scope; - - let nodes : Array = await loadFromString( - assembleCompilableTemplates(this.props.figure, - buildBaseFigureTemplate(this.props.figure) - ) - ); - - for(let i = 0; i != nodes.length; ++i) + let layer = this.props.figure.visibleLayers[i]; + if (this.ref.current) { - if(nodes[i].name == "div") + let canvasArr = this.ref.current.getElementsByTagName("canvas"); + + let canvas = canvasArr[i]; + + let cachedPlasmid = this.plasmidCache.findPlasmidInCache(layer); + + let layerType: CoverageTrackLayer | SNPTrackLayer | undefined; + + layerType = this.props.figure.renderedSNPTracks.find((x) => x.uuid == layer); + + if (!layerType) { - for(let k = 0; k != nodes[i].children.length; ++k) - { - if(nodes[i].children[k].name == "plasmid") - { - plasmid.fromNode(nodes[i].children[k]); - return { - uuid : this.props.figure.uuid, - plasmid : plasmid - }; - } - } + layerType = this.props.figure.renderedCoverageTracks.find((x) => x.uuid == layer); } - } - } - else - { - let coverageTrack = this.props.figure.renderedCoverageTracks.find((x) => x.uuid == target); - if(coverageTrack) - { - let plasmid : Plasmid = new Plasmid(); - plasmid.$scope = scope; + let hasScopeChanged = false; + let oldScope: MapScope | undefined; - let nodes : Array = await loadFromString(assembleCompilableCoverageTrack(this.props.figure,coverageTrack)); - - for(let i = 0; i != nodes.length; ++i) + if (layerType && cachedPlasmid) { - if(nodes[i].name == "div") + oldScope = JSON.parse(cachedPlasmid.oldStrScope); + if (oldScope && oldScope.genome) { - for(let k = 0; k != nodes[i].children.length; ++k) + switch (layerType.type) { - if(nodes[i].children[k].name == "plasmid") + case "coverageTrackLayer": + + console.log(`${oldScope.genome!.radius} ${this.props.figure.radius}`); + if (oldScope.genome.radius != this.props.figure.radius) { - plasmid.fromNode(nodes[i].children[k]); - this.plasmidCache = [ - ...this.plasmidCache, - { - uuid : target, - plasmid : plasmid - } - ]; - - return {uuid:target,plasmid:plasmid}; + hasScopeChanged = true; + break; } + break; } - } - } - } - } - return undefined; - } + if (oldScope.genome.visibleLayers.length != this.props.figure.visibleLayers.length) + { + console.log("visible layers changed"); + hasScopeChanged = true; + } + } - public async updateCanvas() - { - for (let i = 0; i != this.props.figure.visibleLayers.length; ++i) - { - let layer = this.props.figure.visibleLayers[i]; - if (this.ref.current) - { - let canvasArr = this.ref.current.getElementsByTagName("canvas"); - while (canvasArr.length < this.props.figure.visibleLayers.length) - { - this.ref.current.appendChild(document.createElement("canvas")); } - - canvasArr = this.ref.current.getElementsByTagName("canvas"); - - let canvas = canvasArr[i]; - - let plasmid = this.plasmidCache.find((x) => - { - if (x.uuid == layer) - return true; - return false; - }); - - if (!plasmid) + else { - plasmid = await this.loadPlasmid(layer); + hasScopeChanged = true; } - if (plasmid && canvas) + if (hasScopeChanged || !cachedPlasmid) { - let ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); + cachedPlasmid = await this.plasmidCache.loadPlasmidCacheEntry(layer, this.props.figure); - if (ctx) + if (cachedPlasmid && cachedPlasmid.plasmid && canvas) { - canvas.style.position = "absolute"; - canvas.setAttribute("width", `${this.props.width}`); - canvas.setAttribute("height", `${this.props.height}`); - canvas.style.left = `${this.props.x}px`; - canvas.style.top = `${this.props.y}px`; + let ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); + + if (ctx) + { + canvas.style.position = "absolute"; + canvas.setAttribute("width", `${this.props.width}`); + canvas.setAttribute("height", `${this.props.height}`); + canvas.style.left = `${this.props.x}px`; + canvas.style.top = `${this.props.y}px`; - ctx.clearRect(0, 0, this.props.width, this.props.height); + ctx.clearRect(0, 0, this.props.width, this.props.height); - let scope = {genome: this.props.figure}; - plasmid.plasmid.$scope = scope; + let scope = {genome: this.props.figure}; + cachedPlasmid.plasmid.$scope = scope; - await renderSVGToCanvas(plasmid.plasmid.renderStart() + plasmid.plasmid.renderEnd(), ctx); + this.plasmidCache.setOldScope(cachedPlasmid.uuid,this.props.figure); + + await renderSVGToCanvas(cachedPlasmid.plasmid.renderStart() + cachedPlasmid.plasmid.renderEnd(), ctx); + console.log(`Drew ${layer}`); + } } } } } } - public componentDidMount() + public componentDidMount() { this.updateCanvas(); } - public shouldComponentUpdate(prevProps : Readonly,prevState : Readonly) : boolean + public shouldComponentUpdate(prevProps: Readonly, prevState: Readonly): boolean { return true; } - public async componentDidUpdate(prevProps : Readonly,prevState : Readonly) + public async componentDidUpdate(prevProps: Readonly, prevState: Readonly) { let shouldUpdateCanvasDueToResize = false; - - if(this.ref.current) + + this.plasmidCache.prunePlasmidCache(this.props.figure); + + if (this.ref.current) { let canvasArr = this.ref.current.getElementsByTagName("canvas"); - for(let i = 0; i != canvasArr.length; ++i) + while (canvasArr.length < this.props.figure.visibleLayers.length) + { + this.ref.current.appendChild(document.createElement("canvas")); + } + + while (canvasArr.length > this.props.figure.visibleLayers.length) + { + if (this.ref.current.lastChild) + { + this.ref.current.lastChild.remove(); + } + canvasArr = this.ref.current.getElementsByTagName("canvas"); + } + + canvasArr = this.ref.current.getElementsByTagName("canvas"); + + for (let i = 0; i != canvasArr.length; ++i) { let canvas = canvasArr[i]; canvas.style.position = "absolute"; - if(prevProps.width != this.props.width) + if (prevProps.width != this.props.width || canvas.width != this.props.width) { shouldUpdateCanvasDueToResize = true; - canvas.setAttribute("width",`${this.props.width}`); + canvas.setAttribute("width", `${this.props.width}`); } - if(prevProps.height != this.props.height) + if (prevProps.height != this.props.height || canvas.height != this.props.height) { shouldUpdateCanvasDueToResize = true; - canvas.setAttribute("height",`${this.props.height}`); + canvas.setAttribute("height", `${this.props.height}`); } - if(prevProps.x != this.props.x) + if (prevProps.x != this.props.x || canvas.style.left != `${this.props.x}px`) canvas.style.left = `${this.props.x}px`; - if(prevProps.y != this.props.y) + if (prevProps.y != this.props.y || canvas.style.top != `${this.props.y}px`) canvas.style.top = `${this.props.y}px`; } + if (this.props.shouldUpateCanvas) + { + shouldUpdateCanvasDueToResize = true; + } + + if (shouldUpdateCanvasDueToResize || this.props.shouldUpateCanvas) + { + await this.updateCanvas(); + } } - if(this.props.shouldUpateCanvas) - shouldUpdateCanvasDueToResize = true; - if(shouldUpdateCanvasDueToResize || this.props.shouldUpateCanvas) - { - await this.updateCanvas(); - } - + } - public render() : JSX.Element + public render(): JSX.Element { return ( -
+