Skip to content

Commit

Permalink
implementation of a canvas texture renderer, for Caleydo/lineup#35 and
Browse files Browse the repository at this point in the history
  • Loading branch information
domdir committed Jun 3, 2018
1 parent 2bdba45 commit 7af5bd6
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 27 deletions.
1 change: 1 addition & 0 deletions src/styles/engine/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@import './header';
@import './slope_graph';
@import './selection';
@import './texture_renderer';

$engine_assets: '~lineupengine/src/assets';

Expand Down
9 changes: 9 additions & 0 deletions src/styles/engine/_texture_renderer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import '../vars';

##{$lu-css_prefix}-texture-container{
display: flex;
overflow: hidden;
img{
margin-right: 5px;
}
}
86 changes: 86 additions & 0 deletions src/ui/engine/CanvasTextureRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import EngineRanking from './EngineRanking';
import {IDataRow} from '../../provider/ADataProvider';
import NumbersColumn from '../../model/NumbersColumn';

export interface ITextureRenderer{
update(rankings: EngineRanking[], localData: IDataRow[][]): void;
destroy(): void;
show(): void;
hide(): void;
}

export default class CanvasTextureRenderer implements ITextureRenderer {

readonly node: HTMLElement;
readonly canvas: any;

constructor(parent: Element) {
this.node = parent.ownerDocument.createElement('main');
this.node.id = 'lu-texture-container';
parent.appendChild(this.node);
this.canvas = parent.ownerDocument.createElement('canvas');
}

update(rankings: EngineRanking[], localData: IDataRow[][]) {
this.node.innerHTML = ''; //remove all children

rankings.forEach((r, i) => {
const grouped = r.groupData(localData[i]);

r.ranking.flatColumns.forEach((column) => {
let newElement = null;
if (column instanceof NumbersColumn) {
const col = <NumbersColumn>column;
newElement = this.generateImage(grouped.map((value) => {
return (<any>value).v[(<any>col.desc).column];
}), col.getRawColorScale())
} else {
newElement = this.node.ownerDocument.createElement('img');
}
newElement.style.width = column.getWidth() + 'px';
this.node.appendChild(newElement);
});
});
}

private generateImage(data: any[][], colorScale: any){
let height = data.length;
let width = 0;
if(height > 0){
width = data[0].length;
}

this.canvas.setAttribute('height', '' + height);
this.canvas.setAttribute('width', '' + width);

const ctx = <CanvasRenderingContext2D>this.canvas.getContext('2d');

data.forEach((row, y) => {
row.forEach((value, x) => {
ctx.fillStyle = colorScale(value);
ctx.fillRect(x, y, 1, 1);
});
});

ctx.save();

const image = this.node.ownerDocument.createElement('img');
image.src = this.canvas.toDataURL();

return image;
}


destroy() {
this.node.remove();
}

show() {
this.node.classList.remove('hidden');
}

hide() {
this.node.classList.add('hidden');
}
}

75 changes: 50 additions & 25 deletions src/ui/engine/EngineRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import EngineRanking, {IEngineRankingContext} from './EngineRanking';
import {IFilterDialog} from '../../dialogs/AFilterDialog';
import ICellRendererFactory from '../../renderer/ICellRendererFactory';
import {IImposer} from '../../renderer/IRenderContext';
import {default as CanvasTextureRenderer, ITextureRenderer} from './CanvasTextureRenderer';
import * as d3 from 'd3';

