Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Tooltip #170

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 59 additions & 61 deletions viewer-prototype/src/browser/style/output-components-style.css
Original file line number Diff line number Diff line change
@@ -1,96 +1,94 @@
/* Output component styling */
/* Main container*/
.output-container {
display: flex;
color: var(--theia-ui-font-color0)
display: flex;
color: var(--theia-ui-font-color0)
}

.widget-handle {
background-color: var(--theia-layout-color4);
display: grid;
grid-template-rows: 25px 1fr;
background-color: var(--theia-layout-color4);
display: grid;
grid-template-rows: 25px 1fr;
}
.title-bar-label {
text-align: center;
writing-mode: vertical-rl;
height: 50%;
min-height: 150px;
transform: rotate(180deg);
user-select: none;
align-self: center;
justify-self: center;
text-align: center;
writing-mode: vertical-rl;
height: 50%;
min-height: 150px;
transform: rotate(180deg);
user-select: none;
align-self: center;
justify-self: center;
}

.remove-component-button {
background: none;
border: none;
padding: 2px 8px;
align-self: center;
justify-self: center;
color: var(--theia-ui-font-color0)
background: none;
border: none;
padding: 2px 8px;
align-self: center;
justify-self: center;
color: var(--theia-ui-font-color0)
}

.main-output-container {
display: flex;
display: flex;
}

.output-component-tree {
overflow-y: scroll;
white-space: pre-wrap;
overflow-y: scroll;
white-space: pre-wrap;
}

.output-component-chart {
align-self: center;
text-align: center;
align-self: center;
text-align: center;
}

#tooltip-box {
position:absolute;
overflow:hidden;
z-index :2;
top: 80px;
right: 20px;
background-color: rgb(247, 231, 8);
width: 250px;
height: auto;
opacity: 0.8;
font-size: 13px;
font-family: arial;
text-align: left;
color: black;
padding-inline-start: 10px;
}

#timegraph-main {
width: 100%;
display: flex;
width:100%;
overflow-y:scroll;
position:relative;
height: 300px;
z-index:1;
}

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

canvas {
display: block;
display: block;
}

.innerContainer {
width: 100%;
}

.table-tree>tbody>tr:nth-child(1)>th {
background-color: var(--theia-editor-background);
position: sticky;
top: 0;
width: 100%;
}

.table-tree th, .table-tree td {
padding: 3px 5px;
text-align: left;
border-bottom: 1px solid #333;
border-right: 1px solid #333;
white-space: nowrap;
min-width: 50px;
}

.timegraph-tree tr {
/* TODO: Fix row alignment, this number is arbitrary, it works [on my machine], but it should match line height in timeline-chart */
line-height: 18px;
position: relative;
white-space: nowrap;
top: 50%;
padding: 0 0;
#input-filter-tree {
background-color: var(--theia-input-background);
border: none;
border-bottom: 1px solid var(--theia-input-foreground);
padding: 3px;
width: 180px;
color: var(--theia-input-placeholder-foreground)
}

#input-filter-tree {
background-color: var(--theia-input-background);
border: none;
border-bottom: 1px solid var(--theia-input-foreground);
padding: 3px;
width: 180px;
color: var(--theia-input-placeholder-foreground)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { EntryTree } from './utils/filtrer-tree/entry-tree';
import { listToTree, getAllExpandedNodeIds } from './utils/filtrer-tree/utils';
import hash from '../../../common/utils/value-hash';
import ColumnHeader from './utils/filtrer-tree/column-header';
import { TooltipComponent } from './utils/tooltip-component';

