Skip to content

Commit

Permalink
Prototype of timegraph using timeline-chart
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Delisle <simon.delisle@ericsson.com>
  • Loading branch information
delislesim committed Oct 8, 2019
1 parent 8861810 commit 7cc6027
Show file tree
Hide file tree
Showing 8 changed files with 607 additions and 6 deletions.
1 change: 1 addition & 0 deletions viewer-prototype/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"react-chartjs-2": "^2.7.2",
"ag-grid-community": "^19.0.0",
"ag-grid-react": "^19.0.0",
"timeline-chart": "next",
"react-grid-layout": "^0.16.6",
"react-virtualized": "^9.21.0",
"tsp-typescript-client": "https://github.com/theia-ide/tsp-typescript-client"
Expand Down
22 changes: 22 additions & 0 deletions viewer-prototype/src/browser/style/timegraph.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#timegraph-main {
width: 100%;
display: flex;
}

#main {
border: 1px solid;
margin: 10px 0;
overflow: hidden;
}

canvas {
display: block;
}

.innerContainer {
width: 100%;
}

#main-vscroll {
margin-top: 30px;
}
20 changes: 20 additions & 0 deletions viewer-prototype/src/browser/timegraph-view/test-arrows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// import { TimelineChart } from 'timeline-chart/lib/time-graph-model';

// export const timeGraphArrows: TimelineChart.TimeGraphArrow[] = [
// {
// sourceId: 1,
// destinationId: 2,
// range:{
// start: 1332170682486039800,
// end: 1332170682489988000
// }
// },
// {
// sourceId: 2,
// destinationId: 1,
// range:{
// start: 1332170682497734100,
// end: 1332170682497814000
// }
// }
// ]
339 changes: 339 additions & 0 deletions viewer-prototype/src/browser/timegraph-view/timegraph-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
import * as React from 'react';
import { TimeGraphContainer, TimeGraphContainerOptions } from 'timeline-chart/lib/time-graph-container';
import { TimeGraphUnitController } from 'timeline-chart/lib/time-graph-unit-controller';
import { TimeGraphAxis } from 'timeline-chart/lib/layer/time-graph-axis';
import { TimeGraphAxisCursors } from 'timeline-chart/lib/layer/time-graph-axis-cursors';
import { TimeGraphChartGrid } from 'timeline-chart/lib/layer/time-graph-chart-grid';
import { TimeGraphChart, TimeGraphChartProviders } from 'timeline-chart/lib/layer/time-graph-chart';
import { TimeGraphChartCursors } from 'timeline-chart/lib/layer/time-graph-chart-cursors';
import { TimeGraphChartSelectionRange } from 'timeline-chart/lib/layer/time-graph-chart-selection-range';
import { TimeGraphNavigator } from 'timeline-chart/lib/layer/time-graph-navigator';
import { TimeGraphVerticalScrollbar } from 'timeline-chart/lib/layer/time-graph-vertical-scrollbar';
import { TimeGraphLayer } from 'timeline-chart/lib/layer/time-graph-layer';
import { TimeGraphRowElementStyle, TimeGraphRowElement } from 'timeline-chart/lib/components/time-graph-row-element';
import { TimeGraphRowController } from 'timeline-chart/lib/time-graph-row-controller';
import { TimelineChart } from 'timeline-chart/lib/time-graph-model';
import { TspDataProvider } from './tsp-data-provider';
import { TspClient } from 'tsp-typescript-client/lib/protocol/tsp-client';
import { Trace } from 'tsp-typescript-client/lib/models/trace';
import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper';
import { TimeGraphEntry } from 'tsp-typescript-client/lib/models/timegraph';
import { EntryHeader } from 'tsp-typescript-client/lib/models/entry';

