Browse Source

Merge data comparison feature

pull/2471/head
Igor Kulikov 7 years ago
parent
commit
2f11d4336f
  1. 1
      ui-ngx/angular.json
  2. 18
      ui-ngx/src/app/core/api/widget-api.models.ts
  3. 103
      ui-ngx/src/app/core/api/widget-subscription.ts
  4. 20
      ui-ngx/src/app/core/services/utils.service.ts
  5. 248
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  6. 332
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  7. 8
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  8. 7
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  9. 6
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  10. 7
      ui-ngx/src/app/shared/components/json-form/json-form-component.models.ts
  11. 3
      ui-ngx/src/app/shared/components/json-form/json-form.component.ts
  12. 3
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  13. 6
      ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts
  14. 28
      ui-ngx/src/app/shared/models/time/time.models.ts
  15. 28
      ui-ngx/src/app/shared/models/widget.models.ts
  16. 1
      ui-ngx/src/typings/jquery.flot.typings.d.ts

1
ui-ngx/angular.json

@ -55,6 +55,7 @@
"node_modules/flot/src/plugins/jquery.flot.pie.js",
"node_modules/flot/src/plugins/jquery.flot.crosshair.js",
"node_modules/flot/src/plugins/jquery.flot.stack.js",
"node_modules/flot/src/plugins/jquery.flot.symbol.js",
"node_modules/flot.curvedlines/curvedLines.js",
"node_modules/tinycolor2/dist/tinycolor-min.js",
"node_modules/tooltipster/dist/js/tooltipster.bundle.min.js",

18
ui-ngx/src/app/core/api/widget-api.models.ts

