From 060c73a965c692a29d59ffde6191cefb0188e0d4 Mon Sep 17 00:00:00 2001 From: Rodrigo Pinto Date: Tue, 7 Dec 2021 10:34:53 -0500 Subject: [PATCH] Scatter chart Data sets with scattered points, like the "latency vs. time" view, are now plotted in a scatter chart. The type of data set is defined by the string "scatter" in the output id. The scatter chart is to remain not available to the user until the tooltip is implemented. This pull request replaces https://github.com/theia-ide/theia-trace-extension/pull/399. Contributes towards fixing #45. Signed-off-by: Rodrigo Pinto --- .../src/components/xy-output-component.tsx | 186 ++++++++++++++---- .../trace-explorer-views-widget.tsx | 6 +- 2 files changed, 149 insertions(+), 43 deletions(-) diff --git a/packages/react-components/src/components/xy-output-component.tsx b/packages/react-components/src/components/xy-output-component.tsx index c5ffcc59f..206422aa6 100644 --- a/packages/react-components/src/components/xy-output-component.tsx +++ b/packages/react-components/src/components/xy-output-component.tsx @@ -3,6 +3,7 @@ import { AbstractOutputProps, AbstractOutputState } from './abstract-output-comp import { AbstractTreeOutputComponent } from './abstract-tree-output-component'; import * as React from 'react'; import { Line } from 'react-chartjs-2'; +import { Scatter } from 'react-chartjs-2'; import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper'; import { Entry } from 'tsp-typescript-client/lib/models/entry'; import { ResponseStatus } from 'tsp-typescript-client/lib/models/response/responses'; @@ -13,6 +14,7 @@ import { getAllExpandedNodeIds } from './utils/filtrer-tree/utils'; import { TreeNode } from './utils/filtrer-tree/tree-node'; import ColumnHeader from './utils/filtrer-tree/column-header'; import { BIMath } from 'timeline-chart/lib/bigint-utils'; +import { ChangeEvent } from 'react'; import { scaleLinear } from 'd3-scale'; import { axisLeft } from 'd3-axis'; import { select } from 'd3-selection'; @@ -33,11 +35,20 @@ const ZOOM_OUT = false; const PAN_LEFT = true; const PAN_RIGHT = false; +class xyPair { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + export class XYOutputComponent extends AbstractTreeOutputComponent { private currentColorIndex = 0; private colorMap: Map = new Map(); - private lineChartRef: any; private chartRef: any; + private divRef: any; private yAxisRef: any; private mouseIsDown = false; private positionXMove = 0; @@ -46,6 +57,7 @@ export class XYOutputComponent extends AbstractTreeOutputComponent { this.afterChartDraw(chartInstance); } }; @@ -74,9 +86,9 @@ export class XYOutputComponent extends AbstractTreeOutputComponent { if (event.ctrlKey) { event.preventDefault(); } }; - this.chartRef.current.addEventListener('wheel', this.preventDefaultHandler); + this.divRef.current.addEventListener('wheel', this.preventDefaultHandler); } - this.lineChartRef.current.chartInstance.render(); + this.chartRef.current.chartInstance.render(); } } @@ -141,8 +153,8 @@ export class XYOutputComponent extends AbstractTreeOutputComponent - : undefined - ; + : undefined + ; } renderYAxis(): React.ReactNode { @@ -163,8 +175,15 @@ export class XYOutputComponent extends AbstractTreeOutputComponent { - const rowMax = Math.max(...dSet.data); - const rowMin = Math.min(...dSet.data); + let rowMax; + let rowMin; + if (this.isScatterPlot) { + rowMax = Math.max(...dSet.data.map((d: any) => d.y)); + rowMin = Math.min(...dSet.data.map((d: any) => d.y)); + } else { + rowMax = Math.max(...dSet.data); + rowMin = Math.min(...dSet.data); + } allMax = Math.max(allMax, rowMax); allMin = i === 0 ? rowMin : Math.min(allMin, rowMin); }); @@ -174,11 +193,11 @@ export class XYOutputComponent extends AbstractTreeOutputComponent ( - d >= 1000000000000 ? d / 1000000000000 + 'G' : - d >= 1000000000 ? d / 1000000000 + 'B': - d >= 1000000 ? d / 1000000 + 'M' : - d >= 1000 ? d / 1000 + 'K': - d + d >= 1000000000000 ? Math.round(d / 100000000000) / 10 + 'G' : + d >= 1000000000 ? Math.round(d / 100000000) / 10 + 'B': + d >= 1000000 ? Math.round(d / 100000) / 10 + 'M' : + d >= 1000 ? Math.round(d / 100) / 10 + 'K': + Math.round(d * 10) / 10 ); if (allMax > 0) { select(this.yAxisRef.current).call(axisLeft(yScale).tickSizeOuter(0).ticks(4)).call(g => g.select('.domain').remove()); @@ -192,12 +211,15 @@ export class XYOutputComponent extends AbstractTreeOutputComponent; } - renderChart(): React.ReactNode { + chooseChart(): JSX.Element { const lineOptions: Chart.ChartOptions = { responsive: true, elements: { - point: { radius: 0 }, - line: { tension: 0 } + point: { radius: 1 }, + line: { + tension: 0, + borderWidth: 2 + } }, maintainAspectRatio: false, legend: { display: false }, @@ -215,13 +237,55 @@ export class XYOutputComponent extends AbstractTreeOutputComponent + ); + } + + const scatterOptions: Chart.ChartOptions = JSON.parse(JSON.stringify(lineOptions)); + + if (scatterOptions.elements && scatterOptions.elements.point) { + scatterOptions.elements.point.radius = 2; + } + + return ( + + ); + } + + renderChart(): React.ReactNode { if (this.state.outputStatus === ResponseStatus.COMPLETED && this.state.xyTree.length === 0 ) { return { @@ -251,15 +315,9 @@ export class XYOutputComponent extends AbstractTreeOutputComponent this.onMouseLeave(event)} onMouseDown={event => this.onMouseDown(event)} style={{ height: this.props.style.height, position: 'relative' }} - ref={this.chartRef} + ref={this.divRef} > - - + {this.chooseChart()} :
{( @@ -450,26 +508,33 @@ export class XYOutputComponent extends AbstractTreeOutputComponent 0) { + if (this.state.xyData.labels.length > 0 && !this.isScatterPlot) { this.tooltip(event.nativeEvent.x, event.nativeEvent.y); } } @@ -576,7 +641,7 @@ export class XYOutputComponent extends AbstractTreeOutputComponent(); + let xValues: number[] = []; + let yValues: number[] = []; + let pairs: xyPair[] = []; + const offset = this.getChartOffset(); + + seriesObj.forEach(series => { + const color = this.getSeriesColor(series.seriesName); + xValues = series.xValues; + yValues = series.yValues; + + xValues.forEach((value, index) => { + const adjusted = Number(BigInt(value.toString()) - offset); + pairs.push(new xyPair(adjusted, yValues[index])); + }); + + dataSetArray.push({ + label: series.seriesName, + data: pairs, + backgroundColor: color, + borderColor: color, + showLine: false, + fill: false + }); + pairs = []; + }); + + const scatterData = { + labels: xValues, + datasets: dataSetArray + }; + + this.setState({ + xyData: scatterData + }); + } + private buildXYData(seriesObj: XYSeries[]) { const dataSetArray = new Array(); let xValues: number[] = []; diff --git a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx index 84ac547ca..365ff322b 100644 --- a/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx +++ b/packages/react-components/src/trace-explorer/trace-explorer-views-widget.tsx @@ -177,10 +177,8 @@ export class ReactAvailableViewsWidget extends React.Component (value.type !== 'DATA_TREE')); - /* - * Remove certain scatter specific outputs since they are not supported in the theia-traceviewer - * There is no generic way to identify such outputs. This is not ideal because other server - * implementations are not covered. + /** + * Remove outputs of id "scatter" until the tooltip is implemented */ outputs = outputs.filter(value => !value.id.includes('scatter')); return outputs;