Skip to content

Commit

Permalink
widget and component view graph overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
YuanhuanO committed Jun 27, 2024
1 parent 54f266e commit 2c88b54
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 20 deletions.
277 changes: 268 additions & 9 deletions daisen/static/src/componentview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import TaskRenderer from "./taskrenderer";
import XAxisDrawer from "./xaxisdrawer";
import { ScaleLinear } from "d3";
import { Task, Dim } from "./task";
import { Widget, TimeValue } from "./widget";

type DataObject = {
info_type: string;
data: TimeValue[];
};

class ComponentView {
_yIndexAssigner: TaskYIndexAssigner;
Expand All @@ -20,22 +26,73 @@ class ComponentView {
_endTime: number;
_xScale: ScaleLinear<number, number>;
_tasks: Array<Task>;
_widget: Widget;
_graphContentWidth: number;
_graphContentHeight: number;
_graphWidth: number;
_graphHeight: number;
_titleHeight: number;
_graphPaddingTop: number;
_widgetHeight: number;
_widgetWidth: number;
_yAxisWidth: number;
_xAxisHeight: number;
_primaryAxis: string;
_secondaryAxis: string;
_numDots:number;
_componentName: string;
_svg: SVGElement;
_yScale: d3.ScaleLinear<number, number>;
_primaryYScale: d3.ScaleLinear<number, number>;
_secondaryYScale: d3.ScaleLinear<number, number>;
_primaryAxisData: object;
_secondaryAxisData: object;

constructor(
yIndexAssigner: TaskYIndexAssigner,
taskRenderer: TaskRenderer,
xAxisDrawer: XAxisDrawer
xAxisDrawer: XAxisDrawer,
widget: Widget
) {
this._yIndexAssigner = yIndexAssigner;
this._taskRenderer = taskRenderer;
this._xAxisDrawer = xAxisDrawer;
this._widget = widget;

this._marginTop = 5;
this._marginLeft = 5;
this._marginRight = 5;
this._marginBottom = 20;

this._numDots = 40;
this._widgetHeight = 100;
this._widgetWidth = 0;
this._yAxisWidth = 55;
this._graphWidth = this._widgetWidth;
this._graphContentWidth = this._widgetWidth - 2 * this._yAxisWidth;
this._titleHeight = 20;
this._graphHeight = this._widgetHeight - this._titleHeight;
this._graphPaddingTop = 5;
this._xAxisHeight = 30;
this._graphContentHeight =
this._graphHeight - this._xAxisHeight - this._graphPaddingTop;
this._componentName = this._widget._componentName;

this._startTime = 0;
this._endTime = 0;
this._primaryAxis = "ReqInCount";
this._secondaryAxis = "AvgLatency";
this._xScale = null;
this._primaryAxis = "ReqInCount";
this._secondaryAxis = "AvgLatency";
this._xScale = null;
}

setComponentName(componentName: string) {
this._componentName = componentName;
console.log('Component Name set to:', this._componentName);
}

setCanvas(canvas: HTMLElement, tooltip: HTMLElement) {
this._canvas = canvas;
this._canvasWidth = this._canvas.offsetWidth;
Expand All @@ -44,13 +101,20 @@ class ComponentView {
this._taskRenderer.setCanvas(canvas, tooltip);
this._xAxisDrawer.setCanvas(canvas);

d3.select(this._canvas)
.select("svg")
const svg = d3.select(this._canvas)
.select<SVGSVGElement>("svg")
.attr("width", this._canvasWidth)
.attr("height", this._canvasHeight);

const svgElement = svg.node() as SVGElement;

this._updateTimeScale();
this._renderData();

this._fetchAndRenderAxisData(svgElement, false);
if (!this._isSecondaryAxisSkipped()) {
this._fetchAndRenderAxisData(svgElement, true);
}
}

updateLayout() {
Expand All @@ -63,19 +127,34 @@ class ComponentView {
this._updateTimeScale();
}

setPrimaryAxis(axis: string) {
this._primaryAxis = axis;
console.log('Primary Axis set to:', this._primaryAxis);
}

setSecondaryAxis(axis: string) {
this._secondaryAxis = axis;
console.log('Secondary Axis set to:', this._secondaryAxis);
}

setTimeAxis(startTime: number, endTime: number) {
this._startTime = startTime;
this._endTime = endTime;
this._updateTimeScale();
this._widget.setXAxis(startTime, endTime);
this._fetchAndRenderAxisData(this._svg, false);
if (!this._isSecondaryAxisSkipped()) {
this._fetchAndRenderAxisData(this._svg, true);
}
}

_updateTimeScale() {
this._xScale = d3
.scaleLinear()
.domain([this._startTime, this._endTime])
.range([0, this._canvasWidth]);
.scaleLinear()
.domain([this._startTime, this._endTime])
.range([0, this._canvasWidth - this._marginLeft - this._marginRight]);

this._taskRenderer.setXScale(this._xScale);

this._drawXAxis();
}

Expand All @@ -100,12 +179,18 @@ class ComponentView {
this._tasks = tasks;

this._renderData();

if (tasks.length > 0) {
this._showLocation(tasks[0]);
} else {
this._removeLocation();
}
if (this._canvas) {
const svg = d3.select(this._canvas).select<SVGSVGElement>("svg").node() as SVGElement;
this._fetchAndRenderAxisData(svg, false);
if (!this._isSecondaryAxisSkipped()) {
this._fetchAndRenderAxisData(svg, true);
}
}
}

_renderData() {
Expand Down Expand Up @@ -316,6 +401,180 @@ class ComponentView {
}
locationLabel.text("");
}

setWidgetDimensions(width: number, height: number) {
this._widget.setDimensions(width, height);
}

setTimeRange(startTime: number, endTime: number) {
this._widget.setXAxis(startTime, endTime);
this._updateTimeScale();
}

_fetchAndRenderAxisData(svg: SVGElement, isSecondary: boolean) {
const params = new URLSearchParams();
if (isSecondary) {
params.set("info_type", this._secondaryAxis);
} else {
params.set("info_type", this._primaryAxis);
}
params.set("where", this._componentName);
console.log('Fetching data with componentName:', this._componentName);
params.set("start_time", this._startTime.toString());
params.set("end_time", this._endTime.toString());
console.log('Fetching data time', this._startTime.toString(), this._endTime.toString());
params.set("num_dots", this._numDots.toString());
console.log(params.toString());
fetch(`/api/compinfo?${params.toString()}`)
.then((rsp) => rsp.json())
.then((rsp) => {
console.log('After Fetching data with componentName:', this._componentName);
if (isSecondary) {
this._secondaryAxisData = rsp;
} else {
this._primaryAxisData = rsp;
}
this._renderAxisData(svg, rsp, isSecondary);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}


_renderAxisData(svg: SVGElement, data: object, isSecondary: boolean) {
const yScale = this._calculateYScale(data);
if (isSecondary) {
this._secondaryYScale = yScale;
} else {
this._primaryYScale = yScale;
}

this._drawYAxis(svg, yScale, isSecondary);
this._renderDataCurve(svg, data, yScale, isSecondary);
}

_calculateYScale(data: Object) {
let max = 0;

data["data"].forEach((d: TimeValue) => {
if (d.value > max) {
max = d.value;
}
});

const yScale = d3
.scaleLinear()
.domain([0, max])
.range([this._canvasHeight - this._xAxisHeight, this._marginTop]);

return yScale;
}

_drawYAxis(
svg: SVGElement,
yScale: d3.ScaleLinear<number, number>,
isSecondary: boolean
) {
const canvas = d3.select(svg);

let yAxis = d3.axisLeft(yScale);
let xOffset = this._yAxisWidth;
let axisClass = "y-axis-left";
if (isSecondary) {
yAxis = d3.axisRight(yScale);
xOffset += this._graphContentWidth;
axisClass = "y-axis-right";
}

let yAxisGroup = canvas.select("." + axisClass);
if (yAxisGroup.empty()) {
yAxisGroup = canvas.append("g").attr("class", axisClass);
}
yAxisGroup.attr(
"transform",
`translate(${xOffset}, ${this._graphPaddingTop})`
);
yAxisGroup.call(yAxis.ticks(5, ".1e"));
}

_isPrimaryAxisSkipped() {
return this._primaryAxis === "-";
}

_isSecondaryAxisSkipped() {
return this._secondaryAxis === "-";
}

_renderDataCurve(
svg: SVGElement,
data: Object,
yScale: d3.ScaleLinear<number, number>,
isSecondary: boolean
) {
const canvas = d3.select(svg);
const className = `curve-${data["info_type"]}`;
let reqInGroup = canvas.select(`.${className}`);
if (reqInGroup.empty()) {
reqInGroup = canvas.append("g").attr("class", className);
}

let color = "#d7191c";
if (isSecondary) {
color = "#2c7bb6";
}

const pathData = [];
data["data"].forEach((d: TimeValue) => {
pathData.push([d.time, d.value]);
});

const line = d3
.line()
.x((d) => this._yAxisWidth + this._xScale(d[0]))
.y((d) => this._graphPaddingTop + yScale(d[1]))
.curve(d3.curveCatmullRom.alpha(0.5));

let path = reqInGroup.select(".line");
if (path.empty()) {
path = reqInGroup.append("path").attr("class", "line");
}
path
.datum(pathData)
.attr("d", line)
.attr("fill", "none")
.attr("stroke", color);

const circles = reqInGroup.selectAll("circle").data(data["data"]);

const circleEnter = circles
.enter()
.append("circle")
.attr("cx", (d: TimeValue) => {
const x = this._yAxisWidth + this._xScale(d.time);
return x;
})
.attr("cy", this._graphContentHeight + this._graphPaddingTop)
.attr("r", 2)
.attr("fill", color);

circleEnter
.merge(
<d3.Selection<SVGCircleElement, unknown, SVGCircleElement, unknown>>(
circles
)
)
.transition()
.attr("cx", (d: TimeValue, i: number) => {
const x = this._yAxisWidth + this._xScale(d.time);
return x;
})
.attr("cy", (d: TimeValue) => {
return this._graphPaddingTop + yScale(d.value);
});

circles.exit().remove();
}
}

export default ComponentView;
export default ComponentView;
Loading

0 comments on commit 2c88b54

Please sign in to comment.