diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 83f75ae302..e612111e3a 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -146,10 +146,12 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "settingsDirective": "tb-flot-line-widget-settings", + "dataKeySettingsDirective": "tb-flot-line-key-settings", + "latestDataKeySettingsDirective": "tb-flot-latest-key-settings", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" } }, @@ -165,11 +167,13 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "latestDataKeySettingsSchema": "{}", "settingsDirective": "tb-flot-line-widget-settings", + "dataKeySettingsDirective": "tb-flot-line-key-settings", + "latestDataKeySettingsDirective": "tb-flot-latest-key-settings", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" } }, @@ -185,10 +189,12 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "settingsDirective": "tb-flot-bar-widget-settings", + "dataKeySettingsDirective": "tb-flot-bar-key-settings", + "latestDataKeySettingsDirective": "tb-flot-latest-key-settings", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 66317bf596..fcc524ad1e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -18,7 +18,6 @@ /// import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models'; -import * as moment_ from 'moment'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { ComparisonDuration } from '@shared/models/time/time.models'; @@ -163,13 +162,15 @@ export interface TbFlotLabelPatternSettings { settings?: any; } -export interface TbFlotGraphSettings extends TbFlotBaseSettings, TbFlotThresholdsSettings, TbFlotComparisonSettings, TbFlotCustomLegendSettings { +export interface TbFlotGraphSettings extends TbFlotBaseSettings, + TbFlotThresholdsSettings, TbFlotComparisonSettings, TbFlotCustomLegendSettings { smoothLines: boolean; } export declare type BarAlignment = 'left' | 'right' | 'center'; -export interface TbFlotBarSettings extends TbFlotBaseSettings, TbFlotThresholdsSettings, TbFlotComparisonSettings, TbFlotCustomLegendSettings { +export interface TbFlotBarSettings extends TbFlotBaseSettings, + TbFlotThresholdsSettings, TbFlotComparisonSettings, TbFlotCustomLegendSettings { defaultBarWidth: number; barAlignment: BarAlignment; } @@ -183,7 +184,7 @@ export interface TbFlotPieSettings { color: string; width: number; }; - showTooltip: boolean, + showTooltip: boolean; showLabels: boolean; fontColor: string; fontSize: number; @@ -241,480 +242,6 @@ export interface TbFlotLatestKeySettings { thresholdColor: string; } -export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema { - - const schema: JsonSettingsSchema = { - schema: { - type: 'object', - title: 'Settings', - properties: { - } - } - }; - - const properties: any = schema.schema.properties; - properties.stack = { - title: 'Stacking', - type: 'boolean', - default: false - }; - if (chartType === 'graph') { - properties.smoothLines = { - title: 'Display smooth (curved) lines', - type: 'boolean', - default: false - }; - } - if (chartType === 'bar') { - properties.defaultBarWidth = { - title: 'Default bar width for non-aggregated data (milliseconds)', - type: 'number', - default: 600 - }; - properties.barAlignment = { - title: 'Bar alignment', - type: 'string', - default: 'left' - }; - } - if (chartType === 'graph' || chartType === 'bar') { - properties.thresholdsLineWidth = { - title: 'Default line width for all thresholds', - type: 'number' - }; - } - properties.shadowSize = { - title: 'Shadow size', - type: 'number', - default: 4 - }; - properties.fontColor = { - title: 'Font color', - type: 'string', - default: '#545454' - }; - properties.fontSize = { - title: 'Font size', - type: 'number', - default: 10 - }; - properties.tooltipIndividual = { - title: 'Hover individual points', - type: 'boolean', - default: false - }; - properties.tooltipCumulative = { - title: 'Show cumulative values in stacking mode', - type: 'boolean', - default: false - }; - properties.tooltipValueFormatter = { - title: 'Tooltip value format function, f(value)', - type: 'string', - default: '' - }; - properties.hideZeros = { - title: 'Hide zero/false values from tooltip', - type: 'boolean', - default: false - }; - - properties.showTooltip = { - title: 'Show tooltip', - type: 'boolean', - default: true - }; - - properties.grid = { - title: 'Grid settings', - type: 'object', - properties: { - color: { - title: 'Primary color', - type: 'string', - default: '#545454' - }, - backgroundColor: { - title: 'Background color', - type: 'string', - default: null - }, - tickColor: { - title: 'Ticks color', - type: 'string', - default: '#DDDDDD' - }, - outlineWidth: { - title: 'Grid outline/border width (px)', - type: 'number', - default: 1 - }, - verticalLines: { - title: 'Show vertical lines', - type: 'boolean', - default: true - }, - horizontalLines: { - title: 'Show horizontal lines', - type: 'boolean', - default: true - } - } - }; - - properties.xaxis = { - title: 'X axis settings', - type: 'object', - properties: { - showLabels: { - title: 'Show labels', - type: 'boolean', - default: true - }, - title: { - title: 'Axis title', - type: 'string', - default: null - }, - color: { - title: 'Ticks color', - type: 'string', - default: null - } - } - }; - - properties.yaxis = { - title: 'Y axis settings', - type: 'object', - properties: { - min: { - title: 'Minimum value on the scale', - type: 'number', - default: null - }, - max: { - title: 'Maximum value on the scale', - type: 'number', - default: null - }, - showLabels: { - title: 'Show labels', - type: 'boolean', - default: true - }, - title: { - title: 'Axis title', - type: 'string', - default: null - }, - color: { - title: 'Ticks color', - type: 'string', - default: null - }, - ticksFormatter: { - title: 'Ticks formatter function, f(value)', - type: 'string', - default: '' - }, - tickDecimals: { - title: 'The number of decimals to display', - type: 'number', - default: 0 - }, - tickSize: { - title: 'Step size between ticks', - type: 'number', - default: null - } - } - }; - - schema.schema.required = []; - schema.form = ['stack']; - if (chartType === 'graph') { - schema.form.push('smoothLines'); - } - if (chartType === 'bar') { - schema.form.push('defaultBarWidth'); - schema.form.push({ - key: 'barAlignment', - type: 'rc-select', - multiple: false, - items: [ - { - value: 'left', - label: 'Left' - }, - { - value: 'right', - label: 'Right' - }, - { - value: 'center', - label: 'Center' - } - ] - }); - } - if (chartType === 'graph' || chartType === 'bar') { - schema.form.push('thresholdsLineWidth'); - } - schema.form.push('shadowSize'); - schema.form.push({ - key: 'fontColor', - type: 'color' - }); - schema.form.push('fontSize'); - schema.form.push('tooltipIndividual'); - schema.form.push('tooltipCumulative'); - schema.form.push({ - key: 'tooltipValueFormatter', - type: 'javascript', - helpId: 'widget/lib/flot/tooltip_value_format_fn' - }); - schema.form.push('hideZeros'); - schema.form.push('showTooltip'); - schema.form.push({ - key: 'grid', - items: [ - { - key: 'grid.color', - type: 'color' - }, - { - key: 'grid.backgroundColor', - type: 'color' - }, - { - key: 'grid.tickColor', - type: 'color' - }, - 'grid.outlineWidth', - 'grid.verticalLines', - 'grid.horizontalLines' - ] - }); - schema.form.push({ - key: 'xaxis', - items: [ - 'xaxis.showLabels', - 'xaxis.title', - { - key: 'xaxis.color', - type: 'color' - } - ] - }); - schema.form.push({ - key: 'yaxis', - items: [ - 'yaxis.min', - 'yaxis.max', - 'yaxis.tickDecimals', - 'yaxis.tickSize', - 'yaxis.showLabels', - 'yaxis.title', - { - key: 'yaxis.color', - type: 'color' - }, - { - key: 'yaxis.ticksFormatter', - type: 'javascript', - helpId: 'widget/lib/flot/ticks_formatter_fn' - } - ] - }); - 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, ...chartSettingsSchemaForCustomLegend.schema.properties}; - schema.schema.required = schema.schema.required.concat(chartSettingsSchemaForComparison.schema.required, chartSettingsSchemaForCustomLegend.schema.required); - schema.form.push(chartSettingsSchemaForComparison.form, chartSettingsSchemaForCustomLegend.form); - schema.groupInfoes.push({ - formIndex: schema.groupInfoes.length, - GroupTitle: 'Comparison Settings' - }); - schema.groupInfoes.push({ - formIndex: schema.groupInfoes.length, - GroupTitle: 'Custom Legend 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: 'previousInterval' - }, - comparisonCustomIntervalValue: { - title: 'Custom interval value (ms)', - type: 'number', - default: 7200000 - }, - 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: 'previousInterval', - label: 'Previous interval (default)' - }, - { - value: 'days', - label: 'Day ago' - }, - { - value: 'weeks', - label: 'Week ago' - }, - { - value: 'months', - label: 'Month ago' - }, - { - value: 'years', - label: 'Year ago' - }, - { - value: 'customInterval', - label: 'Custom interval' - } - ] - }, - { - key: 'comparisonCustomIntervalValue', - condition: 'model.timeForComparison === "customInterval"' - }, - { - 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', - ] - } - ] -}; - -const chartSettingsSchemaForCustomLegend: JsonSettingsSchema = { - schema: { - title: 'Custom Legend Settings', - type: 'object', - properties: { - customLegendEnabled: { - title: 'Enable custom legend (this will allow you to use attribute/timeseries values in key labels)', - type: 'boolean', - default: false - }, - dataKeysListForLabels: { - title: 'Datakeys list to use in labels', - type: 'array', - items: { - type: 'object', - properties: { - name: { - title: 'Key name', - type: 'string' - }, - type: { - title: 'Key type', - type: 'string', - default: 'attribute' - } - }, - required: [ - 'name' - ] - } - } - }, - required: [] - }, - form: [ - 'customLegendEnabled', - { - key: 'dataKeysListForLabels', - condition: 'model.customLegendEnabled === true', - items: [ - { - key: 'dataKeysListForLabels[].type', - type: 'rc-select', - multiple: false, - items: [ - { - value: 'attribute', - label: 'Attribute' - }, - { - value: 'timeseries', - label: 'Timeseries' - } - ] - }, - 'dataKeysListForLabels[].name' - ] - } - ] -}; - export const flotPieSettingsSchema: JsonSettingsSchema = { schema: { type: 'object', @@ -833,334 +360,3 @@ export const flotPieDatakeySettingsSchema: JsonSettingsSchema = { 'removeFromLegend' ] }; - -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 - }, - hideDataByDefault: { - title: 'Data is hidden by default', - type: 'boolean', - default: false - }, - disableDataHiding: { - title: 'Disable data hiding', - type: 'boolean', - default: false - }, - removeFromLegend: { - title: 'Remove datakey from legend', - type: 'boolean', - default: false - }, - showLines: { - title: 'Show lines', - type: 'boolean', - default: defaultShowLines - }, - fillLines: { - title: 'Fill lines', - type: 'boolean', - default: false - }, - showPoints: { - title: 'Show points', - type: 'boolean', - default: false - }, - 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: 5 - }, - showPointsRadius: { - title: 'Radius of points', - type: 'number', - default: 3 - }, - tooltipValueFormatter: { - title: 'Tooltip value format function, f(value)', - type: 'string', - default: '' - }, - showSeparateAxis: { - title: 'Show separate axis', - type: 'boolean', - default: false - }, - axisMin: { - title: 'Minimum value on the axis scale', - type: 'number', - default: null - }, - axisMax: { - title: 'Maximum value on the axis scale', - type: 'number', - default: null - }, - axisTitle: { - title: 'Axis title', - type: 'string', - default: '' - }, - axisTickDecimals: { - title: 'Axis tick number of digits after floating point', - type: 'number', - default: null - }, - axisTickSize: { - title: 'Axis step size between ticks', - type: 'number', - default: null - }, - axisPosition: { - title: 'Axis position', - type: 'string', - default: 'left' - }, - axisTicksFormatter: { - title: 'Ticks formatter function, f(value)', - type: 'string', - default: '' - } - }, - required: ['showLines', 'fillLines', 'showPoints'] - }, - form: [ - 'hideDataByDefault', - 'disableDataHiding', - 'removeFromLegend', - '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', - helpId: 'widget/lib/flot/point_shape_format_fn' - }, - 'showPointsLineWidth', - 'showPointsRadius', - { - key: 'tooltipValueFormatter', - type: 'javascript', - helpId: 'widget/lib/flot/tooltip_value_format_fn' - }, - 'showSeparateAxis', - 'axisMin', - 'axisMax', - 'axisTitle', - 'axisTickDecimals', - 'axisTickSize', - { - key: 'axisPosition', - type: 'rc-select', - multiple: false, - items: [ - { - value: 'left', - label: 'Left' - }, - { - value: 'right', - label: 'Right' - } - ] - }, - { - key: 'axisTicksFormatter', - type: 'javascript', - helpId: 'widget/lib/flot/ticks_formatter_fn' - } - ] - }; - - const properties = schema.schema.properties; - if (chartType === 'graph' || chartType === 'bar') { - properties.thresholds = { - title: 'Thresholds', - type: 'array', - items: { - title: 'Threshold', - type: 'object', - properties: { - thresholdValueSource: { - title: 'Threshold value source', - type: 'string', - default: 'predefinedValue' - }, - thresholdEntityAlias: { - title: 'Thresholds source entity alias', - type: 'string' - }, - thresholdAttribute: { - title: 'Threshold source entity attribute', - type: 'string' - }, - thresholdValue: { - title: 'Threshold value (if predefined value is selected)', - type: 'number' - }, - lineWidth: { - title: 'Line width', - type: 'number' - }, - color: { - title: 'Color', - type: 'string' - } - } - }, - required: [] - }; - schema.form.push({ - key: 'thresholds', - items: [ - { - key: 'thresholds[].thresholdValueSource', - type: 'rc-select', - multiple: false, - items: [ - { - value: 'predefinedValue', - label: 'Predefined value (Default)' - }, - { - value: 'entityAttribute', - label: 'Value taken from entity attribute' - } - ] - }, - 'thresholds[].thresholdValue', - 'thresholds[].thresholdEntityAlias', - 'thresholds[].thresholdAttribute', - { - key: 'thresholds[].color', - type: 'color' - }, - 'thresholds[].lineWidth' - ] - }); - 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; -} - -export const flotLatestDatakeySettingsSchema: JsonSettingsSchema = { - schema: { - type: 'object', - title: 'LatestDataKeySettings', - properties: { - useAsThreshold: { - title: 'Use key value as threshold', - type: 'boolean', - default: false - }, - thresholdLineWidth: { - title: 'Threshold line width', - type: 'number' - }, - thresholdColor: { - title: 'Threshold color', - type: 'string' - } - } - }, - form: [ - 'useAsThreshold', - { - key: 'thresholdLineWidth', - condition: 'model.useAsThreshold === true' - }, - { - key: 'thresholdColor', - type: 'color', - condition: 'model.useAsThreshold === true' - }, - ] -}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 736cc30450..2ed20870dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -38,10 +38,8 @@ import { } from '@app/shared/models/widget.models'; import { ChartType, - flotDatakeySettingsSchema, flotLatestDatakeySettingsSchema, flotPieDatakeySettingsSchema, flotPieSettingsSchema, - flotSettingsSchema, TbFlotAxisOptions, TbFlotHoverInfo, TbFlotKeySettings, TbFlotLatestKeySettings, @@ -69,7 +67,6 @@ const moment = moment_; const flotPieSettingsSchemaValue = flotPieSettingsSchema; const flotPieDatakeySettingsSchemaValue = flotPieDatakeySettingsSchema; -const latestDatakeySettingsSchemaValue = flotLatestDatakeySettingsSchema; export class TbFlot { @@ -141,18 +138,6 @@ export class TbFlot { return flotPieDatakeySettingsSchemaValue; } - static settingsSchema(chartType: ChartType): JsonSettingsSchema { - return flotSettingsSchema(chartType); - } - - static datakeySettingsSchema(defaultShowLines: boolean, chartType: ChartType): JsonSettingsSchema { - return flotDatakeySettingsSchema(defaultShowLines, chartType); - } - - static latestDatakeySettingsSchema(): JsonSettingsSchema { - return latestDatakeySettingsSchemaValue; - } - constructor(private ctx: WidgetContext, private readonly chartType: ChartType) { this.chartType = this.chartType || 'line'; this.settings = ctx.settings as TbFlotSettings; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.html new file mode 100644 index 0000000000..f96f2ddbff --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.html @@ -0,0 +1,24 @@ + + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts new file mode 100644 index 0000000000..1dd9c73dd0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-bar-key-settings.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { flotDataKeyDefaultSettings } from '@home/components/widget/lib/settings/chart/flot-key-settings.component'; + +@Component({ + selector: 'tb-flot-bar-key-settings', + templateUrl: './flot-bar-key-settings.component.html', + styleUrls: [] +}) +export class FlotBarKeySettingsComponent extends WidgetSettingsComponent { + + flotBarKeySettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.flotBarKeySettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return flotDataKeyDefaultSettings('bar'); + } + + protected onSettingsSet(settings: WidgetSettings) { + this.flotBarKeySettingsForm = this.fb.group({ + flotKeySettings: [settings, []] + }); + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + return settings.flotKeySettings; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html new file mode 100644 index 0000000000..81ebfc7c6f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.html @@ -0,0 +1,238 @@ + +
+
+ widgets.chart.common-settings + + {{ 'widgets.chart.data-is-hidden-by-default' | translate }} + + + {{ 'widgets.chart.disable-data-hiding' | translate }} + + + {{ 'widgets.chart.remove-from-legend' | translate }} + + + {{ 'widgets.chart.exclude-from-stacking' | translate }} + +
+
+ widgets.chart.line-settings + + + + + {{ 'widgets.chart.show-line' | translate }} + + + + widget-config.advanced-settings + + + +
+ + widgets.chart.line-width + + + + {{ 'widgets.chart.fill-line' | translate }} + +
+
+
+
+
+ widgets.chart.points-settings + + + + + {{ 'widgets.chart.show-points' | translate }} + + + + widget-config.advanced-settings + + + +
+
+ + widgets.chart.points-line-width + + + + widgets.chart.points-radius + + +
+ + widgets.chart.point-shape + + + {{ 'widgets.chart.point-shape-circle' | translate }} + + + {{ 'widgets.chart.point-shape-cross' | translate }} + + + {{ 'widgets.chart.point-shape-diamond' | translate }} + + + {{ 'widgets.chart.point-shape-square' | translate }} + + + {{ 'widgets.chart.point-shape-triangle' | translate }} + + + {{ 'widgets.chart.point-shape-custom' | translate }} + + + + + +
+
+
+
+
+ widgets.chart.tooltip-settings + + +
+
+ widgets.chart.yaxis-settings + + {{ 'widgets.chart.show-separate-axis' | translate }} + + + widgets.chart.axis-title + + +
+ + widgets.chart.min-scale-value + + + + widgets.chart.max-scale-value + + +
+ + widgets.chart.axis-position + + + {{ 'widgets.chart.axis-position-left' | translate }} + + + {{ 'widgets.chart.axis-position-right' | translate }} + + + +
+ widgets.chart.yaxis-tick-labels-settings +
+ + widgets.chart.tick-step-size + + + + widgets.chart.number-of-decimals + + +
+ + +
+
+
+ widgets.chart.thresholds +
+
+
+ + +
+
+
+ widgets.chart.no-thresholds +
+
+ +
+
+
+
+ widgets.chart.comparison-settings + + + + + {{ 'widgets.chart.show-values-for-comparison' | translate }} + + + + widget-config.advanced-settings + + + +
+ + widgets.chart.comparison-values-label + + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.ts new file mode 100644 index 0000000000..a97ccc84a0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-key-settings.component.ts @@ -0,0 +1,333 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormControl, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { ChartType, TbFlotKeySettings, TbFlotKeyThreshold } from '@home/components/widget/lib/flot-widget.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { WidgetService } from '@core/http/widget.service'; +import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { IAliasController } from 'src/app/core/api/widget-api.models'; + +export function flotDataKeyDefaultSettings(chartType: ChartType): TbFlotKeySettings { + const settings: TbFlotKeySettings = { + // Common settings + hideDataByDefault: false, + disableDataHiding: false, + removeFromLegend: false, + excludeFromStacking: false, + + // Line settings + showLines: chartType === 'graph', + lineWidth: 1, + fillLines: false, + + // Points settings + showPoints: false, + showPointsLineWidth: 5, + showPointsRadius: 3, + showPointShape: 'circle', + pointShapeFormatter: '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);', + + // Tooltip settings + tooltipValueFormatter: '', + + // Y axis settings + showSeparateAxis: false, + axisTitle: '', + axisMin: null, + axisMax: null, + axisPosition: 'left', + + // --> Y axis tick labels settings + axisTickSize: null, + axisTickDecimals: null, + axisTicksFormatter: '', + + // Thresholds + thresholds: [], + + // Comparison settings + comparisonSettings: { + showValuesForComparison: true, + comparisonValuesLabel: '', + color: '' + } + }; + return settings; +} + +@Component({ + selector: 'tb-flot-key-settings', + templateUrl: './flot-key-settings.component.html', + styleUrls: ['./../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FlotKeySettingsComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => FlotKeySettingsComponent), + multi: true, + } + ] +}) +export class FlotKeySettingsComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator { + + @Input() + disabled: boolean; + + @Input() + chartType: ChartType; + + @Input() + aliasController: IAliasController; + + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + + private modelValue: TbFlotKeySettings; + + private propagateChange = null; + + public flotKeySettingsFormGroup: FormGroup; + + constructor(protected store: Store, + private translate: TranslateService, + private widgetService: WidgetService, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.flotKeySettingsFormGroup = this.fb.group({ + + // Common settings + + hideDataByDefault: [false, []], + disableDataHiding: [false, []], + removeFromLegend: [false, []], + excludeFromStacking: [false, []], + + // Line settings + + showLines: [this.chartType === 'graph', []], + lineWidth: [1, [Validators.min(0)]], + fillLines: [false, []], + + // Points settings + + showPoints: [false, []], + showPointsLineWidth: [5, [Validators.min(0)]], + showPointsRadius: [3, [Validators.min(0)]], + showPointShape: ['circle', []], + pointShapeFormatter: ['', []], + + // Tooltip settings + + tooltipValueFormatter: ['', []], + + // Y axis settings + + showSeparateAxis: [false, []], + axisTitle: [null, []], + axisMin: [null, []], + axisMax: [null, []], + axisPosition: ['left', []], + + // --> Y axis tick labels settings + + axisTickSize: [null, [Validators.min(0)]], + axisTickDecimals: [null, [Validators.min(0)]], + axisTicksFormatter: ['', []], + + // Thresholds + + thresholds: this.fb.array([]), + + // Comparison settings + + comparisonSettings: this.fb.group({ + showValuesForComparison: [true, []], + comparisonValuesLabel: ['', []], + color: ['', []] + }) + + }); + + this.flotKeySettingsFormGroup.get('showLines').valueChanges.subscribe(() => { + this.updateValidators(true); + }); + + this.flotKeySettingsFormGroup.get('showPoints').valueChanges.subscribe(() => { + this.updateValidators(true); + }); + + this.flotKeySettingsFormGroup.get('comparisonSettings.showValuesForComparison').valueChanges.subscribe(() => { + this.updateValidators(true); + }); + + this.flotKeySettingsFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + + this.updateValidators(false); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.flotKeySettingsFormGroup.disable({emitEvent: false}); + } else { + this.flotKeySettingsFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TbFlotKeySettings): void { + const thresholds = value?.thresholds; + this.modelValue = value; + this.flotKeySettingsFormGroup.patchValue( + value, {emitEvent: false} + ); + const thresholdsControls: Array = []; + if (thresholds && thresholds.length) { + thresholds.forEach((threshold) => { + thresholdsControls.push(this.fb.control(threshold, [])); + }); + } + this.flotKeySettingsFormGroup.setControl('thresholds', this.fb.array(thresholdsControls), {emitEvent: false}); + this.updateValidators(false); + } + + validate(c: FormControl) { + return (this.flotKeySettingsFormGroup.valid) ? null : { + flotKeySettings: { + valid: false, + }, + }; + } + + private updateModel() { + const value: TbFlotKeySettings = this.flotKeySettingsFormGroup.value; + this.modelValue = value; + this.propagateChange(this.modelValue); + } + + private updateValidators(emitEvent?: boolean): void { + const showLines: boolean = this.flotKeySettingsFormGroup.get('showLines').value; + const showPoints: boolean = this.flotKeySettingsFormGroup.get('showPoints').value; + const showValuesForComparison: boolean = this.flotKeySettingsFormGroup.get('comparisonSettings.showValuesForComparison').value; + + if (showLines) { + this.flotKeySettingsFormGroup.get('lineWidth').enable({emitEvent}); + this.flotKeySettingsFormGroup.get('fillLines').enable({emitEvent}); + } else { + this.flotKeySettingsFormGroup.get('lineWidth').disable({emitEvent}); + this.flotKeySettingsFormGroup.get('fillLines').disable({emitEvent}); + } + + if (showPoints) { + this.flotKeySettingsFormGroup.get('showPointsLineWidth').enable({emitEvent}); + this.flotKeySettingsFormGroup.get('showPointsRadius').enable({emitEvent}); + this.flotKeySettingsFormGroup.get('showPointShape').enable({emitEvent}); + this.flotKeySettingsFormGroup.get('pointShapeFormatter').enable({emitEvent}); + } else { + this.flotKeySettingsFormGroup.get('showPointsLineWidth').disable({emitEvent}); + this.flotKeySettingsFormGroup.get('showPointsRadius').disable({emitEvent}); + this.flotKeySettingsFormGroup.get('showPointShape').disable({emitEvent}); + this.flotKeySettingsFormGroup.get('pointShapeFormatter').disable({emitEvent}); + } + + if (showValuesForComparison) { + this.flotKeySettingsFormGroup.get('comparisonSettings.comparisonValuesLabel').enable({emitEvent}); + this.flotKeySettingsFormGroup.get('comparisonSettings.color').enable({emitEvent}); + } else { + this.flotKeySettingsFormGroup.get('comparisonSettings.comparisonValuesLabel').disable({emitEvent}); + this.flotKeySettingsFormGroup.get('comparisonSettings.color').disable({emitEvent}); + } + + this.flotKeySettingsFormGroup.get('lineWidth').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('fillLines').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('showPointsLineWidth').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('showPointsRadius').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('showPointShape').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('pointShapeFormatter').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('comparisonSettings.comparisonValuesLabel').updateValueAndValidity({emitEvent: false}); + this.flotKeySettingsFormGroup.get('comparisonSettings.color').updateValueAndValidity({emitEvent: false}); + } + + thresholdsFormArray(): FormArray { + return this.flotKeySettingsFormGroup.get('thresholds') as FormArray; + } + + public trackByThreshold(index: number, thresholdControl: AbstractControl): any { + return thresholdControl; + } + + public removeThreshold(index: number) { + (this.flotKeySettingsFormGroup.get('thresholds') as FormArray).removeAt(index); + } + + public addThreshold() { + const threshold: TbFlotKeyThreshold = { + thresholdValueSource: 'predefinedValue', + thresholdEntityAlias: null, + thresholdAttribute: null, + thresholdValue: null, + lineWidth: null, + color: null + }; + const thresholdsArray = this.flotKeySettingsFormGroup.get('thresholds') as FormArray; + const thresholdControl = this.fb.control(threshold, []); + (thresholdControl as any).new = true; + thresholdsArray.push(thresholdControl); + this.flotKeySettingsFormGroup.updateValueAndValidity(); + } + + thresholdDrop(event: CdkDragDrop) { + const thresholdsArray = this.flotKeySettingsFormGroup.get('thresholds') as FormArray; + const threshold = thresholdsArray.at(event.previousIndex); + thresholdsArray.removeAt(event.previousIndex); + thresholdsArray.insert(event.currentIndex, threshold); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html new file mode 100644 index 0000000000..0e210366d5 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.html @@ -0,0 +1,47 @@ + +
+
+ widgets.chart.threshold-settings + + + + + {{ 'widgets.chart.use-as-threshold' | translate }} + + + + widget-config.advanced-settings + + + +
+ + widgets.chart.threshold-line-width + + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.ts new file mode 100644 index 0000000000..e3b4b5acf1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-latest-key-settings.component.ts @@ -0,0 +1,74 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Component({ + selector: 'tb-flot-latest-key-settings', + templateUrl: './flot-latest-key-settings.component.html', + styleUrls: ['./../widget-settings.scss'] +}) +export class FlotLatestKeySettingsComponent extends WidgetSettingsComponent { + + flotLatestKeySettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.flotLatestKeySettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return { + useAsThreshold: false, + thresholdLineWidth: null, + thresholdColor: null + }; + } + + protected onSettingsSet(settings: WidgetSettings) { + this.flotLatestKeySettingsForm = this.fb.group({ + useAsThreshold: [settings.useAsThreshold, []], + thresholdLineWidth: [settings.thresholdLineWidth, [Validators.min(0)]], + thresholdColor: [settings.thresholdColor, []] + }); + } + + protected validatorTriggers(): string[] { + return ['useAsThreshold']; + } + + protected updateValidators(emitEvent: boolean) { + const useAsThreshold: boolean = this.flotLatestKeySettingsForm.get('useAsThreshold').value; + if (useAsThreshold) { + this.flotLatestKeySettingsForm.get('thresholdLineWidth').enable(); + this.flotLatestKeySettingsForm.get('thresholdColor').enable(); + } else { + this.flotLatestKeySettingsForm.get('thresholdLineWidth').disable(); + this.flotLatestKeySettingsForm.get('thresholdColor').disable(); + } + this.flotLatestKeySettingsForm.get('thresholdLineWidth').updateValueAndValidity({emitEvent}); + this.flotLatestKeySettingsForm.get('thresholdColor').updateValueAndValidity({emitEvent}); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.html new file mode 100644 index 0000000000..fbc179599f --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.html @@ -0,0 +1,24 @@ + + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts new file mode 100644 index 0000000000..46b83715fe --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-line-key-settings.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component } from '@angular/core'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { flotDataKeyDefaultSettings } from '@home/components/widget/lib/settings/chart/flot-key-settings.component'; + +@Component({ + selector: 'tb-flot-line-key-settings', + templateUrl: './flot-line-key-settings.component.html', + styleUrls: [] +}) +export class FlotLineKeySettingsComponent extends WidgetSettingsComponent { + + flotLineKeySettingsForm: FormGroup; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + } + + protected settingsForm(): FormGroup { + return this.flotLineKeySettingsForm; + } + + protected defaultSettings(): WidgetSettings { + return flotDataKeyDefaultSettings('graph'); + } + + protected onSettingsSet(settings: WidgetSettings) { + this.flotLineKeySettingsForm = this.fb.group({ + flotKeySettings: [settings, []] + }); + } + + protected prepareOutputSettings(settings: any): WidgetSettings { + return settings.flotKeySettings; + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html new file mode 100644 index 0000000000..9fdf98fec7 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.html @@ -0,0 +1,57 @@ + + + +
+ +
+
{{ thresholdText() }}
+
+
+
+
+
+ + +
+
+ +
+ +
+ +
+ + widgets.chart.line-width + + + + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss new file mode 100644 index 0000000000..06d9988e53 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + display: block; + .mat-expansion-panel { + box-shadow: none; + &.flot-threshold { + border: 1px groove rgba(0, 0, 0, .25); + .mat-expansion-panel-header { + padding: 0 24px 0 8px; + &.mat-expanded { + height: 48px; + } + } + } + } +} + +:host ::ng-deep { + .mat-expansion-panel { + &.flot-threshold { + .mat-expansion-panel-body { + padding: 0 8px 8px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts new file mode 100644 index 0000000000..61f3a16176 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-threshold.component.ts @@ -0,0 +1,136 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ValueSourceProperty } from '@home/components/widget/lib/settings/common/value-source.component'; +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { isNumber } from '@core/utils'; +import { IAliasController } from '@core/api/widget-api.models'; +import { TbFlotKeyThreshold } from '@home/components/widget/lib/flot-widget.models'; + +@Component({ + selector: 'tb-flot-threshold', + templateUrl: './flot-threshold.component.html', + styleUrls: ['./flot-threshold.component.scss', './../widget-settings.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FlotThresholdComponent), + multi: true + } + ] +}) +export class FlotThresholdComponent extends PageComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + expanded = false; + + @Input() + aliasController: IAliasController; + + @Output() + removeThreshold = new EventEmitter(); + + private modelValue: TbFlotKeyThreshold; + + private propagateChange = null; + + public thresholdFormGroup: FormGroup; + + constructor(protected store: Store, + private translate: TranslateService, + private fb: FormBuilder) { + super(store); + } + + ngOnInit(): void { + this.thresholdFormGroup = this.fb.group({ + valueSource: [null, []], + lineWidth: [null, [Validators.min(0)]], + color: [null, []] + }); + this.thresholdFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.thresholdFormGroup.disable({emitEvent: false}); + } else { + this.thresholdFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TbFlotKeyThreshold): void { + this.modelValue = value; + const valueSource: ValueSourceProperty = { + valueSource: value?.thresholdValueSource, + entityAlias: value?.thresholdEntityAlias, + attribute: value?.thresholdAttribute, + value: value?.thresholdValue + }; + this.thresholdFormGroup.patchValue( + {valueSource, lineWidth: value?.lineWidth, color: value?.color}, {emitEvent: false} + ); + } + + thresholdText(): string { + const value: ValueSourceProperty = this.thresholdFormGroup.get('valueSource').value; + return this.valueSourcePropertyText(value); + } + + private valueSourcePropertyText(source?: ValueSourceProperty): string { + if (source) { + if (source.valueSource === 'predefinedValue') { + return `${isNumber(source.value) ? source.value : 0}`; + } else if (source.valueSource === 'entityAttribute') { + const alias = source.entityAlias || 'Undefined'; + const key = source.attribute || 'Undefined'; + return `${alias}.${key}`; + } + } + return 'Undefined'; + } + + private updateModel() { + const value: {valueSource: ValueSourceProperty, lineWidth: number, color: string} = this.thresholdFormGroup.value; + this.modelValue = { + thresholdValueSource: value?.valueSource?.valueSource, + thresholdEntityAlias: value?.valueSource?.entityAlias, + thresholdAttribute: value?.valueSource?.attribute, + thresholdValue: value?.valueSource?.value, + lineWidth: value?.lineWidth, + color: value?.color + }; + this.propagateChange(this.modelValue); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts index 8accd374a6..8d00d55b63 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts @@ -34,7 +34,6 @@ import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { ComparisonDuration } from '@shared/models/time/time.models'; import { WidgetService } from '@core/http/widget.service'; -import { fixedColorLevelValidator } from '@home/components/widget/lib/settings/gauge/fixed-color-level.component'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { LabelDataKey, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 8ee227e253..c6eb6ce5fe 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -83,6 +83,17 @@ import { LabelDataKeyComponent } from '@home/components/widget/lib/settings/char import { FlotBarWidgetSettingsComponent } from '@home/components/widget/lib/settings/chart/flot-bar-widget-settings.component'; +import { FlotThresholdComponent } from '@home/components/widget/lib/settings/chart/flot-threshold.component'; +import { FlotKeySettingsComponent } from '@home/components/widget/lib/settings/chart/flot-key-settings.component'; +import { + FlotLineKeySettingsComponent +} from '@home/components/widget/lib/settings/chart/flot-line-key-settings.component'; +import { + FlotBarKeySettingsComponent +} from '@home/components/widget/lib/settings/chart/flot-bar-key-settings.component'; +import { + FlotLatestKeySettingsComponent +} from '@home/components/widget/lib/settings/chart/flot-latest-key-settings.component'; @NgModule({ declarations: [ @@ -113,7 +124,12 @@ import { FlotWidgetSettingsComponent, LabelDataKeyComponent, FlotLineWidgetSettingsComponent, - FlotBarWidgetSettingsComponent + FlotBarWidgetSettingsComponent, + FlotThresholdComponent, + FlotKeySettingsComponent, + FlotLineKeySettingsComponent, + FlotBarKeySettingsComponent, + FlotLatestKeySettingsComponent ], imports: [ CommonModule, @@ -148,7 +164,12 @@ import { FlotWidgetSettingsComponent, LabelDataKeyComponent, FlotLineWidgetSettingsComponent, - FlotBarWidgetSettingsComponent + FlotBarWidgetSettingsComponent, + FlotThresholdComponent, + FlotKeySettingsComponent, + FlotLineKeySettingsComponent, + FlotBarKeySettingsComponent, + FlotLatestKeySettingsComponent ] }) export class WidgetSettingsModule { @@ -174,5 +195,8 @@ export const widgetSettingsComponentsMap: {[key: string]: Type