type TimegraphOutputProps = AbstractOutputProps & {
addWidgetResizeHandler: (handler: () => void) => void;
Expand All @@ -30,15 +31,17 @@ type TimegraphOutputState = AbstractOutputState & {
timegraphTree: TimeGraphEntry[];
collapsedNodes: number[];
columns: ColumnHeader[];
isElementSelected: boolean;
isTooltipOpened: boolean;
};

export class TimegraphOutputComponent extends AbstractTreeOutputComponent<TimegraphOutputProps, TimegraphOutputState> {
private totalHeight = 0;
private rowController: TimeGraphRowController;
private chartLayer: TimeGraphChart;
private vscrollLayer: TimeGraphVerticalScrollbar;
private horizontalContainer: React.RefObject<HTMLDivElement>;

private toolTipObject: { [key: string]: string } = {};
private toolTipPosition: { x: number, y: number } = { x: 0, y: 0 };
private tspDataProvider: TspDataProvider;
private styleMap = new Map<string, TimeGraphRowElementStyle>();

Expand All @@ -50,9 +53,13 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
outputStatus: ResponseStatus.RUNNING,
timegraphTree: [],
collapsedNodes: [],
columns: []
columns: [],
isElementSelected: false,
isTooltipOpened: false,
};
this.onToggleCollapse = this.onToggleCollapse.bind(this);
this.closeTooltip = this.closeTooltip.bind(this);
this.openTooltip = this.openTooltip.bind(this);
this.tspDataProvider = new TspDataProvider(this.props.tspClient, this.props.traceId, this.props.outputDescriptor.id);
this.rowController = new TimeGraphRowController(this.props.style.rowHeight, this.totalHeight);
this.horizontalContainer = React.createRef();
Expand All @@ -68,13 +75,23 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
};
this.chartLayer = new TimeGraphChart('timeGraphChart', providers, this.rowController);
this.vscrollLayer = new TimeGraphVerticalScrollbar('timeGraphVerticalScrollbar', this.rowController);
this.chartLayer.registerRowElementMouseInteractions({
click: (el: TimeGraphRowElement, ev: PIXI.InteractionEvent) => {
this.setTooltipPosition(el, ev);
},
mouseover: (el: TimeGraphRowElement, ev: PIXI.InteractionEvent) => {
this.setTooltipPosition(el, ev);
},
mouseout: () => {
setTimeout(() => { this.closeTooltip(); }, 6000);
}
});

this.rowController.onVerticalOffsetChangedHandler(() => {
if (this.treeRef.current) {
this.treeRef.current.scrollTop = this.rowController.verticalOffset;
}
});

