From cecc19f1a7102b0eaa1090a48353cd66ba189ac0 Mon Sep 17 00:00:00 2001 From: Alexey Velikiy Date: Fri, 29 Oct 2021 14:37:49 +0300 Subject: [PATCH] copy types from hastic-grafana-app --- client/package.json | 2 + client/src/components/hastic_pod/index.ts | 6 +- client/src/types/analytic_units/index.ts | 195 ++++++++++++++++++++++ client/src/types/colors.ts | 105 ++++++++++++ client/src/types/detection.ts | 20 +++ client/src/types/segment.ts | 36 ++++ client/src/types/segment_array.ts | 101 +++++++++++ client/src/types/segment_set.ts | 14 ++ client/src/types/user.ts | 6 +- client/yarn.lock | 10 ++ 10 files changed, 489 insertions(+), 6 deletions(-) create mode 100644 client/src/types/analytic_units/index.ts create mode 100644 client/src/types/colors.ts create mode 100644 client/src/types/detection.ts create mode 100644 client/src/types/segment.ts create mode 100644 client/src/types/segment_array.ts create mode 100644 client/src/types/segment_set.ts diff --git a/client/package.json b/client/package.json index 0ed9a30..cf43ea9 100644 --- a/client/package.json +++ b/client/package.json @@ -12,8 +12,10 @@ "@chartwerk/line-pod": "^0.4.0", "@kyvg/vue3-notification": "^2.3.4", "@types/lodash": "^4.14.176", + "@types/tinycolor2": "^1.4.3", "axios": "^0.23.0", "lodash": "^4.17.21", + "tinycolor2": "^1.4.2", "vue": "^3.0.0", "vue-class-component": "^8.0.0-0", "vue-router": "^4.0.0-0", diff --git a/client/src/components/hastic_pod/index.ts b/client/src/components/hastic_pod/index.ts index ee353ea..fc8f2ff 100644 --- a/client/src/components/hastic_pod/index.ts +++ b/client/src/components/hastic_pod/index.ts @@ -109,13 +109,13 @@ export class HasticPod extends LinePod { const x = this.xScale(from); const y = 0; - const w = 100; - const h = 100; + const w = this.xScale(to) - x; + const h = this.height const r = this.chartContainer .append('rect') .attr('x', x) - .attr('y', x) + .attr('y', y) .attr('width', w) .attr('height', h) .attr('fill', 'red') diff --git a/client/src/types/analytic_units/index.ts b/client/src/types/analytic_units/index.ts new file mode 100644 index 0000000..acece0f --- /dev/null +++ b/client/src/types/analytic_units/index.ts @@ -0,0 +1,195 @@ +import { SegmentsSet } from '@/types/segment_set'; +import { SegmentArray } from '@/types/segment_array'; +import { Segment, SegmentId } from '@/types/segment'; +import { DetectionSpan } from '../detection'; + +import { ANALYTIC_UNIT_COLORS, DEFAULT_DELETED_SEGMENT_COLOR } from '@/types/colors'; + +import _ from 'lodash'; + + +// TODO: move types to ./types +export enum DetectorType { + PATTERN = 'pattern', + THRESHOLD = 'threshold', + ANOMALY = 'anomaly' +}; + +export enum LabelingMode { + LABELING = 'LABELING', + UNLABELING = 'UNLABELING', + DELETING = 'DELETING', + NOT_IN_LABELING_MODE = 'NOT_IN_LABELING_MODE' +}; + +export type AnalyticSegmentPair = { analyticUnit: AnalyticUnit, segment: AnalyticSegment }; +export type AnalyticSegmentsSearcher = (point: number, rangeDist: number) => AnalyticSegmentPair[]; + +export type AnalyticUnitId = string; + +export class AnalyticSegment extends Segment { + constructor(public labeled: boolean, id: SegmentId, from: number, to: number, public deleted = false) { + super(id, from, to); + if(!_.isBoolean(this.labeled)) { + throw new Error('labeled value is not boolean'); + } + if(labeled && deleted) { + throw new Error('Segment can`t be both labeled and deleted'); + } + } +} + +const DEFAULTS = { + id: null, + name: 'AnalyticUnitName', + type: 'GENERAL', + detectorType: DetectorType.PATTERN, + labeledColor: ANALYTIC_UNIT_COLORS[0], + deletedColor: DEFAULT_DELETED_SEGMENT_COLOR, + alert: false, + visible: true, + collapsed: false +}; + +const LABELING_MODES = []; + +export class AnalyticUnit { + + private _labelingMode: LabelingMode = LabelingMode.LABELING; + private _selected: boolean = false; + private _saving: boolean = false; + private _segmentSet = new SegmentArray(); + private _detectionSpans: DetectionSpan[]; + private _inspect = false; + private _changed = false; + private _status: string; + private _error: string; + + // TODO: serverObject -> fields + constructor(protected _serverObject?: any) { + if(_serverObject === undefined) { + this._serverObject = _.clone(DEFAULTS); + } + _.defaults(this._serverObject, DEFAULTS); + } + + toJSON() { + return { + id: this.id, + name: this.name, + // TODO: enum type + // TODO: type -> subType + type: this.type, + // TODO: detectorType -> type + detectorType: this.detectorType, + labeledColor: this.labeledColor, + deletedColor: this.deletedColor, + alert: this.alert, + visible: this.visible, + collapsed: this.collapsed + }; + } + + get id(): AnalyticUnitId { return this._serverObject.id; } + set id(value: AnalyticUnitId) { this._serverObject.id = value; } + + set name(value: string) { this._serverObject.name = value; } + get name(): string { return this._serverObject.name; } + + set detectorType(value: DetectorType) { this._serverObject.detectorType = value; } + get detectorType(): DetectorType { return this._serverObject.detectorType; } + + set type(value: string) { this._serverObject.type = value; } + get type(): string { return this._serverObject.type; } + + set labeledColor(value: string) { this._serverObject.labeledColor = value; } + get labeledColor(): string { return this._serverObject.labeledColor; } + + set deletedColor(value: string) { this._serverObject.deletedColor = value; } + get deletedColor(): string { return this._serverObject.deletedColor; } + + get collapsed(): boolean { return this._serverObject.collapsed; } + set collapsed(value: boolean) { this._serverObject.collapsed = value; } + + set alert(value: boolean) { this._serverObject.alert = value; } + get alert(): boolean { return this._serverObject.alert; } + + get selected(): boolean { return this._selected; } + set selected(value: boolean) { this._selected = value; } + + get labelingMode(): LabelingMode { return this._labelingMode; } + set labelingMode(value: LabelingMode) { this._labelingMode = value; } + + get saving(): boolean { return this._saving; } + set saving(value: boolean) { this._saving = value; } + + get changed(): boolean { return this._changed; } + set changed(value: boolean) { this._changed = value; } + + get inspect(): boolean { return this._inspect; } + set inspect(value: boolean) { this._inspect = value; } + + get visible(): boolean { + return (this._serverObject.visible === undefined) ? true : this._serverObject.visible + } + set visible(value: boolean) { + this._serverObject.visible = value; + } + + addSegment(segment: Segment, deleted: boolean): AnalyticSegment { + const addedSegment = new AnalyticSegment(!deleted, segment.id, segment.from, segment.to, deleted); + this._segmentSet.addSegment(addedSegment); + return addedSegment; + } + + removeSegmentsInRange(from: number, to: number): AnalyticSegment[] { + let deletedSegments = this._segmentSet.removeInRange(from, to); + return deletedSegments; + } + + get segments(): SegmentsSet { return this._segmentSet; } + set segments(value: SegmentsSet) { + this._segmentSet.setSegments(value.getSegments()); + } + + get detectionSpans(): DetectionSpan[] { return this._detectionSpans; } + set detectionSpans(value: DetectionSpan[]) { this._detectionSpans = value; } + + get status() { return this._status; } + set status(value) { + // TODO: use enum + if( + value !== '404' && + value !== 'READY' && + value !== 'LEARNING' && + value !== 'DETECTION' && + value !== 'PENDING' && + value !== 'FAILED' && + value !== 'SUCCESS' && + value !== null + ) { + throw new Error('Unsupported status value: ' + value); + } + this._status = value; + } + + get error() { return this._error; } + set error(value) { this._error = value; } + + get isActiveStatus() { + switch(this.status) { + case '404': + case 'READY': + case 'FAILED': + return false; + } + return true; + } + + get serverObject() { return this._serverObject; } + + // TODO: make it abstract + get labelingModes() { + return LABELING_MODES; + } +} \ No newline at end of file diff --git a/client/src/types/colors.ts b/client/src/types/colors.ts new file mode 100644 index 0000000..bc4b013 --- /dev/null +++ b/client/src/types/colors.ts @@ -0,0 +1,105 @@ +import tinycolor from 'tinycolor2'; +import { DetectionStatus } from '@/types/detection'; + +export const PALETTE_ROWS = 4; +export const PALETTE_COLUMNS = 14; +export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)'; +export const OK_COLOR = 'rgba(11, 237, 50, 1)'; +export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)'; +export const REGION_FILL_ALPHA = 0.09; + +let colors = [ + '#7EB26D', + '#EAB839', + '#6ED0E0', + '#EF843C', + '#E24D42', + '#1F78C1', + '#BA43A9', + '#705DA0', + '#508642', + '#CCA300', + '#447EBC', + '#C15C17', + '#890F02', + '#0A437C', + '#6D1F62', + '#584477', + '#B7DBAB', + '#F4D598', + '#70DBED', + '#F9BA8F', + '#F29191', + '#82B5D8', + '#E5A8E2', + '#AEA2E0', + '#629E51', + '#E5AC0E', + '#64B0C8', + '#E0752D', + '#BF1B00', + '#0A50A1', + '#962D82', + '#614D93', + '#9AC48A', + '#F2C96D', + '#65C5DB', + '#F9934E', + '#EA6460', + '#5195CE', + '#D683CE', + '#806EB7', + '#3F6833', + '#967302', + '#2F575E', + '#99440A', + '#58140C', + '#052B51', + '#511749', + '#3F2B5B', + '#E0F9D7', + '#FCEACA', + '#CFFAFF', + '#F9E2D2', + '#FCE2DE', + '#BADFF4', + '#F9D9F9', + '#DEDAF7', +]; + +export const ANALYTIC_UNIT_COLORS = [ + '#FF99FF', + '#71b1f9', + '#aee9fb', + '#9ce677', + '#f88990', + '#f9e26e', + '#f8c171', +]; + +export const DEFAULT_DELETED_SEGMENT_COLOR = '#00f0ff'; +export const REGION_UNLABEL_COLOR_LIGHT = '#d1d1d1'; +export const REGION_UNLABEL_COLOR_DARK = 'white'; +export const LABELED_SEGMENT_BORDER_COLOR = 'black'; +export const DELETED_SEGMENT_BORDER_COLOR = 'black'; + +export const SEGMENT_FILL_ALPHA = 0.5; +export const SEGMENT_STROKE_ALPHA = 0.8; +export const LABELING_MODE_ALPHA = 0.7; + +export const DETECTION_STATUS_COLORS = new Map([ + [DetectionStatus.READY, 'green'], + [DetectionStatus.RUNNING, 'gold'], + [DetectionStatus.FAILED, 'red'] +]); + +export function hexToHsl(color) { + return tinycolor(color).toHsl(); +} + +export function hslToHex(color) { + return tinycolor(color).toHexString(); +} + + +export default colors; \ No newline at end of file diff --git a/client/src/types/detection.ts b/client/src/types/detection.ts new file mode 100644 index 0000000..6df361a --- /dev/null +++ b/client/src/types/detection.ts @@ -0,0 +1,20 @@ +import { AnalyticUnitId } from '@/types/analytic_units/'; + +export enum DetectionStatus { + READY = 'READY', + RUNNING = 'RUNNING', + FAILED = 'FAILED' +}; + +export type DetectionSpan = { + id: AnalyticUnitId, + status: DetectionStatus, + from: number, + to: number +}; + +export const DETECTION_STATUS_TEXT = new Map([ + [DetectionStatus.READY, '[DetectionStatus]: done'], + [DetectionStatus.RUNNING, '[DetectionStatus]: running...'], + [DetectionStatus.FAILED, '[DetectionStatus]: failed'] +]); \ No newline at end of file diff --git a/client/src/types/segment.ts b/client/src/types/segment.ts new file mode 100644 index 0000000..82cc200 --- /dev/null +++ b/client/src/types/segment.ts @@ -0,0 +1,36 @@ +export type SegmentId = string; + +export class Segment { + constructor(private _id: SegmentId, public from: number, public to: number) { + if(this._id === undefined) { + throw new Error('id is undefined'); + } + if(isNaN(+from)) { + throw new Error('from can`t be NaN'); + } + if(isNaN(+to)) { + throw new Error('to can`t be NaN'); + } + } + + get id(): SegmentId { return this._id; } + set id(value) { this._id = value; } + + get middle() { return (this.from + this.to) / 2; } + + get length() { + return Math.max(this.from, this.to) - Math.min(this.from, this.to); + } + + expandDist(allDist: number, portion: number): Segment { + var p = Math.round(this.middle - allDist * portion / 2); + var q = Math.round(this.middle + allDist * portion / 2); + p = Math.min(p, this.from); + q = Math.max(q, this.to); + return new Segment(this._id, p, q); + } + + equals(segment: Segment) { + return this._id === segment._id; + } +} \ No newline at end of file diff --git a/client/src/types/segment_array.ts b/client/src/types/segment_array.ts new file mode 100644 index 0000000..eea032f --- /dev/null +++ b/client/src/types/segment_array.ts @@ -0,0 +1,101 @@ +import { SegmentsSet } from './segment_set'; +import { Segment, SegmentId } from './segment'; + + +export class SegmentArray implements SegmentsSet { + private _segments: T[]; + private _keyToSegment: Map = new Map(); + + constructor(private segments?: T[]) { + this.setSegments(segments); + } + + getSegments(from?: number, to?: number): T[] { + if(from === undefined) { + from = -Infinity; + } + if(to === undefined) { + to = Infinity; + } + var result = []; + for(var i = 0; i < this._segments.length; i++) { + var s = this._segments[i]; + if(from <= s.from && s.to <= to) { + result.push(s); + } + } + return result; + } + + setSegments(segments: T[]) { + this._segments = []; + this._keyToSegment.clear(); + if(segments) { + segments.forEach(s => { + this.addSegment(s); + }); + } + } + + addSegment(segment: T) { + if(this.has(segment.id)) { + throw new Error(`Segment with key ${segment.id} exists in set`); + } + this._keyToSegment.set(segment.id, segment); + this._segments.push(segment); + } + + findSegments(point: number, rangeDist: number): T[] { + return this._segments.filter(s => { + const expanded = s.expandDist(rangeDist, 0.01); + return (expanded.from <= point) && (point <= expanded.to); + }); + } + + removeInRange(from: number, to: number): T[] { + var deleted = []; + var newSegments = []; + for(var i = 0; i < this._segments.length; i++) { + var s = this._segments[i]; + if(from <= s.from && s.to <= to) { + this._keyToSegment.delete(s.id); + deleted.push(s); + } else { + newSegments.push(s); + } + } + this._segments = newSegments; + return deleted; + } + + get length() { + return this._segments.length; + } + + clear() { + this._segments = []; + this._keyToSegment.clear(); + } + + has(key: SegmentId): boolean { + return this._keyToSegment.has(key); + } + + remove(key: SegmentId): boolean { + if(!this.has(key)) { + return false; + } + var index = this._segments.findIndex(s => s.id === key); + this._segments.splice(index, 1); + this._keyToSegment.delete(key); + return true; + } + + updateId(fromKey: SegmentId, toKey: SegmentId) { + var segment = this._keyToSegment.get(fromKey); + this._keyToSegment.delete(fromKey); + segment.id = toKey; + this._keyToSegment.set(toKey, segment); + } + +} \ No newline at end of file diff --git a/client/src/types/segment_set.ts b/client/src/types/segment_set.ts new file mode 100644 index 0000000..d6a8375 --- /dev/null +++ b/client/src/types/segment_set.ts @@ -0,0 +1,14 @@ +import { Segment, SegmentId } from '@/types/segment' + +export interface SegmentsSet { + getSegments(from?: number, to?: number): T[]; + setSegments(segments: T[]): void; + addSegment(segment: T): void; + findSegments(point: number, rangeDist: number): T[]; + removeInRange(from: number, to: number): T[]; + remove(id: SegmentId): boolean; + has(id: SegmentId): boolean; + clear(): void; + updateId(fromId: SegmentId, toId: SegmentId): void; + length: number; +} \ No newline at end of file diff --git a/client/src/types/user.ts b/client/src/types/user.ts index 4f2aa2b..10824f6 100644 --- a/client/src/types/user.ts +++ b/client/src/types/user.ts @@ -1,5 +1,5 @@ export class User { - public username?: String - public email?: String - public password?: String + public username?: String + public email?: String + public password?: String } \ No newline at end of file diff --git a/client/yarn.lock b/client/yarn.lock index b45fc4f..e40613d 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -691,6 +691,11 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== +"@types/tinycolor2@^1.4.3": + version "1.4.3" + resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" + integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== + "@types/uglify-js@*": version "3.13.1" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea" @@ -8912,6 +8917,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tinycolor2@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" + integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"