Attempt to make *hastic* client which works with old version of hastic-server https://code.corpglory.net/hastic/hastic-server
based on new version of hastic client from https://code.corpglory.net/hastic/hastic
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.0 KiB
166 lines
4.0 KiB
2 years ago
|
import { HasticPod } from './hastic_pod';
|
||
|
import { AnomalyHSR, TimeRange } from '@/types';
|
||
|
|
||
|
import { Segment } from "@/types/segment";
|
||
|
import { LineTimeSerie } from '@chartwerk/line-pod';
|
||
|
import { SegmentsSet } from '@/types/segment_set';
|
||
|
|
||
|
import * as _ from 'lodash';
|
||
|
|
||
|
|
||
|
export type UpdateDataCallback = (range: TimeRange) => Promise<{
|
||
|
timeserie: LineTimeSerie[],
|
||
|
hsr: AnomalyHSR,
|
||
|
segments: Segment[]
|
||
|
}>;
|
||
|
|
||
|
export type SetSeasonalityCallback = (from: number, to: number) => void;
|
||
|
|
||
|
|
||
|
export class AnomalyPod extends HasticPod<UpdateDataCallback> {
|
||
|
|
||
|
private _ssc: SetSeasonalityCallback;
|
||
|
|
||
|
private _hsr: AnomalyHSR;
|
||
|
|
||
|
private _zKeyIsDown: boolean;
|
||
|
|
||
|
private _labelSeasonality: boolean;
|
||
|
|
||
|
constructor(
|
||
|
el: HTMLElement,
|
||
|
udc: UpdateDataCallback,
|
||
|
ssc: SetSeasonalityCallback,
|
||
|
segmentSet: SegmentsSet<Segment>
|
||
|
) {
|
||
|
super(el, udc, segmentSet);
|
||
|
this._zKeyIsDown = false;
|
||
|
this._ssc = ssc;
|
||
|
|
||
|
window.addEventListener("keydown", e => {
|
||
|
if(e.code == "KeyZ") {
|
||
|
this._zKeyIsDown = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
window.addEventListener("keyup", (e) => {
|
||
|
if(e.code == "KeyZ") {
|
||
|
this._zKeyIsDown = false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.fetchData();
|
||
|
}
|
||
|
|
||
|
protected onBrushStart(): void {
|
||
|
if(this._zKeyIsDown) {
|
||
|
this._labelSeasonality = true;
|
||
|
this.svg.select('.selection')
|
||
|
.attr('fill', 'orange');
|
||
|
}
|
||
|
|
||
|
// TODO: move to state
|
||
|
this.isBrushing === true;
|
||
|
const selection = this.d3.event.selection;
|
||
|
if(selection !== null && selection.length > 0) {
|
||
|
this.brushStartSelection = this.d3.event.selection[0];
|
||
|
}
|
||
|
this.onMouseOut();
|
||
|
}
|
||
|
|
||
|
protected onBrushEnd(): void {
|
||
|
console.log("END");
|
||
|
if(!this._labelSeasonality) {
|
||
|
super.onBrushEnd();
|
||
|
} else {
|
||
|
const extent = this.d3.event.selection;
|
||
|
this.isBrushing === false;
|
||
|
if(extent === undefined || extent === null || extent.length < 2) {
|
||
|
return;
|
||
|
}
|
||
|
this.chartContainer
|
||
|
.call(this.brush.move, null);
|
||
|
|
||
|
const startTimestamp = this.xScale.invert(extent[0]);
|
||
|
const endTimestamp = this.xScale.invert(extent[1]);
|
||
|
|
||
|
if(this._labelSeasonality) {
|
||
|
this._ssc(startTimestamp, endTimestamp);
|
||
|
this._labelSeasonality = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public fetchData(): void {
|
||
|
let to = Math.floor(Date.now() / 1000);
|
||
|
let from = to - 50000; // -50000 seconds
|
||
|
|
||
|
if(!(this.state.xValueRange[0] == 0 && this.state.xValueRange[1] == 1)) {
|
||
|
[from, to] = this.state?.xValueRange;
|
||
|
}
|
||
|
|
||
|
this.udc({ from, to })
|
||
|
.then(resp => {
|
||
|
this.updateSegments(resp.segments);
|
||
|
this.updateHSR(resp.hsr);
|
||
|
this.updateData(resp.timeserie, undefined, true);
|
||
|
})
|
||
|
.catch(() => { /* set "error" message */ })
|
||
|
}
|
||
|
|
||
|
renderMetrics() {
|
||
|
this.renderHSR()
|
||
|
super.renderMetrics();
|
||
|
}
|
||
|
|
||
|
updateHSR(hsr: AnomalyHSR) {
|
||
|
this._hsr = hsr;
|
||
|
}
|
||
|
|
||
|
renderHSR() {
|
||
|
// TODO: check the case when this._bounds == undefined
|
||
|
if(this._hsr == undefined) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const pointsUp = this._hsr.ts.map(([t, v, [p, q]]) => [t, q]);
|
||
|
const pointsDown = this._hsr.ts.map(([t, v, [p, q]]) => [t, p]);
|
||
|
|
||
|
const points = pointsUp.reverse().concat(pointsDown)
|
||
|
.map(([t, v]) => `${this.xScale(t)},${this.yScale(v)}`)
|
||
|
.join(' ')
|
||
|
|
||
|
this.metricContainer
|
||
|
.append('g')
|
||
|
.append('polygon')
|
||
|
.attr('fill', 'green')
|
||
|
.attr('stroke', 'none')
|
||
|
.attr('fill-opacity', 0.2)
|
||
|
.attr('pointer-events', 'none')
|
||
|
.attr('points', points);
|
||
|
|
||
|
// seasonality grid
|
||
|
let ts = this._hsr.timestamp;
|
||
|
this._renderHSRGridLine(ts, true);
|
||
|
ts -= this._hsr.seasonality;
|
||
|
while(ts > this.state.xValueRange[0]) {
|
||
|
this._renderHSRGridLine(ts, false);
|
||
|
ts -= this._hsr.seasonality;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_renderHSRGridLine(timestamp, head) {
|
||
|
const x = this.xScale(timestamp);
|
||
|
|
||
|
this.metricContainer
|
||
|
.append('line')
|
||
|
.attr('x1', x)
|
||
|
.attr('x2', x)
|
||
|
.attr('y1', 0)
|
||
|
.attr('y2', this.height)
|
||
|
.attr("style", `stroke:blue;stroke-width: ${head ? 2 : 1}`)
|
||
|
.attr('opacity', head ? 0.5 : 0.3)
|
||
|
}
|
||
|
|
||
|
}
|