widgets/implementations/datapoints-graph/datapoints-graph-view/datapoints-graph-widget-view.component.ts
| providers |
ChartEventsService
ChartAlarmsService
WidgetTimeContextDateRangeService
|
||||||||||||||
| selector | c8y-datapoints-graph-widget-view |
||||||||||||||
| standalone | true |
||||||||||||||
| imports |
A11yModule
TooltipModule
BsDropdownModule
PopoverModule
|
||||||||||||||
| templateVariables |
|
||||||||||||||
| templateUrl | ./datapoints-graph-widget-view.component.html |
No results matching.
A11yModule
CommonModule
ChartsComponent
CoreModule
TooltipModule
BsDropdownModule
PopoverModule
AlarmsModule
GlobalContextConnectorComponent
LocalControlsComponent
DatapointsExportSelectorComponent
SelectComponent
SelectItemDirective
SelectedItemsDirective
WidgetActionWrapperComponent
ChartEventsService
ChartAlarmsService
WidgetTimeContextDateRangeService
OnDestroy
@if (displayMode() === GLOBAL_CONTEXT_DISPLAY_MODE.DASHBOARD) {
<c8y-global-context-connector
[controls]="widgetControls()"
[config]="contextConfig()"
[isLoading]="isLoading$ | async"
[dashboardChild]="getDashboardChild()"
[linked]="isLinkedToGlobal()"
[propagateAutoRefresh]="false"
(configChange)="onContextChange($event)"
></c8y-global-context-connector>
} @else {
<c8y-local-controls
[controls]="widgetControls()"
[displayMode]="displayMode()"
[config]="contextConfig()"
[isLoading]="isLoading$ | async"
[propagateAutoRefresh]="false"
(configChange)="onContextChange($event)"
></c8y-local-controls>
}
<div
class="p-l-16 p-r-16"
style="min-height: 34px"
>
<div class="d-flex gap-16 a-i-center">
@if (hasAtLeastOneAlarmActive) {
<c8y-alarms-filter
class="d-contents form-group-sm min-width-0"
(onFilterApplied)="filterSeverity($event)"
></c8y-alarms-filter>
}
@if (displayConfig?.datapoints.length > 0) {
<div class="d-flex a-i-center min-width-0">
@if (
displayConfig.dataPointLegendDisplay === 'dropdown' ||
(displayConfig.dataPointLegendDisplay === 'auto' && totalLegendItems > 5)
) {
<c8y-select
class="min-width-0 c8y-select-v2--sm"
aria-label="{{ 'Select data points, alarms or events' | translate }}"
[placeholder]="'Select data points, alarms or events' | translate"
[multi]="true"
[filterItems]="true"
[(ngModel)]="selectedItems"
(onSelect)="onItemSelected($event)"
(onDeselect)="onItemDeselected($event)"
>
@for (item of selectableItems; track item.value) {
@let isDuplicate = isDuplicateDropdownItem(item);
<div
class="d-flex a-i-center gap-4"
[style.opacity]="isDuplicate ? '0.5' : null"
*c8ySelectItem="item.value; label: item.label"
>
@if (
!hasAtLeastOneDatapointActive &&
item.value.type === 'DATAPOINT' &&
(!item.value.renderTypeFn
? item.value.original.__active
: isRenderTypeFunctionVisible(
$any(item.value.original),
item.value.renderTypeFn
))
) {
<i
class="text-warning m-r-4"
c8yIcon="exclamation-triangle"
[tooltip]="'At least 1 data point must be active.' | translate"
container="body"
data-cy="datapoint-warning-icon"
[adaptivePosition]="false"
></i>
}
@if (isDuplicate) {
<i
class="text-warning m-r-4"
c8yIcon="warning2"
[tooltip]="duplicateAggrWarning"
container="body"
[adaptivePosition]="false"
></i>
}
@if (item.value.type === 'DATAPOINT') {
@let fnColor =
item.value.renderTypeFn
? $any(item.value.original).renderTypeColors?.[item.value.renderTypeFn] ||
item.value.original.color
: item.value.original.color;
<span
class="circle-icon-wrapper a-s-start circle-icon-wrapper--small"
[style.background-color]="fnColor"
></span>
}
@if (item.value.type === 'ALARM') {
<span
class="circle-icon-wrapper a-s-start circle-icon-wrapper--small"
[style.background-color]="item.value.original.color"
>
<i
class="stroked-icon"
c8yIcon="bell"
></i>
</span>
}
@if (item.value.type === 'EVENT') {
<span
class="circle-icon-wrapper a-s-start circle-icon-wrapper--small"
[ngStyle]="{ 'background-color': item.value.original.color }"
>
<i
class="stroked-icon"
c8yIcon="online1"
></i>
</span>
}
<span class="text-truncate d-col">
<small class="text-truncate">{{ item.label }}</small>
<span class="text-muted text-10">{{ item.value.original.__target?.name }}</span>
</span>
</div>
}
<span
class="tag tag--info chip"
[style.opacity]="isDuplicateDropdownItem(selectedItem) ? '0.6' : null"
title="{{ selectedItem.label }}"
*c8ySelectedItems="let selectedItem"
>
@let isDuplicateChip = isDuplicateDropdownItem(selectedItem);
<button
class="btn btn-xs btn-clean text-10"
title="{{ selectedItem.label }}"
type="button"
(click)="
$event.preventDefault(); $event.stopPropagation(); onItemDeselected(selectedItem)
"
>
<i c8yIcon="times"></i>
</button>
@if (isLastActiveDatapoint(selectedItem)) {
<i
class="text-warning a-s-start"
c8yIcon="exclamation-triangle"
[tooltip]="'At least 1 data point must be active.' | translate"
container="body"
data-cy="datapoint-warning-icon"
[adaptivePosition]="false"
></i>
}
@if (isDuplicateChip) {
<i
class="text-warning a-s-start"
c8yIcon="warning2"
[tooltip]="duplicateAggrWarning"
container="body"
[adaptivePosition]="false"
></i>
}
@if (selectedItem.value.type === 'DATAPOINT') {
@let fnColor =
selectedItem.value.renderTypeFn
? $any(selectedItem.value.original).renderTypeColors?.[
selectedItem.value.renderTypeFn
] || selectedItem.value.original.color
: selectedItem.value?.original.color || '';
<span
class="circle-icon-wrapper circle-icon-wrapper--small"
[style.background-color]="fnColor"
></span>
}
@if (selectedItem.value?.type === 'ALARM') {
<span
class="circle-icon-wrapper circle-icon-wrapper--small"
[style.background-color]="selectedItem.value?.original.color || ''"
>
<i
class="stroked-icon"
c8yIcon="bell"
></i>
</span>
}
@if (selectedItem.value?.type === 'EVENT') {
<span
class="circle-icon-wrapper circle-icon-wrapper--small"
[ngStyle]="{
'background-color': selectedItem.value?.original.color || ''
}"
>
<i
class="stroked-icon"
c8yIcon="online1"
></i>
</span>
}
</span>
</c8y-select>
} @else {
<div class="inner-scroll">
<div class="p-t-4 d-flex a-i-start gap-8">
@for (datapoint of displayConfig.datapoints; track datapoint) {
@let rtColors = getAggregationFunctionColors(datapoint);
@let dpId = getDpId(datapoint);
@if (datapoint.retrievalError) {
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="'Data point no longer exists.' | translate"
container="body"
[adaptivePosition]="false"
></i>
} @else if (
rtColors.length > 0 &&
datapointsWithDuplicateAggr.has(dpId) &&
displayConfig?.aggregation
) {
<!-- All aggregation functions return the same value — collapse to a single muted pill -->
<div
class="c8y-datapoint-pill pill--sm flex-no-shrink"
[class.active]="datapoint.__active"
style="opacity: 0.5"
title="{{ datapoint.label }} - {{ datapoint.__target.name }}"
>
<button
class="c8y-datapoint-pill__btn"
title="{{
(datapoint.__active ? hideDatapointLabel : showDatapointLabel) | translate
}}"
type="button"
data-cy="datapoint-toggle-visibility-btn"
(click)="toggleChart(datapoint)"
>
<i
class="icon-14"
[c8yIcon]="datapoint.__active ? 'eye text-primary' : 'eye-slash text-muted'"
></i>
</button>
<div class="c8y-datapoint-pill__label c8y-datapoint-pill__btn">
<i
class="m-r-4 icon-14"
c8yIcon="circle"
[style.color]="datapoint.color"
></i>
<span
class="text-truncate"
[class.text-muted]="!datapoint.__active"
>
<span class="text-truncate text-bold">{{ datapoint.label }}</span>
<small class="text-muted text-10">{{ datapoint.__target.name }}</small>
</span>
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="duplicateAggrWarning"
container="body"
[adaptivePosition]="false"
></i>
</div>
</div>
} @else if (rtColors.length > 0) {
@for (entry of rtColors; track entry.renderType) {
@let isVisible = isRenderTypeFunctionVisible(datapoint, entry.renderType);
<div
class="c8y-datapoint-pill pill--sm flex-no-shrink"
[class.active]="isVisible"
title="{{ datapoint.label }} [{{ entry.renderType }}] - {{
datapoint.__target.name
}}"
>
@if (!hasAtLeastOneDatapointActive && isVisible) {
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="'At least 1 data point must be active.' | translate"
container="body"
data-cy="datapoint-warning-icon"
[adaptivePosition]="false"
></i>
}
<button
class="c8y-datapoint-pill__btn"
title="{{
(isVisible ? hideDatapointLabel : showDatapointLabel) | translate
}}"
type="button"
data-cy="datapoint-toggle-visibility-btn"
(click)="toggleRenderTypeFunction(datapoint, entry.renderType, !isVisible)"
>
<i
class="icon-14"
[c8yIcon]="isVisible ? 'eye text-primary' : 'eye-slash text-muted'"
></i>
</button>
<div class="c8y-datapoint-pill__label c8y-datapoint-pill__btn">
<i
class="m-r-4 icon-14"
c8yIcon="circle"
[style.color]="entry.color"
></i>
<span
class="text-truncate"
[class.text-muted]="!isVisible"
>
<span class="text-truncate text-bold">
{{ datapoint.label }} [{{ entry.renderType }}]
</span>
<small class="text-muted text-10">
{{ datapoint.__target.name }}
</small>
</span>
</div>
</div>
}
} @else {
<div
class="c8y-datapoint-pill pill--sm flex-no-shrink"
title="{{ datapoint.label }} - {{ datapoint.__target.name }}"
[ngClass]="{ active: datapoint.__active }"
>
@if (!hasAtLeastOneDatapointActive && datapoint.__active) {
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="'At least 1 data point must be active.' | translate"
container="body"
data-cy="datapoint-warning-icon"
[adaptivePosition]="false"
></i>
}
<button
class="c8y-datapoint-pill__btn"
title="{{
(datapoint.__active ? hideDatapointLabel : showDatapointLabel) | translate
}} "
type="button"
data-cy="datapoint-toggle-visibility-btn"
(click)="toggleChart(datapoint)"
>
<i
class="icon-14"
[c8yIcon]="datapoint.__active ? 'eye text-primary' : 'eye-slash text-muted'"
></i>
</button>
<div class="c8y-datapoint-pill__label c8y-datapoint-pill__btn">
<i
class="m-r-4 icon-14"
c8yIcon="circle"
[ngStyle]="{
color: datapoint.color
}"
></i>
<span
class="text-truncate"
[ngClass]="{ 'text-muted': !datapoint.__active }"
>
<span class="text-truncate text-bold">
{{ datapoint.label }}
</span>
<small class="text-muted text-10">
{{ datapoint.__target.name }}
</small>
</span>
@if (datapointsOutOfSync.get(datapoint)) {
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="
'Measurements received for this data point may be out of sync.'
| translate
"
container="body"
[adaptivePosition]="false"
></i>
}
</div>
</div>
}
}
@for (alarm of alarms; track alarm) {
<div
class="c8y-alarm-pill pill--sm flex-no-shrink"
title="{{ alarm.filters.type }} "
>
@if (displayConfig?.activeAlarmTypesOutOfRange?.includes(alarm.filters.type)) {
<i
class="text-warning m-l-4"
c8yIcon="exclamation-triangle"
[tooltip]="
'Alarm of this type is currently active and outside of the selected time range'
| translate
"
container="body"
[adaptivePosition]="false"
></i>
}
<button
class="c8y-alarm-pill__btn"
title="{{ alarm.filters.type }} "
type="button"
(click)="toggleAlarmEventType(alarm)"
>
<i
class="icon-14"
[c8yIcon]="
alarm.__hidden || !alarm.__active
? 'eye-slash text-muted'
: 'eye text-primary'
"
></i>
</button>
<button
class="c8y-alarm-pill__label c8y-alarm-pill__btn"
(click)="toggleMarkedArea(alarm)"
[ngClass]="{
active:
!isMarkedAreaEnabled && alarm.filters.type === enabledMarkedAreaAlarmType
}"
>
<span
class="circle-icon-wrapper circle-icon-wrapper--small m-r-4"
[style.background-color]="alarm.color"
>
<i
class="stroked-icon"
c8yIcon="bell"
></i>
</span>
<span
class="text-truncate"
[ngClass]="{ 'text-muted': alarm.__hidden }"
>
<span class="text-truncate text-bold">
{{ alarm.label || alarm.filters.type }}
</span>
<small class="text-muted text-10">
{{ alarm.__target.name }}
</small>
</span>
</button>
</div>
}
@for (event of events; track event) {
<div
class="c8y-event-pill pill--sm flex-no-shrink"
title="{{ event.filters.type }}"
>
<button
class="c8y-event-pill__btn"
title="{{ event.filters.type }} "
type="button"
(click)="toggleAlarmEventType(event)"
>
<i
class="icon-14"
[c8yIcon]="
event.__hidden || !event.__active
? 'eye-slash text-muted'
: 'eye text-primary'
"
></i>
</button>
<div class="c8y-event-pill__label c8y-event-pill__btn">
<span
class="circle-icon-wrapper circle-icon-wrapper--small m-r-4"
[ngStyle]="{ 'background-color': event.color }"
>
<i
class="stroked-icon"
c8yIcon="online1"
></i>
</span>
<span
class="text-truncate text-bold"
[ngClass]="{ 'text-muted': event.__hidden }"
>
<span class="text-truncate">
{{ event.label || event.filters.type }}
</span>
<small class="text-muted text-10">
{{ event.__target.name }}
</small>
</span>
</div>
</div>
}
</div>
</div>
<button
class="btn-help btn-help--sm m-r-8"
[attr.aria-label]="'Help' | translate"
[popover]="legendHelpTemplate"
placement="left"
triggers="focus"
container="body"
[adaptivePosition]="true"
></button>
}
</div>
}
</div>
</div>
@if (displayConfig !== null) {
<c8y-charts
#chart
[config]="displayConfig"
[alerts]="alerts"
[chartViewContext]="chartViewContext"
(updateAlarmsAndEvents)="updateAlarmsAndEvents($event)"
(configChangeOnZoomOut)="updateDashboardTimeContext($event)"
(datapointOutOfSync)="handleDatapointOutOfSync($event)"
(datapointBackInSync)="handleDatapointBackInSync($event)"
(finishLoading)="this.isLoading$.next($event)"
(isMarkedAreaEnabled)="isMarkedAreaEnabled = $event"
(duplicateAggregationsDetected)="onDuplicateAggregationsDetected($event)"
></c8y-charts>
}
<ng-template #legendHelpTemplate>
<div [innerHTML]="legendHelp"></div>
</ng-template>
<c8y-widget-action>
<c8y-datapoints-export-selector
[displayMode]="'icon-only'"
[exportConfig]="exportConfig"
[containerClass]="'d-contents'"
></c8y-datapoints-export-selector>
<button
class="btn btn-icon"
[attr.aria-label]="'Save as image' | translate"
tooltip="{{ 'Save as image' | translate }}"
container="body"
type="button"
[delay]="500"
(click)="chartComponent.saveAsImage()"
[adaptivePosition]="false"
>
<i
class="icon-14"
c8yIcon="image-file-checked"
></i>
</button>
</c8y-widget-action>