@ -17,12 +17,15 @@
import { Observable } from 'rxjs';
import { EntityId } from '@app/shared/models/id/entity-id';
import {
WidgetActionDescriptor,
widgetType,
DataSet,
Datasource,
DatasourceData,
DatasourceType,
KeyInfo,
LegendConfig,
LegendData,
Datasource,
DatasourceData, DataSet, DatasourceType, KeyInfo
WidgetActionDescriptor,
widgetType
} from '@shared/models/widget.models';
import { TimeService } from '../services/time.service';
import { DeviceService } from '../http/device.service';
@ -36,10 +39,8 @@ import { DatasourceService } from '@core/api/datasource.service';
import { RafService } from '@core/services/raf.service';
import { EntityAliases } from '@shared/models/alias.models';
import { EntityInfo } from '@app/shared/models/entity.models';
import { Type } from '@angular/core';
import { AssetService } from '@core/http/asset.service';
import { DialogService } from '@core/services/dialog.service';
import { IDashboardComponent } from '@home/models/dashboard-component.models';
import * as moment_ from 'moment';
export interface TimewindowFunctions {
onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
@ -200,6 +201,8 @@ export interface WidgetSubscriptionOptions {
timeWindowConfig?: Timewindow;
dashboardTimewindow?: Timewindow;
legendConfig?: LegendConfig;
comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor;
decimals?: number;
units?: string;
callbacks?: WidgetSubscriptionCallbacks;
@ -227,6 +230,7 @@ export interface IWidgetSubscription {
hiddenData?: Array<{data: DataSet}>;
timeWindowConfig?: Timewindow;
timeWindow?: WidgetTimewindow;
comparisonTimeWindow?: WidgetTimewindow;
alarms?: Array<AlarmInfo>;
alarmSource?: Datasource;

103
ui-ngx/src/app/core/api/widget-subscription.ts

@ -22,6 +22,7 @@ import {
WidgetSubscriptionOptions
} from '@core/api/widget-api.models';
import {
DataKey,
DataSet,
DataSetHolder,
Datasource,
@ -36,6 +37,7 @@ import {
import { HttpErrorResponse } from '@angular/common/http';
import {
createSubscriptionTimewindow,
createTimewindowForComparison,
SubscriptionTimewindow,
Timewindow,
toHistoryTimewindow,
@ -52,6 +54,9 @@ import * as deepEqual from 'deep-equal';
import { EntityId } from '@app/shared/models/id/entity-id';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { entityFields } from '@shared/models/entity.models';
import * as moment_ from 'moment';
const moment = moment_;
export class WidgetSubscription implements IWidgetSubscription {
@ -77,6 +82,10 @@ export class WidgetSubscription implements IWidgetSubscription {
stateData: boolean;
decimals: number;
units: string;
comparisonEnabled: boolean;
timeForComparison: moment_.unitOfTime.DurationConstructor;
comparisonTimeWindow: WidgetTimewindow;
timewindowForComparison: SubscriptionTimewindow;
alarms: Array<AlarmInfo>;
alarmSource: Datasource;
@ -204,6 +213,13 @@ export class WidgetSubscription implements IWidgetSubscription {
}
this.subscriptionTimewindow = null;
this.comparisonEnabled = options.comparisonEnabled;
if (this.comparisonEnabled) {
this.timeForComparison = options.timeForComparison;
this.comparisonTimeWindow = {};
this.timewindowForComparison = null;
}
this.units = options.units || '';
this.decimals = isDefined(options.decimals) ? options.decimals : 2;
@ -345,11 +361,23 @@ export class WidgetSubscription implements IWidgetSubscription {
}
private configureData() {
const additionalDatasources: Datasource[] = [];
let dataIndex = 0;
let additionalKeysNumber = 0;
this.datasources.forEach((datasource) => {
const additionalDataKeys: DataKey[] = [];
let datasourceAdditionalKeysNumber = 0;
datasource.dataKeys.forEach((dataKey) => {
dataKey.hidden = false;
dataKey.pattern = dataKey.label;
if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) {
datasourceAdditionalKeysNumber++;
additionalKeysNumber++;
const additionalDataKey = this.ctx.utils.createAdditionalDataKey(dataKey, datasource,
this.timeForComparison, this.datasources, additionalKeysNumber);
dataKey.settings.comparisonSettings.color = additionalDataKey.color;
additionalDataKeys.push(additionalDataKey);
}
const datasourceData: DatasourceData = {
datasource,
dataKey,
@ -379,7 +407,43 @@ export class WidgetSubscription implements IWidgetSubscription {
this.legendData.data.push(legendKeyData);
}
});
if (datasourceAdditionalKeysNumber > 0) {
const additionalDatasource: Datasource = deepClone(datasource);
additionalDatasource.dataKeys = additionalDataKeys;
additionalDatasource.isAdditional = true;
additionalDatasources.push(additionalDatasource);
}
});
additionalDatasources.forEach((additionalDatasource) => {
additionalDatasource.dataKeys.forEach((additionalDataKey) => {
const additionalDatasourceData: DatasourceData = {
datasource: additionalDatasource,
dataKey: additionalDataKey,
data: []
};
this.data.push(additionalDatasourceData);
this.hiddenData.push({data: []});
if (this.displayLegend) {
const additionalLegendKey: LegendKey = {
dataKey: additionalDataKey,
dataIndex: dataIndex++
};
this.legendData.keys.push(additionalLegendKey);
const additionalLegendKeyData: LegendKeyData = {
min: null,
max: null,
avg: null,
total: null,
hidden: false
};
this.legendData.data.push(additionalLegendKeyData);
}
});
});
this.datasources = this.datasources.concat(additionalDatasources);
if (this.displayLegend) {
this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label));
}
@ -678,6 +742,9 @@ export class WidgetSubscription implements IWidgetSubscription {
this.notifyDataLoading();
if (this.type === widgetType.timeseries && this.timeWindowConfig) {
this.updateRealtimeSubscription();
if (this.comparisonEnabled) {
this.updateSubscriptionForComparison();
}
if (this.subscriptionTimewindow.fixedWindow) {
this.onDataUpdated();
}
@ -702,6 +769,17 @@ export class WidgetSubscription implements IWidgetSubscription {
datasourceIndex: index
};
if (this.comparisonEnabled && datasource.isAdditional) {
listener.subscriptionTimewindow = this.timewindowForComparison;
listener.updateRealtimeSubscription = () => {
this.subscriptionTimewindow = this.updateSubscriptionForComparison();
return this.subscriptionTimewindow;
};
listener.setRealtimeSubscription = () => {
this.updateSubscriptionForComparison();
};
}
let entityFieldKey = false;
for (let a = 0; a < datasource.dataKeys.length; a++) {
@ -842,7 +920,7 @@ export class WidgetSubscription implements IWidgetSubscription {
private updateTimewindow() {
this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
if (this.subscriptionTimewindow.realtimeWindowMs) {
this.timeWindow.maxTime = Date.now() + this.timeWindow.stDiff;
this.timeWindow.maxTime = moment().valueOf() + this.timeWindow.stDiff;
this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
} else if (this.subscriptionTimewindow.fixedWindow) {
this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs;
@ -862,6 +940,26 @@ export class WidgetSubscription implements IWidgetSubscription {
return this.subscriptionTimewindow;
}
private updateComparisonTimewindow() {
this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
if (this.timewindowForComparison.realtimeWindowMs) {
this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();
this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs;
} else if (this.timewindowForComparison.fixedWindow) {
this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs;
this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs;
}
}
private updateSubscriptionForComparison() {
if (!this.subscriptionTimewindow) {
this.subscriptionTimewindow = this.updateRealtimeSubscription();
}
this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
this.updateComparisonTimewindow();
return this.timewindowForComparison;
}
private dataUpdated(sourceData: DataSetHolder, datasourceIndex: number, dataKeyIndex: number, detectChanges: boolean) {
for (let x = 0; x < this.datasourceListeners.length; x++) {
this.datasources[x].dataReceived = this.datasources[x].dataReceived === true;
@ -892,6 +990,9 @@ export class WidgetSubscription implements IWidgetSubscription {
if (update) {
if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
this.updateTimewindow();
if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) {
this.updateComparisonTimewindow();
}
}
currentData.data = sourceData.data;
if (this.caulculateLegendData) {

20
ui-ngx/src/app/core/services/utils.service.ts

@ -359,6 +359,26 @@ export class UtilsService {
return dataKey;
}
public createAdditionalDataKey(dataKey: DataKey, datasource: Datasource, timeUnit: string,
datasources: Datasource[], additionalKeysNumber: number): DataKey {
const additionalDataKey = deepClone(dataKey);
if (dataKey.settings.comparisonSettings.comparisonValuesLabel) {
additionalDataKey.label = this.createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
} else {
additionalDataKey.label = dataKey.label + ' ' + this.translate.instant('legend.comparison-time-ago.'+timeUnit);
}
additionalDataKey.pattern = additionalDataKey.label;
if (dataKey.settings.comparisonSettings.color) {
additionalDataKey.color = dataKey.settings.comparisonSettings.color;
} else {
const index = datasources.map((_datasource) => datasource.dataKeys.length)
.reduce((previousValue, currentValue) => previousValue + currentValue, 0);
additionalDataKey.color = this.getMaterialColor(index + additionalKeysNumber);
}
additionalDataKey._hash = Math.random();
return additionalDataKey;
}
public createLabelFromDatasource(datasource: Datasource, pattern: string) {
let label = pattern;
if (!datasource) {

248
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts

@ -17,7 +17,8 @@
// tslint:disable-next-line:no-reference
/// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" />
import { JsonSettingsSchema, DataKey, DatasourceData } from '@shared/models/widget.models';
import { JsonSettingsSchema, DataKey, DatasourceData, Datasource } from '@shared/models/widget.models';
import * as moment_ from 'moment';
export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph';
@ -29,6 +30,7 @@ export declare type TbFlotTicksFormatterFunction = (t: number, a?: TbFlotPlotAxi
export interface TbFlotSeries extends DatasourceData, JQueryPlotSeriesOptions {
dataKey: TbFlotDataKey;
xaxisIndex?: number;
yaxisIndex?: number;
yaxis?: number;
}
@ -50,6 +52,7 @@ export interface TbFlotAxisOptions extends JQueryPlotAxisOptions {
}
export interface TbFlotPlotDataSeries extends JQueryPlotDataSeries {
datasource?: Datasource;
dataKey?: TbFlotDataKey;
percent?: number;
}
@ -93,6 +96,12 @@ export interface TbFlotXAxisSettings {
color: boolean;
}
export interface TbFlotSecondXAxisSettings {
axisPosition: TbFlotXAxisPosition;
showLabels: boolean;
title: string;
}
export interface TbFlotYAxisSettings {
min: number;
max: number;
@ -112,16 +121,23 @@ export interface TbFlotBaseSettings {
tooltipIndividual: boolean;
tooltipCumulative: boolean;
tooltipValueFormatter: string;
hideZeros: boolean;
grid: TbFlotGridSettings;
xaxis: TbFlotXAxisSettings;
yaxis: TbFlotYAxisSettings;
}
export interface TbFlotGraphSettings extends TbFlotBaseSettings {
export interface TbFlotComparisonSettings {
comparisonEnabled: boolean;
timeForComparison: moment_.unitOfTime.DurationConstructor;
xaxisSecond: TbFlotSecondXAxisSettings;
}
export interface TbFlotGraphSettings extends TbFlotBaseSettings, TbFlotComparisonSettings {
smoothLines: boolean;
}
export interface TbFlotBarSettings extends TbFlotBaseSettings {
export interface TbFlotBarSettings extends TbFlotBaseSettings, TbFlotComparisonSettings {
defaultBarWidth: number;
}
@ -140,11 +156,17 @@ export interface TbFlotPieSettings {
}
export declare type TbFlotYAxisPosition = 'left' | 'right';
export declare type TbFlotXAxisPosition = 'top' | 'bottom';
export interface TbFlotKeySettings {
excludeFromStacking: boolean;
showLines: boolean;
fillLines: boolean;
showPoints: boolean;
showPointShape: string;
pointShapeFormatter: string;
showPointsLineWidth: number;
showPointsRadius: number;
lineWidth: number;
tooltipValueFormatter: string;
showSeparateAxis: boolean;
@ -218,6 +240,11 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
type: 'string',
default: ''
};
properties.hideZeros = {
title: 'Hide zero/false values from tooltip',
type: 'boolean',
default: false
};
properties.grid = {
title: 'Grid settings',
@ -345,6 +372,7 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
key: 'tooltipValueFormatter',
type: 'javascript'
});
schema.form.push('hideZeros');
schema.form.push({
key: 'grid',
items: [
@ -395,9 +423,112 @@ export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema {
}
]
});
if (chartType === 'graph' || chartType === 'bar') {
schema.groupInfoes = [{
formIndex: 0,
GroupTitle: 'Common Settings'
}];
schema.form = [schema.form];
schema.schema.properties = {...schema.schema.properties, ...chartSettingsSchemaForComparison.schema.properties};
schema.schema.required = schema.schema.required.concat(chartSettingsSchemaForComparison.schema.required);
schema.form.push(chartSettingsSchemaForComparison.form);
schema.groupInfoes.push({
formIndex: schema.groupInfoes.length,
GroupTitle:'Comparison Settings'
});
}
return schema;
}
const chartSettingsSchemaForComparison: JsonSettingsSchema = {
schema: {
title: 'Comparison Settings',
type: 'object',
properties: {
comparisonEnabled: {
title: 'Enable comparison',
type: 'boolean',
default: false
},
timeForComparison: {
title: 'Time to show historical data',
type: 'string',
default: 'months'
},
xaxisSecond: {
title: 'Second X axis',
type: 'object',
properties: {
axisPosition: {
title: 'Axis position',
type: 'string',
default: 'top'
},
showLabels: {
title: 'Show labels',
type: 'boolean',
default: true
},
title: {
title: 'Axis title',
type: 'string',
default: null
}
}
}
},
required: []
},
form: [
'comparisonEnabled',
{
key: 'timeForComparison',
type: 'rc-select',
multiple: false,
items: [
{
value: 'days',
label: 'Day ago'
},
{
value: 'weeks',
label: 'Week ago'
},
{
value: 'months',
label: 'Month ago (default)'
},
{
value: 'years',
label: 'Year ago'
}
]
},
{
key: 'xaxisSecond',
items: [
{
key: 'xaxisSecond.axisPosition',
type: 'rc-select',
multiple: false,
items: [
{
value: 'top',
label: 'Top (default)'
},
{
value: 'bottom',
label: 'Bottom'
}
]
},
'xaxisSecond.showLabels',
'xaxisSecond.title',
]
}
]
};
export const flotPieSettingsSchema: JsonSettingsSchema = {
schema: {
type: 'object',
@ -481,12 +612,17 @@ export const flotPieSettingsSchema: JsonSettingsSchema = {
]
};
export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema {
return {
export function flotDatakeySettingsSchema(defaultShowLines: boolean, chartType: ChartType): JsonSettingsSchema {
const schema: JsonSettingsSchema = {
schema: {
type: 'object',
title: 'DataKeySettings',
properties: {
excludeFromStacking: {
title: 'Exclude from stacking(available in "Stacking" mode)',
type: 'boolean',
default: false
},
showLines: {
title: 'Show lines',
type: 'boolean',
@ -502,10 +638,29 @@ export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettin
type: 'boolean',
default: false
},
lineWidth: {
title: 'Line width',
showPointShape: {
title: 'Select point shape:',
type: 'string',
default: 'circle'
},
pointShapeFormatter: {
title: 'Point shape format function, f(ctx, x, y, radius, shadow)',
type: 'string',
default: 'var size = radius * Math.sqrt(Math.PI) / 2;\n' +
'ctx.moveTo(x - size, y - size);\n' +
'ctx.lineTo(x + size, y + size);\n' +
'ctx.moveTo(x - size, y + size);\n' +
'ctx.lineTo(x + size, y - size);'
},
showPointsLineWidth: {
title: 'Line width of points',
type: 'number',
default: null
default: 5
},
showPointsRadius: {
title: 'Radius of points',
type: 'number',
default: 3
},
tooltipValueFormatter: {
title: 'Tooltip value format function, f(value)',
@ -556,9 +711,47 @@ export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettin
required: ['showLines', 'fillLines', 'showPoints']
},
form: [
'excludeFromStacking',
'showLines',
'fillLines',
'showPoints',
{
key: 'showPointShape',
type: 'rc-select',
multiple: false,
items: [
{
value: 'circle',
label: 'Circle'
},
{
value: 'cross',
label: 'Cross'
},
{
value: 'diamond',
label: 'Diamond'
},
{
value: 'square',
label: 'Square'
},
{
value: 'triangle',
label: 'Triangle'
},
{
value: 'custom',
label: 'Custom function'
}
]
},
{
key: 'pointShapeFormatter',
type: 'javascript'
},
'showPointsLineWidth',
'showPointsRadius',
{
key: 'tooltipValueFormatter',
type: 'javascript'
@ -590,4 +783,43 @@ export function flotDatakeySettingsSchema(defaultShowLines: boolean): JsonSettin
}
]
};
const properties = schema.schema.properties;
if (chartType === 'graph' || chartType === 'bar') {
properties.comparisonSettings = {
title: 'Comparison Settings',
type: 'object',
properties: {
showValuesForComparison: {
title: 'Show historical values for comparison',
type: 'boolean',
default: true
},
comparisonValuesLabel: {
title: 'Historical values label',
type: 'string',
default: ''
},
color: {
title: 'Color',
type: 'string',
default: ''
}
},
required: ['showValuesForComparison']
};
schema.form.push({
key: 'comparisonSettings',
items: [
'comparisonSettings.showValuesForComparison',
'comparisonSettings.comparisonValuesLabel',
{
key: 'comparisonSettings.color',
type: 'color'
}
]
});
}
return schema;
}

332
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -41,6 +41,7 @@ import * as tinycolor_ from 'tinycolor2';
import { AggregationType } from '@shared/models/time/time.models';
import { CancelAnimationFrame } from '@core/services/raf.service';
import Timeout = NodeJS.Timeout;
import { UtilsService } from '@core/services/utils.service';
const tinycolor = tinycolor_;
const moment = moment_;
@ -56,6 +57,7 @@ export class TbFlot {
private readonly yAxisTickFormatter: TbFlotTicksFormatterFunction;
private ticksFormatterFunction: TbFlotTicksFormatterFunction;
private readonly yaxis: TbFlotAxisOptions;
private readonly xaxis: TbFlotAxisOptions;
private yaxes: Array<TbFlotAxisOptions>;
private readonly options: JQueryPlotOptions;
@ -66,6 +68,7 @@ export class TbFlot {
private readonly trackDecimals: number;
private readonly tooltipIndividual: boolean;
private readonly tooltipCumulative: boolean;
private readonly hideZeros: boolean;
private readonly defaultBarWidth: number;
@ -106,13 +109,14 @@ export class TbFlot {
return flotSettingsSchema(chartType);
}
static datakeySettingsSchema(defaultShowLines: boolean): JsonSettingsSchema {
return flotDatakeySettingsSchema(defaultShowLines);
static datakeySettingsSchema(defaultShowLines: boolean, chartType: ChartType): JsonSettingsSchema {
return flotDatakeySettingsSchema(defaultShowLines, chartType);
}
constructor(private ctx: WidgetContext, private readonly chartType: ChartType) {
this.chartType = this.chartType || 'line';
this.settings = ctx.settings as TbFlotSettings;
const utils = this.ctx.$injector.get(UtilsService);
this.tooltip = $('#flot-series-tooltip');
if (this.tooltip.length === 0) {
this.tooltip = this.createTooltipElement();
@ -123,6 +127,7 @@ export class TbFlot {
this.tooltipIndividual = this.chartType === 'pie' || (isDefined(this.settings.tooltipIndividual)
? this.settings.tooltipIndividual : false);
this.tooltipCumulative = isDefined(this.settings.tooltipCumulative) ? this.settings.tooltipCumulative : false;
this.hideZeros = isDefined(this.settings.hideZeros) ? this.settings.hideZeros : false;
const font = {
color: this.settings.fontColor || '#545454',
@ -147,7 +152,8 @@ export class TbFlot {
};
if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
this.options.xaxis = {
this.options.xaxes = [];
this.xaxis = {
mode: 'time',
timezone: 'browser',
font: deepClone(font),
@ -158,16 +164,11 @@ export class TbFlot {
labelFont: deepClone(font)
};
if (this.settings.xaxis) {
if (this.settings.xaxis.showLabels === false) {
this.options.xaxis.tickFormatter = () => {
return '';
};
}
this.options.xaxis.font.color = this.settings.xaxis.color || this.options.xaxis.font.color;
this.options.xaxis.label = this.settings.xaxis.title || null;
this.options.xaxis.labelFont.color = this.options.xaxis.font.color;
this.options.xaxis.labelFont.size = this.options.xaxis.font.size + 2;
this.options.xaxis.labelFont.weight = 'bold';
this.xaxis.font.color = this.settings.xaxis.color || this.xaxis.font.color;
this.xaxis.label = utils.customTranslation(this.settings.xaxis.title, this.settings.xaxis.title) || null;
this.xaxis.labelFont.color = this.xaxis.font.color;
this.xaxis.labelFont.size = this.xaxis.font.size + 2;
this.xaxis.labelFont.weight = 'bold';
}
this.yAxisTickFormatter = this.formatYAxisTicks.bind(this);
@ -178,7 +179,7 @@ export class TbFlot {
this.yaxis.font.color = this.settings.yaxis.color || this.yaxis.font.color;
this.yaxis.min = isDefined(this.settings.yaxis.min) ? this.settings.yaxis.min : null;
this.yaxis.max = isDefined(this.settings.yaxis.max) ? this.settings.yaxis.max : null;
this.yaxis.label = this.settings.yaxis.title || null;
this.yaxis.label = utils.customTranslation(this.settings.yaxis.title, this.settings.yaxis.title) || null;
this.yaxis.labelFont.color = this.yaxis.font.color;
this.yaxis.labelFont.size = this.yaxis.font.size + 2;
this.yaxis.labelFont.weight = 'bold';
@ -212,7 +213,7 @@ export class TbFlot {
this. options.grid.borderWidth = isDefined(this.settings.grid.outlineWidth) ?
this.settings.grid.outlineWidth : 1;
if (this.settings.grid.verticalLines === false) {
this.options.xaxis.tickLength = 0;
this.xaxis.tickLength = 0;
}
if (this.settings.grid.horizontalLines === false) {
this.yaxis.tickLength = 0;
@ -225,14 +226,41 @@ export class TbFlot {
}
}
this.options.xaxes[0] = deepClone(this.xaxis);
if (this.settings.xaxis && this.settings.xaxis.showLabels === false) {
this.options.xaxes[0].tickFormatter = () => {
return '';
};
}
if (this.settings.comparisonEnabled) {
const xaxis = deepClone(this.xaxis);
xaxis.position = 'top';
if (this.settings.xaxisSecond) {
if (this.settings.xaxisSecond.showLabels === false) {
xaxis.tickFormatter = () => {
return '';
};
}
xaxis.label = utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
xaxis.position = this.settings.xaxisSecond.axisPosition;
}
xaxis.tickLength = 0;
this.options.xaxes.push(xaxis);
this.options.series = {
stack: false
};
} else {
this.options.series = {
stack: this.settings.stack === true
};
}
this.options.crosshair = {
mode: 'x'
};
this.options.series = {
stack: this.settings.stack === true
};
if (this.chartType === 'line' && this.settings.smoothLines) {
this.options.series.curvedLines = {
active: true,
@ -339,6 +367,13 @@ export class TbFlot {
series.lines = {
fill: keySettings.fillLines === true
};
if (this.settings.stack && !this.settings.comparisonEnabled) {
series.stack = !keySettings.excludeFromStacking;
} else {
series.stack = false;
}
if (this.chartType === 'line' || this.chartType === 'state') {
series.lines.show = keySettings.showLines !== false;
} else {
@ -353,8 +388,16 @@ export class TbFlot {
};
if (keySettings.showPoints === true) {
series.points.show = true;
series.points.lineWidth = 5;
series.points.radius = 3;
series.points.lineWidth = isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5;
series.points.radius = isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3;
series.points.symbol = isDefined(keySettings.showPointShape) ? keySettings.showPointShape : 'circle';
if (series.points.symbol === 'custom' && keySettings.pointShapeFormatter) {
try {
series.points.symbol = new Function('ctx, x, y, radius, shadow', keySettings.pointShapeFormatter);
} catch (e) {
series.points.symbol = 'circle';
}
}
}
if (this.chartType === 'line' && this.settings.smoothLines && !series.points.show) {
series.curvedLines = {
@ -367,6 +410,14 @@ export class TbFlot {
series.highlightColor = lineColor.toRgbString();
if (series.datasource.isAdditional) {
series.xaxisIndex = 1;
series.xaxis = 2;
} else {
series.xaxisIndex = 0;
series.xaxis = 1;
}
if (this.yaxis) {
const units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.trackUnits;
let yaxis: TbFlotAxisOptions;
@ -384,7 +435,7 @@ export class TbFlot {
series.yaxisIndex = this.yaxes.indexOf(yaxis);
series.yaxis = series.yaxisIndex + 1;
yaxis.keysInfo[i] = {hidden: false};
yaxis.hidden = false;
yaxis.show = true;
}
}
this.options.colors = colors;
@ -398,8 +449,12 @@ export class TbFlot {
this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
}
}
this.options.xaxis.min = this.subscription.timeWindow.minTime;
this.options.xaxis.max = this.subscription.timeWindow.maxTime;
this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
if (this.settings.comparisonEnabled) {
this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
}
}
this.checkMouseEvents();
@ -469,8 +524,12 @@ export class TbFlot {
}
}
this.options.xaxis.min = this.subscription.timeWindow.minTime;
this.options.xaxis.max = this.subscription.timeWindow.maxTime;
this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
if (this.settings.comparisonEnabled) {
this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
}
if (this.chartType === 'bar') {
if (this.subscription.timeWindowConfig.aggregation &&
this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) {
@ -485,6 +544,10 @@ export class TbFlot {
} else {
this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
if (this.settings.comparisonEnabled) {
this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
}
if (this.chartType === 'bar') {
if (this.subscription.timeWindowConfig.aggregation &&
this.subscription.timeWindowConfig.aggregation.type === AggregationType.NONE) {
@ -653,7 +716,7 @@ export class TbFlot {
divElement.css({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start'
justifyContent: 'space-between'
});
const lineSpan = $('<span></span>');
lineSpan.css({
@ -734,57 +797,98 @@ export class TbFlot {
return divElement.prop('outerHTML');
}
private formatChartTooltip(hoverInfo: TbFlotHoverInfo, seriesIndex: number): string {
private formatChartTooltip(hoverInfo: TbFlotHoverInfo[], seriesIndex: number): string {
let content = '';
const timestamp = parseInt(hoverInfo.time, 10);
const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
const dateDiv = $(`<div>${date}</div>`);
dateDiv.css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
fontWeight: '700'
});
content += dateDiv.prop('outerHTML');
if (this.tooltipIndividual) {
const found = hoverInfo.seriesHover.find((seriesHover) => {
return seriesHover.index === seriesIndex;
});
if (found) {
content += this.seriesInfoDivFromInfo(found, seriesIndex);
let seriesHoverArray: TbFlotSeriesHoverInfo[];
if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
seriesHoverArray = hoverInfo[0].seriesHover.concat(hoverInfo[1].seriesHover);
} else {
seriesHoverArray = hoverInfo[0].seriesHover;
}
} else {
const seriesDiv = $('<div></div>');
seriesDiv.css({
display: 'flex',
flexDirection: 'row'
const found = seriesHoverArray.filter((seriesHover) => {
return seriesHover.index === seriesIndex;
});
const maxRows = 15;
const columns = Math.ceil(hoverInfo.seriesHover.length / maxRows);
let columnsContent = '';
for (let c = 0; c < columns; c++) {
const columnDiv = $('<div></div>');
columnDiv.css({
if (found && found.length) {
let timestamp: number;
if (!isNumber(hoverInfo[0].time) || (found[0].time < hoverInfo[0].time)) {
timestamp = parseInt(hoverInfo[1].time, 10);
} else {
timestamp = parseInt(hoverInfo[0].time, 10);
}
const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
const dateDiv = $('<div>' + date + '</div>');
dateDiv.css({
display: 'flex',
flexDirection: 'column'
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
fontWeight: '700'
});
let columnContent = '';
for (let i = c * maxRows; i < (c + 1) * maxRows; i++) {
if (i === hoverInfo.seriesHover.length) {
break;
content += dateDiv.prop('outerHTML');
content += this.seriesInfoDivFromInfo(found[0], seriesIndex);
}
} else {
let maxRows: number;
if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
maxRows = 5;
} else {
maxRows = 15;
}
let columns = 0;
if (hoverInfo[1] && (hoverInfo[1].seriesHover.length > hoverInfo[0].seriesHover.length)) {
columns = Math.ceil(hoverInfo[1].seriesHover.length / maxRows);
} else {
columns = Math.ceil(hoverInfo[0].seriesHover.length / maxRows);
}
hoverInfo.forEach((hoverData) => {
if (isNumber(hoverData.time)) {
let columnsContent = '';
const timestamp = parseInt(hoverData.time, 10);
const date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
const dateDiv = $('<div>' + date + '</div>');
dateDiv.css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
fontWeight: '700'
});
content += dateDiv.prop('outerHTML');
const seriesDiv = $('<div></div>');
seriesDiv.css({
display: 'flex',
flexDirection: 'row'
});
for (let c = 0; c < columns; c++) {
const columnDiv = $('<div></div>');
columnDiv.css({
display: 'flex',
flexDirection: 'column',
flex: '1'
});
let columnContent = '';
for (let i = c*maxRows; i < (c+1)*maxRows; i++) {
if (i >= hoverData.seriesHover.length) {
break;
}
const seriesHoverInfo = hoverData.seriesHover[i];
columnContent += this.seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
}
columnDiv.html(columnContent);
if (columnContent) {
if (c > 0) {
columnsContent += '<span style="min-width: 20px;"></span>';
}
columnsContent += columnDiv.prop('outerHTML');
}
}
const seriesHoverInfo = hoverInfo.seriesHover[i];
columnContent += this.seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
}
columnDiv.html(columnContent);
if (c > 0) {
columnsContent += '<span style="width: 20px;"></span>';
seriesDiv.html(columnsContent);
content += seriesDiv.prop('outerHTML');
}
columnsContent += columnDiv.prop('outerHTML');
}
seriesDiv.html(columnsContent);
content += seriesDiv.prop('outerHTML');
});
}
return content;
}
@ -848,16 +952,21 @@ export class TbFlot {
const pageY = pos.pageY;
let tooltipHtml;
let hoverInfo: TbFlotHoverInfo;
let hoverInfo: TbFlotHoverInfo[];
if (this.chartType === 'pie') {
tooltipHtml = this.formatPieTooltip(item);
} else {
hoverInfo = this.getHoverInfo(this.plot.getData(), pos);
if (isNumber(hoverInfo.time)) {
hoverInfo.seriesHover.sort((a, b) => {
if (isNumber(hoverInfo[0].time) || (hoverInfo[1] && isNumber(hoverInfo[1].time))) {
hoverInfo[0].seriesHover.sort((a, b) => {
return b.value - a.value;
});
if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
hoverInfo[1].seriesHover.sort((a, b) => {
return b.value - a.value;
});
}
tooltipHtml = this.formatChartTooltip(hoverInfo, item ? item.seriesIndex : -1);
}
}
@ -883,8 +992,10 @@ export class TbFlot {
left
});
if (multipleModeTooltip) {
hoverInfo.seriesHover.forEach((seriesHoverInfo) => {
this.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex);
hoverInfo.forEach((hoverData) => {
hoverData.seriesHover.forEach((seriesHoverInfo) => {
this.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex);
});
});
}
}
@ -927,7 +1038,7 @@ export class TbFlot {
this.isMouseInteraction = false;
}
private getHoverInfo(seriesList: TbFlotPlotDataSeries[], pos: JQueryPlotPoint): TbFlotHoverInfo {
private getHoverInfo(seriesList: TbFlotPlotDataSeries[], pos: JQueryPlotPoint): TbFlotHoverInfo[] {
let i: number;
let series: TbFlotPlotDataSeries;
let hoverIndex: number;
@ -935,19 +1046,40 @@ export class TbFlot {
let minDistance: number;
let pointTime: any;
let minTime: any;
let minTimeHistorical: any;
let hoverData: TbFlotSeriesHoverInfo;
let value: any;
let lastValue: any;
const results: TbFlotHoverInfo = {
let minDistanceHistorical: number;
const results: TbFlotHoverInfo[] = [{
seriesHover: []
};
}];
if (this.settings.comparisonEnabled) {
results.push({
seriesHover: []
});
}
for (i = 0; i < seriesList.length; i++) {
series = seriesList[i];
hoverIndex = this.findHoverIndexFromData(pos.x, series);
let posx: number;
if (series.datasource.isAdditional) {
posx = pos.x2;
} else {
posx = pos.x;
}
hoverIndex = this.findHoverIndexFromData(posx, series);
if (series.data[hoverIndex] && series.data[hoverIndex][0]) {
hoverDistance = pos.x - series.data[hoverIndex][0];
hoverDistance = posx - series.data[hoverIndex][0];
pointTime = series.data[hoverIndex][0];
if (!minDistance
if (series.datasource.isAdditional) {
if (!minDistanceHistorical
|| (hoverDistance >= 0 && (hoverDistance < minDistanceHistorical || minDistanceHistorical < 0))
|| (hoverDistance < 0 && hoverDistance > minDistanceHistorical)) {
minDistanceHistorical = hoverDistance;
minTimeHistorical = pointTime;
}
} else if (!minDistance
|| (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0))
|| (hoverDistance < 0 && hoverDistance > minDistance)) {
minDistance = hoverDistance;
@ -964,23 +1096,33 @@ export class TbFlot {
value = series.data[hoverIndex][1];
}
if (series.stack || (series.curvedLines && series.curvedLines.apply)) {
hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
hoverIndex = this.findHoverIndexFromDataPoints(posx, series, hoverIndex);
}
if (!this.hideZeros || value) {
hoverData = {
value,
hoverIndex,
color: series.dataKey.color,
label: series.dataKey.label,
units: series.dataKey.units,
decimals: series.dataKey.decimals,
tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction,
time: pointTime,
distance: hoverDistance,
index: i
};
if (series.datasource.isAdditional) {
results[1].seriesHover.push(hoverData);
} else {
results[0].seriesHover.push(hoverData);
}
}
results.seriesHover.push({
value,
hoverIndex,
color: series.dataKey.color,
label: series.dataKey.label,
units: series.dataKey.units,
decimals: series.dataKey.decimals,
tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction,
time: pointTime,
distance: hoverDistance,
index: i
});
}
}
results.time = minTime;
if (results[1] && results[1].seriesHover.length) {
results[1].time = minTimeHistorical;
}
results[0].time = minTime;
return results;
}

8
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -23,7 +23,7 @@ import {
Datasource,
DatasourceType,
datasourceTypeTranslationMap,
defaultLegendConfig,
defaultLegendConfig, GroupInfo, JsonSchema,
widgetType
} from '@shared/models/widget.models';
import {
@ -60,11 +60,11 @@ import { WidgetActionsData } from './action/manage-widget-actions.component.mode
import { DashboardState } from '@shared/models/dashboard.models';
import { entityFields } from '@shared/models/entity.models';
const emptySettingsSchema = {
const emptySettingsSchema: JsonSchema = {
type: 'object',
properties: {}
};
const emptySettingsGroupInfoes = [];
const emptySettingsGroupInfoes: GroupInfo[] = [];
const defaultSettingsForm = [
'*'
];
@ -594,7 +594,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
public displayAdvanced(): boolean {
return this.modelValue && this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema;
return !!this.modelValue && !!this.modelValue.settingsSchema && !!this.modelValue.settingsSchema.schema;
}
public removeDatasource(index: number) {

7
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -44,7 +44,7 @@ import {
Widget,
WidgetActionDescriptor,
widgetActionSources,
WidgetActionType,
WidgetActionType, WidgetComparisonSettings,
WidgetResource,
widgetType,
WidgetTypeParameters
@ -847,9 +847,12 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
const createSubscriptionSubject = new ReplaySubject();
let options: WidgetSubscriptionOptions;
if (this.widget.type !== widgetType.rpc && this.widget.type !== widgetType.static) {
const comparisonSettings: WidgetComparisonSettings = this.widgetContext.settings;
options = {
type: this.widget.type,
stateData: this.typeParameters.stateData
stateData: this.typeParameters.stateData,
comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison
};
if (this.widget.type === widgetType.alarm) {
options.alarmSource = deepClone(this.widget.config.alarmSource);

6
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -27,7 +27,7 @@ import {
widgetType,
WidgetTypeDescriptor,
WidgetTypeParameters,
Widget
Widget, JsonSettingsSchema
} from '@shared/models/widget.models';
import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models';
import {
@ -243,8 +243,8 @@ export interface WidgetConfigComponentData {
typeParameters: WidgetTypeParameters;
actionSources: {[actionSourceId: string]: WidgetActionSource};
isDataEnabled: boolean;
settingsSchema: any;
dataKeySettingsSchema: any;
settingsSchema: JsonSettingsSchema;
dataKeySettingsSchema: JsonSettingsSchema;
}
export const MissingWidgetType: WidgetInfo = {

7
ui-ngx/src/app/shared/components/json-form/json-form-component.models.ts

@ -15,9 +15,8 @@
///
export interface JsonFormComponentData {
import { JsonSettingsSchema } from '@shared/models/widget.models';
export interface JsonFormComponentData extends JsonSettingsSchema {
model?: any;
schema?: any;
form?: any;
groupInfoes?: any[];
}

3
ui-ngx/src/app/shared/components/json-form/json-form.component.ts

@ -42,6 +42,7 @@ import * as ReactDOM from 'react-dom';
import ReactSchemaForm from './react/json-form-react';
import JsonFormUtils from './react/json-form-utils';
import { JsonFormComponentData } from './json-form-component.models';
import { GroupInfo } from '@shared/models/widget.models';
const tinycolor = tinycolor_;
@ -94,7 +95,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
model: any;
schema: any;
form: any;
groupInfoes: any[];
groupInfoes: GroupInfo[];
isModelValid = true;

3
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -32,10 +32,11 @@ import ThingsboardImage from './json-form-image';
import ThingsboardCheckbox from './json-form-checkbox';
import ThingsboardHelp from './json-form-help';
import ThingsboardFieldSet from './json-form-fieldset';
import { JsonFormProps, GroupInfo, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models';
import { JsonFormProps, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models';
import _ from 'lodash';
import * as tinycolor_ from 'tinycolor2';
import { GroupInfo } from '@shared/models/widget.models';
const tinycolor = tinycolor_;
class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {

6
ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts

@ -19,6 +19,7 @@ import * as equal from 'deep-equal';
import ObjectPath from 'objectpath';
import * as React from 'react';
import * as tinycolor_ from 'tinycolor2';
import { GroupInfo } from '@shared/models/widget.models';
const tinycolor = tinycolor_;
@ -45,11 +46,6 @@ export interface DefaultsFormOptions {
ignore?: {[key: string]: boolean};
}
export interface GroupInfo {
formIndex: number;
GroupTitle: string;
}
export type onChangeFn = (key: (string | number)[], val: any, forceUpdate?: boolean) => void;
export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA,
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void;

28
ui-ngx/src/app/shared/models/time/time.models.ts

@ -16,6 +16,9 @@
import { TimeService } from '@core/services/time.service';
import { deepClone, isDefined, isUndefined } from '@app/core/utils';
import * as moment_ from 'moment';
const moment = moment_;
export const SECOND = 1000;
export const MINUTE = 60 * SECOND;
@ -289,6 +292,31 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
return subscriptionTimewindow;
}
export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow {
const timewindowForComparison: SubscriptionTimewindow = {
fixedWindow: null,
realtimeWindowMs: null,
aggregation: subscriptionTimewindow.aggregation
};
if (subscriptionTimewindow.realtimeWindowMs) {
timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf();
timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;
} else if (subscriptionTimewindow.fixedWindow) {
const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();
timewindowForComparison.startTs = endTimeMs - timeInterval;
timewindowForComparison.fixedWindow = {
startTimeMs: timewindowForComparison.startTs,
endTimeMs
};
}
return timewindowForComparison;
}
export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
const cloned: Timewindow = {};
if (isDefined(timewindow.selectedTab)) {

28
ui-ngx/src/app/shared/models/widget.models.ts

@ -20,9 +20,9 @@ import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { Timewindow } from '@shared/models/time/time.models';
import { EntityType } from '@shared/models/entity-type.models';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { Data } from '@angular/router';
import { DataKeyType } from './telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import * as moment_ from 'moment';
export enum widgetType {
timeseries = 'timeseries',
@ -261,6 +261,7 @@ export interface Datasource {
entityLabel?: string;
entityDescription?: string;
generated?: boolean;
isAdditional?: boolean;
[key: string]: any;
}
@ -331,6 +332,11 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor {
stateEntityParamName?: string;
}
export interface WidgetComparisonSettings {
comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor;
}
export interface WidgetConfig {
title?: string;
titleIcon?: string;
@ -382,14 +388,22 @@ export interface Widget {
config: WidgetConfig;
}
export interface GroupInfo {
formIndex: number;
GroupTitle: string;
}
export interface JsonSchema {
type: string;
title?: string;
properties: {[key: string]: any};
required?: string[];
}
export interface JsonSettingsSchema {
schema?: {
type: string;
title: string;
properties: {[key: string]: any};
required?: string[];
};
schema?: JsonSchema;
form?: any[];
groupInfoes?: GroupInfo[]
}
export interface WidgetPosition {

1
ui-ngx/src/typings/jquery.flot.typings.d.ts

@ -24,6 +24,7 @@ interface JQueryPlot extends jquery.flot.plot {
interface JQueryPlotPoint extends jquery.flot.point {
pageX: number;
pageY: number;
x2: number;
}
interface JQueryPlotDataSeries extends jquery.flot.dataSeries, JQueryPlotSeriesOptions {

Loading…
Cancel
Save