export interface IEngineRendererOptions {
header: Partial<{
Expand Down Expand Up @@ -67,6 +69,9 @@ export default class EngineRenderer extends AEventDispatcher implements ILineUpR
private readonly updateAbles: ((ctx: IRankingHeaderContext) => void)[] = [];
private zoomFactor = 1;

public useTextureRenderer: boolean = false;
private textureRenderer: ITextureRenderer;

constructor(protected data: DataProvider, parent: Element, options: Readonly<IEngineRendererOptions>) {
super();
this.options = options;
Expand Down Expand Up @@ -104,6 +109,8 @@ export default class EngineRenderer extends AEventDispatcher implements ILineUpR
this.node.id = this.options.idPrefix;
this.table = new MultiTableRowRenderer(this.node, `#${options.idPrefix}`);

this.textureRenderer = new CanvasTextureRenderer(this.node);

this.initProvider(data);
}

Expand Down Expand Up @@ -260,40 +267,49 @@ export default class EngineRenderer extends AEventDispatcher implements ILineUpR
this.updateHist();
}

const round2 = (v: number) => round(v, 2);
if (this.useTextureRenderer) {
this.hide();
this.textureRenderer.show();
this.textureRenderer.update(rankings, localData);

} else {
this.textureRenderer.hide();
this.show();

const heightsFor = (ranking: Ranking, data: (IGroupItem|IGroupData)[]) => {
if (this.options.body.dynamicHeight) {
const impl = this.options.body.dynamicHeight(data, ranking);
const round2 = (v: number) => round(v, 2);

const heightsFor = (ranking: Ranking, data: (IGroupItem|IGroupData)[]) => {
if (this.options.body.dynamicHeight) {
const impl = this.options.body.dynamicHeight(data, ranking);
return {
defaultHeight: round2(this.zoomFactor * impl.defaultHeight),
height: (d: IGroupItem|IGroupData) => round2(this.zoomFactor * impl.height(d))
};
}
const item = round2(this.zoomFactor * this.options.body.rowHeight!);
const group = round2(this.zoomFactor * this.options.body.groupHeight!);
return {
defaultHeight: round2(this.zoomFactor * impl.defaultHeight),
height: (d: IGroupItem|IGroupData) => round2(this.zoomFactor * impl.height(d))
defaultHeight: item,
height: (d: IGroupItem|IGroupData) => isGroup(d) ? group : item
};
}
const item = round2(this.zoomFactor * this.options.body.rowHeight!);
const group = round2(this.zoomFactor * this.options.body.groupHeight!);
return {
defaultHeight: item,
height: (d: IGroupItem|IGroupData) => isGroup(d) ? group : item
};
};
const groupPadding = round2(this.zoomFactor * this.options.body.groupPadding!);
const rowPadding = round2(this.zoomFactor * this.options.body.rowPadding!);
const groupPadding = round2(this.zoomFactor * this.options.body.groupPadding!);
const rowPadding = round2(this.zoomFactor * this.options.body.rowPadding!);

rankings.forEach((r, i) => {
const grouped = r.groupData(localData[i]);
rankings.forEach((r, i) => {
const grouped = r.groupData(localData[i]);

const {height, defaultHeight} = heightsFor(r.ranking, grouped);
const {height, defaultHeight} = heightsFor(r.ranking, grouped);

const rowContext = nonUniformContext(grouped.map(height), defaultHeight, (index) => {
if (index >= 0 && grouped[index] && (isGroup(grouped[index]) || (<IGroupItem>grouped[index]).meta === 'last' || (<IGroupItem>grouped[index]).meta === 'first last')) {
return groupPadding + rowPadding;
}
return rowPadding;
const rowContext = nonUniformContext(grouped.map(height), defaultHeight, (index) => {
if (index >= 0 && grouped[index] && (isGroup(grouped[index]) || (<IGroupItem>grouped[index]).meta === 'last' || (<IGroupItem>grouped[index]).meta === 'first last')) {
return groupPadding + rowPadding;
}
return rowPadding;
});
r.render(grouped, rowContext);
});
r.render(grouped, rowContext);
});
}

this.updateSlopeGraphs(rankings);

Expand Down Expand Up @@ -324,9 +340,18 @@ export default class EngineRenderer extends AEventDispatcher implements ILineUpR
destroy() {
this.takeDownProvider();
this.table.destroy();
this.textureRenderer.destroy();
this.node.remove();
}

show() {
d3.select(this.node).select('main').classed('hidden', false);
}

hide() {
d3.select(this.node).select('main').classed('hidden', true);
}

scrollIntoView(_index: number) {
// TODO
}
Expand Down
5 changes: 3 additions & 2 deletions src/ui/taggle/Taggle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {IEngineRendererOptions} from '../engine/EngineRenderer';
import {regular, spacefilling} from './LineUpRuleSet';
//import {regular, spacefilling} from './LineUpRuleSet';
import {RENDERER_EVENT_HOVER_CHANGED} from '../interfaces';
import SidePanel from '../panel/SidePanel';
import DataProvider from '../../provider/ADataProvider';
Expand Down Expand Up @@ -42,7 +42,8 @@ export default class Taggle extends AEventDispatcher {
this.spaceFilling = <HTMLElement>this.node.querySelector('.lu-rule-button-chooser')!;
this.spaceFilling.addEventListener('click', () => {
const selected = this.spaceFilling.classList.toggle('chosen');
this.renderer.switchRule(selected ? spacefilling : regular);
//this.renderer.switchRule(selected ? spacefilling : regular);
this.renderer.useTextureRenderer(selected);
});
}

Expand Down
5 changes: 5 additions & 0 deletions src/ui/taggle/TaggleRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export default class TaggleRenderer extends AEventDispatcher {
this.update();
}

useTextureRenderer(use: boolean){
this.renderer.useTextureRenderer = use;
this.update();
}

destroy() {
this.renderer.destroy();
window.removeEventListener('resize', this.resizeListener);
Expand Down
44 changes: 44 additions & 0 deletions src/ui/taggle/TextureRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import CanvasTextureRenderer from '../engine/CanvasTextureRenderer';
import DataProvider from '../../provider/ADataProvider';
import {AEventDispatcher} from '../../utils';


export default class TextureRenderer extends AEventDispatcher {

private readonly renderer: CanvasTextureRenderer;

constructor(parent: HTMLElement, public data: DataProvider) {
super();

this.renderer = new CanvasTextureRenderer(data, parent);
}

get ctx() {
return this.renderer.ctx;
}

protected createEventList() {
return super.createEventList(); /*.concat([TaggleRenderer.EVENT_HOVER_CHANGED])*/
}

destroy() {
this.renderer.destroy();
}

update() {
this.renderer.update();
}

changeDataStorage(data: DataProvider) {
this.renderer.changeDataStorage(data);
this.update();
}

show() {
this.renderer.show();
}

hide() {
this.renderer.hide();
}
}

0 comments on commit 7af5bd6

Please sign in to comment.