diff --git a/src/ui/CanvasTextureRenderer.ts b/src/ui/CanvasTextureRenderer.ts index e5c9761a1..cd791e9b1 100644 --- a/src/ui/CanvasTextureRenderer.ts +++ b/src/ui/CanvasTextureRenderer.ts @@ -31,6 +31,7 @@ export interface ITextureRenderer { export default class CanvasTextureRenderer implements ITextureRenderer { static readonly MAX_CANVAS_SIZE = 32767; + static readonly AGGRIGATED_ROW_HEIGHT = 45; readonly node: HTMLElement; readonly canvas: any; @@ -45,7 +46,7 @@ export default class CanvasTextureRenderer implements ITextureRenderer { private currentRankingWidths: number[] = []; private engineRenderer: EngineRenderer; private engineRankings: EngineRanking[][] = []; - private skipUpdateEvents: number = 0; + //private skipUpdateEvents: number = 0; private alreadyExpanded: boolean = false; private expandLaterRows: any[] = []; private readonly options: Readonly; @@ -80,7 +81,10 @@ export default class CanvasTextureRenderer implements ITextureRenderer { update(rankings: EngineRanking[] = this.currentRankings, localData: IDataRow[][] = this.currentLocalData) { this.detailParts = []; - this.currentLocalData = localData; + rankings.forEach((r, i) => { + const rankingIndex = this.currentRankings.findIndex((v) => v === r); + this.currentLocalData[rankingIndex] = localData[i]; + }); this.currentNodeHeight = this.node.offsetHeight; let totalWidth = 0; rankings.forEach((r, i) => { @@ -99,18 +103,20 @@ export default class CanvasTextureRenderer implements ITextureRenderer { private renderColumns (rankings: EngineRanking[], localData: IDataRow[][]) { rankings.forEach((r, i) => { + let notAggregatedCount = localData[i].length; let gIndex = 0; const aggregatedParts = []; r.ranking.getGroups().forEach((g) => { if (this.engineRenderer.ctx.provider.isAggregated(r.ranking, g)) { aggregatedParts.push([gIndex, gIndex + g.order.length - 1]); + notAggregatedCount -= g.order.length; } gIndex += g.order.length; }); + this.currentNodeHeight -= CanvasTextureRenderer.AGGRIGATED_ROW_HEIGHT * aggregatedParts.length; const rankingIndex = this.currentRankings.findIndex((v) => v === r); this.engineRankings[rankingIndex] = []; - //TODO: combine this.detailParts = []; let startIndex = -1; for (let j = 0; j < localData[i].length; j++) { @@ -131,7 +137,7 @@ export default class CanvasTextureRenderer implements ITextureRenderer { aggregatedParts.forEach((g: any) => { for (let j = 0; j < this.detailParts.length; j++) { if (g[0] <= this.detailParts[j][0]) { - if (g[1] <= this.detailParts[j][0]) { + if (g[1] < this.detailParts[j][0]) { this.detailParts.splice(j, 0, g); aggregateIndices.push(j); return; @@ -142,16 +148,18 @@ export default class CanvasTextureRenderer implements ITextureRenderer { return; } this.detailParts.splice(j, 1); + j--; + continue; } if (g[0] > this.detailParts[j][1]) { - return; + continue; } if (g[1] < this.detailParts[j][1]) { - this.detailParts.splice(j, 1, [this.detailParts[j][0], g[0] - 1], g, [g[1] + 1, this.detailParts[j][1]]); - aggregateIndices.push(j + 1); - return; - } - this.detailParts.splice(j, 1, [this.detailParts[j][0], g[0] - 1]); + this.detailParts.splice(j, 1, [this.detailParts[j][0], g[0] - 1], g, [g[1] + 1, this.detailParts[j][1]]); + aggregateIndices.push(j + 1); + return; + } + this.detailParts.splice(j, 1, [this.detailParts[j][0], g[0] - 1]); } this.detailParts.push(g); aggregateIndices.push(this.detailParts.length - 1); @@ -208,7 +216,7 @@ export default class CanvasTextureRenderer implements ITextureRenderer { aggregateOffset += newOffset; if (!aggregated) { - const height = data.length / localData[i].length * this.currentNodeHeight; + const height = data.length / notAggregatedCount * this.currentNodeHeight; if (height >= 1) { //only render parts larger than 1px const textureDiv = this.node.ownerDocument.createElement('div'); textureDiv.style.height = `${height}px`; @@ -242,14 +250,14 @@ export default class CanvasTextureRenderer implements ITextureRenderer { const engineRanking = table.pushTable((header, body, tableId, style) => new EngineRanking(r.ranking, header, body, tableId, style, this.engineRenderer.ctx, { animation: this.options.animated, customRowUpdate: this.options.customRowUpdate || (() => undefined), - levelOfDetail: this.options.levelOfDetail || (() => 'high'), + levelOfDetail: this.options.levelOfDetail || (() => 'high'),// flags: this.options.flags - })); + }, true)); this.engineRenderer.render(engineRanking, data); this.engineRankings[rankingIndex].push(engineRanking); - engineRanking.on(EngineRanking.EVENT_UPDATE_DATA, () => this.handleUpdateEvent(r)); - this.skipUpdateEvents++; + //engineRanking.on(EngineRanking.EVENT_UPDATE_DATA, () => this.handleUpdateEvent(r)); + //this.skipUpdateEvents++; }; if (this.alreadyExpanded || aggregated) { expandLater(); @@ -480,6 +488,7 @@ export default class CanvasTextureRenderer implements ITextureRenderer { addRanking(ranking: EngineRanking) { this.currentRankings.push(ranking); + this.currentLocalData.push([]); const rankingDiv = this.node.ownerDocument.createElement('div'); rankingDiv.classList.add('rankingContainer'); rankingDiv.setAttribute('data-ranking', `${this.currentRankings.length-1}`); @@ -496,6 +505,7 @@ export default class CanvasTextureRenderer implements ITextureRenderer { } this.currentRankings.splice(index, 1); this.engineRankings.splice(index, 1); + this.currentLocalData.splice(index, 1); d3.select(this.node).select(`[data-ranking="${index}"]`).remove(); } @@ -513,33 +523,10 @@ export default class CanvasTextureRenderer implements ITextureRenderer { s2d() { this.engineRenderer.ctx.provider.setDetail(this.engineRenderer.ctx.provider.getSelection()); - //this.detailParts = []; - //let startIndex = -1; - //for (let i = 0; i < this.currentLocalData[0].length; i++) { - // if (this.engineRenderer.ctx.provider.isSelected(this.currentLocalData[0][i].i)) { - // if (startIndex === -1) { - // startIndex = i; - // } - // } else if (startIndex !== -1) { - // this.detailParts.push([startIndex, i-1]); - // startIndex = -1; - // } - //} - //if (startIndex !== -1) { - // this.detailParts.push([startIndex, this.currentLocalData[0].length-1]); - //} - //this.renderColumns(this.currentRankings, this.currentLocalData); } d2s() { this.engineRenderer.ctx.provider.setSelection(this.engineRenderer.ctx.provider.getDetail()); - //d3.select(this.node).selectAll('.engineRendererContainer').nodes().forEach((v: any, i: number) => { - // const first = d3.select(v).select('.lu-row:first-child').node(); - // const last = d3.select(v).select('.lu-row:last-child').node(); - // if (typeof first !== 'undefined' && typeof last !== 'undefined') { - // this.engineRankings[i].selection.select(i !== 0, first, last); - // } - //}); } drawSelection() { @@ -564,12 +551,4 @@ export default class CanvasTextureRenderer implements ITextureRenderer { ctx.save(); }); } - - private handleUpdateEvent (r: EngineRanking) { - if (this.skipUpdateEvents > 0) { - this.skipUpdateEvents--; - } else { - this.engineRenderer.update([r]); - } - } } diff --git a/src/ui/EngineRanking.ts b/src/ui/EngineRanking.ts index ae01d1753..037032356 100644 --- a/src/ui/EngineRanking.ts +++ b/src/ui/EngineRanking.ts @@ -156,7 +156,7 @@ export default class EngineRanking extends ACellTableSection imple } }; - constructor(public readonly ranking: Ranking, header: HTMLElement, body: HTMLElement, tableId: string, style: GridStyleManager, private readonly ctx: IEngineRankingContext, roptions: Partial = {}) { + constructor(public readonly ranking: Ranking, header: HTMLElement, body: HTMLElement, tableId: string, style: GridStyleManager, private readonly ctx: IEngineRankingContext, roptions: Partial = {}, readonly noEvents: boolean = false) { super(header, body, tableId, style, {mixins: [PrefetchMixin], batchSize: 10}); Object.assign(this.roptions, roptions); body.classList.add('lu-row-body'); @@ -179,43 +179,45 @@ export default class EngineRanking extends ACellTableSection imple this.delayedUpdateAll = debounce(() => this.updateAll(), 50); this.delayedUpdateColumnWidths = debounce(() => this.updateColumnWidths(), 50); - ranking.on(`${Ranking.EVENT_ADD_COLUMN}.hist`, (col: Column, index: number) => { - this.columns.splice(index, 0, this.createCol(col, index)); - this.reindex(); - this.updateHist(col); - this.delayedUpdateAll(); - }); - ranking.on(`${Ranking.EVENT_REMOVE_COLUMN}.body`, (col: Column, index: number) => { - EngineRanking.disableListener(col); - this.columns.splice(index, 1); - this.reindex(); - this.delayedUpdateAll(); - }); - ranking.on(`${Ranking.EVENT_MOVE_COLUMN}.body`, (col: Column, index: number, old: number) => { - //delete first - const c = this.columns.splice(old, 1)[0]; - console.assert(c.c === col); - // adapt target index based on previous index, i.e shift by one - this.columns.splice(old < index ? index - 1 : index, 0, c); - this.reindex(); - this.delayedUpdateAll(); - }); - ranking.on(`${Ranking.EVENT_COLUMN_VISIBILITY_CHANGED}.body`, (col: Column, _oldValue: boolean, newValue: boolean) => { - if (newValue) { - // become visible - const index = ranking.children.indexOf(col); + if (!noEvents) { + ranking.on(`${Ranking.EVENT_ADD_COLUMN}.hist`, (col: Column, index: number) => { this.columns.splice(index, 0, this.createCol(col, index)); + this.reindex(); this.updateHist(col); - } else { - // hide - const index = this.columns.findIndex((d) => d.c === col); + this.delayedUpdateAll(); + }); + ranking.on(`${Ranking.EVENT_REMOVE_COLUMN}.body`, (col: Column, index: number) => { EngineRanking.disableListener(col); this.columns.splice(index, 1); - } - this.reindex(); - this.delayedUpdateAll(); - }); - ranking.on(`${Ranking.EVENT_ORDER_CHANGED}.body`, this.delayedUpdate); + this.reindex(); + this.delayedUpdateAll(); + }); + ranking.on(`${Ranking.EVENT_MOVE_COLUMN}.body`, (col: Column, index: number, old: number) => { + //delete first + const c = this.columns.splice(old, 1)[0]; + console.assert(c.c === col); + // adapt target index based on previous index, i.e shift by one + this.columns.splice(old < index ? index - 1 : index, 0, c); + this.reindex(); + this.delayedUpdateAll(); + }); + ranking.on(`${Ranking.EVENT_COLUMN_VISIBILITY_CHANGED}.body`, (col: Column, _oldValue: boolean, newValue: boolean) => { + if (newValue) { + // become visible + const index = ranking.children.indexOf(col); + this.columns.splice(index, 0, this.createCol(col, index)); + this.updateHist(col); + } else { + // hide + const index = this.columns.findIndex((d) => d.c === col); + EngineRanking.disableListener(col); + this.columns.splice(index, 1); + } + this.reindex(); + this.delayedUpdateAll(); + }); + ranking.on(`${Ranking.EVENT_ORDER_CHANGED}.body`, this.delayedUpdate); + } this.selection = new SelectionManager(this.ctx, body); this.selection.on(SelectionManager.EVENT_SELECT_RANGE, (from: number, to: number, additional: boolean) => { diff --git a/src/ui/taggle/Taggle.ts b/src/ui/taggle/Taggle.ts index accc83421..7481d0834 100644 --- a/src/ui/taggle/Taggle.ts +++ b/src/ui/taggle/Taggle.ts @@ -6,6 +6,7 @@ import {ALineUp} from '../ALineUp'; import SidePanel from '../panel/SidePanel'; import spaceFillingRule from './spaceFillingRule'; import TaggleRenderer from './TaggleRenderer'; +import LocalDataProvider from '../../provider/LocalDataProvider'; export {ITaggleOptions} from '../../interfaces'; @@ -61,14 +62,24 @@ export default class Taggle extends ALineUp { this.spaceFilling = this.node.querySelector('.lu-rule-button-chooser')!; const ruleInput = this.spaceFilling.querySelector('input.spaceFilling'); ruleInput.onchange = () => { + let useTextureRenderer = true; + if (data instanceof LocalDataProvider) { + const ldp = data; + if (this.node.offsetHeight > ldp.data.length) { + useTextureRenderer = false; + } + } const selected = this.spaceFilling!.classList.toggle('chosen'); - //self.setTimeout(() => this.renderer.switchRule(selected ? spaceFilling : null)); - this.renderer!.useTextureRenderer(selected); - if (selected) { - expandButton.style.display = ''; - } else { - expandButton.style.display = 'none'; + if (useTextureRenderer) { + this.renderer!.useTextureRenderer(selected); + if (selected) { + expandButton.style.display = ''; + } else { + expandButton.style.display = 'none'; + } + return; } + self.setTimeout(() => this.renderer!.switchRule(selected ? spaceFilling : null)); }; if (this.options.overviewMode) { ruleInput.checked = true;