widgets/implementations/datapoints-graph/datapoints-graph-view/datapoints-graph-widget-view.component.ts

Implements

OnDestroy

Metadata

Relationships

@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>

results matching ""

    No results matching ""