export class TimeGraphView {
protected styleConfig = {
mainWidth: 1000,
mainHeight: 300,
naviBackgroundColor: 0x3f3f3f,
chartBackgroundColor: 0x3f3f3f,
cursorColor: 0x8888ff,
lineColor: 0xbbbbbb
}
protected rowHeight = 15;
protected totalHeight: number = 0;

protected unitController: TimeGraphUnitController;
protected rowController: TimeGraphRowController;
protected dataProvider: TspDataProvider;

protected timeGraphData?: TimelineChart.TimeGraphModel;

protected chartLayer: TimeGraphChart;
// protected arrows: TimeGraphChartArrows;
protected vscrollLayer: TimeGraphVerticalScrollbar;

protected styleMap = new Map<string, TimeGraphRowElementStyle>();

protected horizontalContainer: React.RefObject<HTMLDivElement>;

protected widgetResizeHandlers: (() => void)[] = [];
protected readonly addWidgetResizeHandler = (h: () => void) => {
this.widgetResizeHandlers.push(h);
}

private tspClient: TspClient;

constructor(client: TspClient, protected handler: {
updateHandler: () => void,
selectionHandler: (el?: TimeGraphRowElement) => void,
mouseOverHandler: (el?: TimeGraphRowElement) => void
mouseOutHandler: (el?: TimeGraphRowElement) => void
}) {
this.tspClient = client;
this.dataProvider = new TspDataProvider(client);
this.unitController = new TimeGraphUnitController(0);
this.rowController = new TimeGraphRowController(this.rowHeight, this.totalHeight);

this.unitController.scaleSteps = [1, 2];

const providers: TimeGraphChartProviders = {
dataProvider: async (range: TimelineChart.TimeGraphRange, resolution: number) => {
if (this.unitController) {
const length = range.end - range.start;
const overlap = ((length * 5) - length) / 2;
const start = range.start - overlap > 0 ? range.start - overlap : 0;
const end = range.end + overlap < this.unitController.absoluteRange ? range.end + overlap : this.unitController.absoluteRange;
const newRange: TimelineChart.TimeGraphRange = { start, end };
const newResolution: number = resolution * 0.8;
this.timeGraphData = await this.dataProvider.getData(newRange, newResolution);
if (this.timeGraphData && selectedElement) {
for (const row of this.timeGraphData.rows) {
const selEl = row.states.find(el => !!selectedElement && el.id === selectedElement.id);
if (selEl) {
selEl.selected = true;
break;
}
}
}
return {
rows: this.timeGraphData ? this.timeGraphData.rows : [],
range: newRange,
resolution: newResolution
};
}
return {
rows: [],
range: { start: 0, end: 0 },
resolution: 0
};
},
rowElementStyleProvider: (model: TimelineChart.TimeGraphRowElementModel) => {
const styles: TimeGraphRowElementStyle[] = [
{
color: 0xf19d0b,
height: this.rowHeight * 0.8
}, {
color: 0xf0670a,
height: this.rowHeight * 0.7
}, {
color: 0xef2809,
height: this.rowHeight * 0.6
}
];
let style: TimeGraphRowElementStyle | undefined = styles[0];
const val = model.label;

style = this.styleMap.get(val);
if (!style) {
style = styles[(this.styleMap.size % styles.length)];
this.styleMap.set(val, style);
}
return {
color: style.color,
height: style.height,
borderWidth: model.selected ? 2 : 0,
borderColor: 0xeef20c
};
},
rowStyleProvider: (row: TimelineChart.TimeGraphRowModel) => {
return {
backgroundColor: 0xaaaaff,
backgroundOpacity: row.selected ? 0.2 : 0,
lineColor: row.data && row.data.hasStates ? 0xdddddd : 0xaa4444,
lineThickness: row.data && row.data.hasStates ? 1 : 3
}
}
}

this.horizontalContainer = React.createRef();

this.chartLayer = new TimeGraphChart('timeGraphChart', providers, this.rowController);
let origColor: number | undefined;
this.chartLayer.registerRowElementMouseInteractions({
mouseover: (el: TimeGraphRowElement, ev: PIXI.interaction.InteractionEvent) => {
origColor = el.style.color;
el.style = {
color: 0xceeda3
}
this.handler.mouseOverHandler(el);
},
mouseout: (el: TimeGraphRowElement, ev: PIXI.interaction.InteractionEvent) => {
el.style = {
color: origColor
}
this.handler.mouseOutHandler(el);
}
});
let selectedElement: TimeGraphRowElement | undefined;
this.chartLayer.onSelectedRowElementChanged((model) => {
if (model) {
const el = this.chartLayer.getElementById(model.id);
if (el) {
selectedElement = el;
}
} else {
selectedElement = undefined;
}
this.handler.selectionHandler(selectedElement);
});
this.vscrollLayer = new TimeGraphVerticalScrollbar('timeGraphVerticalScrollbar', this.rowController);
this.initialize();
}

protected async initialize() {
const traces: Trace[] = await this.tspClient.fetchTraces();
if (traces && traces.length) {
const resourcesTreeParameters = QueryHelper.timeQuery([0, 1]);
const treeResponse = await this.tspClient.fetchTimeGraphTree<TimeGraphEntry, EntryHeader>(
traces[0].UUID,
'org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ResourcesStatusDataProvider',
resourcesTreeParameters);
const nbEntries = treeResponse.model ? treeResponse.model.entries.length : 1;
const traceStart = traces[0].start;
const traceEnd = traces[0].end;
const traceRange = traceEnd - traceStart;
this.unitController.absoluteRange = traceRange;
this.unitController.numberTranslator = (theNumber: number) => {
return (theNumber - Math.trunc(theNumber)) !== 0 ? '' : theNumber.toString();
};
this.unitController.viewRange = {
start: traceStart,
end: traceEnd// this.unitController.absoluteRange
};
this.totalHeight = nbEntries * this.rowHeight;
this.rowController.totalHeight = this.totalHeight;
}
window.onresize = () => this.onWidgetResize();
this.onWidgetResize();
}

onWidgetResize() {
this.styleConfig.mainWidth = this.horizontalContainer.current ? this.horizontalContainer.current.clientWidth : 1000;
this.handler.updateHandler();
this.widgetResizeHandlers.forEach(h => h());
}

renderTimeGraphChart(): React.ReactNode {
return <React.Fragment>
{this.renderMainGraphContent()}
<div id='main-vscroll'>
{this.getVerticalScrollbar()}
</div>
</React.Fragment >
}
protected renderMainGraphContent() {
return <div id='main-timegraph-content' ref={this.horizontalContainer}>
{this.getAxisContainer()}
{this.getChartContainer()}
{this.getNaviContainer()}
</div>
}

protected getAxisContainer() {
const axisLayer = this.getAxisLayer();
const axisCursorLayer = this.getAxisCursors();
return <ReactTimeGraphContainer
id='timegraph-axis'
options={{
id: 'timegraph-axis',
height: 30,
width: this.styleConfig.mainWidth,
backgroundColor: 0xFFFFFF,
classNames: 'horizontal-canvas'
}}
onWidgetResize={this.addWidgetResizeHandler}
unitController={this.unitController}
layer={[axisLayer, axisCursorLayer]}>
</ReactTimeGraphContainer>;
}

protected getAxisLayer() {
const timeAxisLayer = new TimeGraphAxis('timeGraphAxis', {
color: this.styleConfig.naviBackgroundColor,
lineColor: this.styleConfig.lineColor
});
return timeAxisLayer;
}

protected getAxisCursors() {
return new TimeGraphAxisCursors('timeGraphAxisCursors', { color: this.styleConfig.cursorColor });
}

protected getChartContainer() {
const grid = new TimeGraphChartGrid('timeGraphGrid', this.rowHeight, this.styleConfig.lineColor);

const cursors = new TimeGraphChartCursors('chart-cursors', this.chartLayer, this.rowController, { color: this.styleConfig.cursorColor });
const selectionRange = new TimeGraphChartSelectionRange('chart-selection-range', { color: this.styleConfig.cursorColor });

return <ReactTimeGraphContainer
options={
{
id: 'timegraph-chart',
height: this.styleConfig.mainHeight,
width: this.styleConfig.mainWidth,
backgroundColor: this.styleConfig.chartBackgroundColor,
classNames: 'horizontal-canvas'
}
}
onWidgetResize={this.addWidgetResizeHandler}
unitController={this.unitController}
id='timegraph-chart'
layer={[
grid, this.chartLayer, selectionRange, cursors
]}
>
</ReactTimeGraphContainer>;
}

protected getNaviContainer() {
const navi = new TimeGraphNavigator('timeGraphNavigator');
return <ReactTimeGraphContainer
id='navi'
options={{
width: this.styleConfig.mainWidth,
height: 10,
id: 'navi',
backgroundColor: this.styleConfig.naviBackgroundColor,
classNames: 'horizontal-canvas'
}}
onWidgetResize={this.addWidgetResizeHandler}
unitController={this.unitController}
layer={[navi]}
></ReactTimeGraphContainer>
}

protected getVerticalScrollbar() {
return <ReactTimeGraphContainer
id='vscroll'
options={{
id: 'vscroll',
width: 10,
height: this.styleConfig.mainHeight,
backgroundColor: this.styleConfig.naviBackgroundColor
}}
onWidgetResize={this.addWidgetResizeHandler}
unitController={this.unitController}
layer={[this.vscrollLayer]}
></ReactTimeGraphContainer>;
}
}

export namespace ReactTimeGraphContainer {
export interface Props {
id: string,
options: TimeGraphContainerOptions,
unitController: TimeGraphUnitController,
layer: TimeGraphLayer[],
onWidgetResize: (handler: () => void) => void
}
}

export class ReactTimeGraphContainer extends React.Component<ReactTimeGraphContainer.Props> {
protected ref: HTMLCanvasElement | undefined;
protected container?: TimeGraphContainer;

componentDidMount() {
this.container = new TimeGraphContainer(this.props.options, this.props.unitController, this.ref);
this.props.layer.forEach(l => {
this.container && this.container.addLayer(l);
});

this.props.onWidgetResize(() => {
this.container && this.container.reInitCanvasSize(this.props.options.width);
})
}

render() {
return <canvas ref={ref => this.ref = ref || undefined} onWheel={e => e.preventDefault()}></canvas>
}
}
Loading

0 comments on commit 7cc6027

Please sign in to comment.