this.chartLayer.onSelectedRowElementChanged(model => {
if (model) {
const el = this.chartLayer.getElementById(model.id);
Expand All @@ -93,6 +110,32 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
this.rowController.verticalOffset = this.treeRef.current.scrollTop;
}
}
closeTooltip = (): void => {
this.setState({
isTooltipOpened: false
});
};

openTooltip(): void {
this.setState({
isElementSelected: true,
isTooltipOpened: true
});
}

setTooltipPosition(el: TimeGraphRowElement, ev: PIXI.InteractionEvent): void {
const X_MAX = document.documentElement.clientWidth * 0.5;
const Y_MAX = this.totalHeight - 100;
const TOOLTIP_OFFSET = 40;
let posx = ev.data.getLocalPosition(el.displayObject).x;
posx = Math.min(posx, X_MAX - 0.5 * TOOLTIP_OFFSET);
el.position.y = Math.min(el.position.y, Y_MAX - 1.5 * TOOLTIP_OFFSET);
this.toolTipPosition.x = (posx > 0.9 * X_MAX) ? posx - 0.5 * TOOLTIP_OFFSET : posx + 0.5 * TOOLTIP_OFFSET;
this.toolTipPosition.y = (el.position.y > 0.5 * Y_MAX) ? el.position.y - 0.5 * TOOLTIP_OFFSET : el.position.y + 0.5 * TOOLTIP_OFFSET;
this.onElementSelected(this.chartLayer.getElementById(el.id));
this.closeTooltip();
this.openTooltip();
}

async componentDidMount(): Promise<void> {
this.waitAnalysisCompletion();
Expand All @@ -109,10 +152,10 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
const columns: ColumnHeader[] = [];
if (treeResponse.model.headers && treeResponse.model.headers.length > 0) {
treeResponse.model.headers.forEach(header => {
columns.push({title: header.name, sortable: true, tooltip: header.tooltip});
columns.push({ title: header.name, sortable: true, tooltip: header.tooltip });
});
} else {
columns.push({title: 'Name', sortable: true});
columns.push({ title: 'Name', sortable: true });
}
// TODO Style should not be retreive in the "initialization" part or at least async
const styleResponse = (await this.props.tspClient.fetchStyles(this.props.traceId, this.props.outputDescriptor.id, QueryHelper.query())).getModel();
Expand Down Expand Up @@ -157,8 +200,11 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
renderChart(): React.ReactNode {
return <React.Fragment>
{this.state.outputStatus === ResponseStatus.COMPLETED ?
<div id='timegraph-main' className='ps__child--consume' onWheel={ev => { ev.preventDefault(); ev.stopPropagation(); }} style={{ height: this.props.style.height }} >
<div id='timegraph-main' className='ps__child--consume' onMouseLeave={_ev => this.closeTooltip()}
onWheel={ev => { ev.preventDefault(); ev.stopPropagation(); }} style={{ height: this.props.style.height }}>
{this.renderTimeGraphContent()}
{this.state.isElementSelected && this.state.isTooltipOpened &&
<TooltipComponent tooltip={this.toolTipObject} position={this.toolTipPosition}></TooltipComponent>}
</div> :
'Analysis running...'}
</React.Fragment>;
Expand Down Expand Up @@ -209,8 +255,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
layer={[this.vscrollLayer]}
></ReactTimeGraphContainer>;
}

private async onElementSelected(element: TimeGraphRowElement | undefined) {
async getToolTipObject(element: TimeGraphRowElement | undefined): Promise<{ [key: string]: string }> {
if (element && this.props.viewRange) {
const elementRange = element.model.range;
const offset = this.props.viewRange.getOffset();
Expand All @@ -225,11 +270,16 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
...responseModel.model,
'Row': element.row.model.name
};
SignalManager.getInstance().fireTooltipSignal(tooltipObject);
this.toolTipObject = tooltipObject;
this.forceUpdate();
return this.toolTipObject;
}
}
return {};
}
async onElementSelected(element: TimeGraphRowElement | undefined): Promise<void> {
SignalManager.getInstance().fireTooltipSignal(await this.getToolTipObject(element));
}

private async fetchTimegraphData(range: TimelineChart.TimeGraphRange, resolution: number) {
const treeNodes = listToTree(this.state.timegraphTree, this.state.columns);
const orderedTreeIds = getAllExpandedNodeIds(treeNodes, this.state.collapsedNodes);
Expand Down Expand Up @@ -359,3 +409,4 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as React from 'react';
import { Component } from 'react';

interface TooltipProps {
tooltip: { [key: string]: string };
position: { x: number, y: number };
}

interface TooltipStates {
style: { top: string, left: string };

}

export class TooltipComponent extends Component<TooltipProps, TooltipStates> {
constructor(props: TooltipProps) {
super(props);
this.state = { style: { top: props.position.y + 'px', left: props.position.x + 'px' } };
}

tooltipRef: React.RefObject<HTMLDivElement> = React.createRef();

renderTooltip(): React.ReactNode {
const tooltipArray: React.ReactNode[] = [];
if (this.props.tooltip) {
const keys = Object.keys(this.props.tooltip);
keys.forEach(key => {
tooltipArray.push(<p key={key}>{key + ': ' + this.props.tooltip[key]}</p>);
});
}
else {
console.log('Tooltip null');
}
return <React.Fragment>
{tooltipArray.map(element => element)}
</React.Fragment>;
}

render(): React.ReactNode {
return <div id='tooltip-box' ref={this.tooltipRef} style={this.state.style}>
{this.renderTooltip()}
</div>;
}
}