UNPKG

@nova-ui/dashboards

Version:

Nova Dashboards is a framework designed to provide feature developers with a common solution for presenting data coming from various sources within a single view, as well as a set of predefined widget visualizations that are 100% configuration-driven and

333 lines 84.9 kB
// © 2022 SolarWinds Worldwide, LLC. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import { ChangeDetectorRef, Component, ElementRef, HostBinding, Inject, Input, KeyValueDiffers, NgZone, ViewChild, ViewEncapsulation, } from "@angular/core"; import isEqual from "lodash/isEqual"; import some from "lodash/some"; import { EventBus, LoggerService, UnitConversionService, } from "@nova-ui/bits"; import { Chart, ChartAssist, ChartDonutContentPlugin, ChartPalette, defaultColorProvider, MappedValueProvider, SELECT_DATA_POINT_EVENT, SequentialColorProvider, } from "@nova-ui/charts"; import { DashboardUnitConversionPipe } from "../../common/pipes/dashboard-unit-conversion-pipe"; import { CategoryChartUtilService } from "../../services/category-chart-util.service"; import { INTERACTION } from "../../services/types"; import { DATA_SOURCE, PIZZAGNA_EVENT_BUS, WellKnownDataSourceFeatures, } from "../../types"; import { LegendPlacement } from "../../widget-types/common/widget/legend"; import { ProportionalWidgetChartTypes, } from "./types"; import * as i0 from "@angular/core"; import * as i1 from "@nova-ui/bits"; import * as i2 from "@angular/common"; import * as i3 from "@nova-ui/charts"; import * as i4 from "@angular/cdk/portal"; import * as i5 from "../../pizzagna/directives/component-portal/component-portal.directive"; import * as i6 from "./proportional-donut-content/proportional-donut-content.component"; import * as i7 from "../../common/pipes/dashboard-unit-conversion-pipe"; /** @ignore */ export class ProportionalWidgetComponent { static { this.lateLoadKey = "ProportionalWidgetComponent"; } static { this.NO_SWITCH_LAYOUT_INTERVAL_SIZE = 20; } static { this.MAX_ROW_LAYOUT_SIZE = 360; } static { this.TICK_LABEL_MAX_WIDTH = 75; } get interactive() { return (this.configuration?.interactive || this.dataSource?.features?.getFeatureConfig(WellKnownDataSourceFeatures.Interactivity)?.enabled || false); } constructor(changeDetector, ngZone, kvDiffers, eventBus, dataSource, logger, unitConversionService) { this.changeDetector = changeDetector; this.ngZone = ngZone; this.kvDiffers = kvDiffers; this.eventBus = eventBus; this.dataSource = dataSource; this.logger = logger; this.prioritizedGridRows = { right: false, bottom: false, }; this.chartPalette = new ChartPalette(defaultColorProvider()); this.differ = this.kvDiffers.find(this.prioritizedGridRows).create(); this.unitConversionPipe = new DashboardUnitConversionPipe(unitConversionService); } // Note: Using this helper method to be able to use // optional method chaining and prevent strict mode error computeLegendTileValue(legendSeries) { // @ts-ignore: Suppressing series null parameter value error to avoid breaking default flow return this.accessors.data?.value?.(legendSeries, 0, null, null); } ngOnChanges(changes) { const newChartColors = changes.configuration?.currentValue?.chartColors; const prevChartColors = changes.configuration?.previousValue?.chartColors; if (changes.widgetData || !isEqual(newChartColors, prevChartColors)) { this.updateChartColors(); } if (changes.configuration) { const newChartType = changes.configuration.currentValue.chartOptions.type; const prevChartType = changes.configuration.previousValue && changes.configuration.previousValue.chartOptions.type; // configure the chart if (newChartType && newChartType !== prevChartType) { this.buildChart(newChartType); if (this.widgetData) { this.updateChart(); } } this.legendFormatter = this.configuration.chartOptions.legendFormatter; this.contentFormatter = this.configuration.chartOptions.contentFormatter; this.chartFormatterComponentType = this.configuration.chartOptions.chartFormatterComponentType; this.getContentFormatterProperties(); this.changeDetector.markForCheck(); } if (changes.widgetData) { if (this.chartAssist) { this.updateChart(); this.getContentFormatterProperties(); this.changeDetector.markForCheck(); } } } ngAfterViewInit() { this.handleGridFlowOnResize(); } ngOnDestroy() { this.proportionalWidgetResizeObserver?.disconnect(); this.chartTypeSubscription$?.unsubscribe(); } getContentFormatterProperties() { this.contentFormatterProperties = { data: this.widgetData, config: this.configuration, chartAssist: this.chartAssist, properties: this.contentFormatter?.properties, }; } /** Checks if chart is donut. */ isDonutChart() { return (this.configuration.chartOptions.type === ProportionalWidgetChartTypes.DonutChart); } /** Checks if chart is radial. */ isRadialChart() { return ([ ProportionalWidgetChartTypes.DonutChart, ProportionalWidgetChartTypes.PieChart, ].indexOf(this.configuration.chartOptions.type) !== -1); } /** Checks if legend should be shown. */ hasLegend() { return (this.configuration.chartOptions.legendPlacement !== LegendPlacement.None); } /** Checks if legend should be aligned to right. */ legendShouldBeAlignedRight() { return (this.configuration.chartOptions.legendPlacement === LegendPlacement.Right); } onInteraction(data) { if (!this.interactive) { return; } this.eventBus.getStream(INTERACTION).next({ payload: { data } }); } /** Configures the chart options */ buildChart(chartType) { this.donutContentPlugin = null; const { grid, accessors, renderer, scales, preprocessor } = CategoryChartUtilService.getChartAttributes(chartType, this.chartPalette?.standardColors); // TODO: Refactor this to be able to pass different types of preprocessor to get rid of the any this.chartAssist = new ChartAssist(new Chart(grid), preprocessor, this.chartPalette); this.renderer = renderer; this.accessors = accessors; this.scales = scales; if (this.isDonutChart()) { this.donutContentPlugin = new ChartDonutContentPlugin(); this.chartAssist.chart.addPlugin(this.donutContentPlugin); } if (this.configuration.chartOptions.type === ProportionalWidgetChartTypes.HorizontalBarChart) { this.scales.x.formatters.tick = (value) => this.unitConversionPipe.transform(value); this.applyTickLabelMaxWidths(); } else if (this.configuration.chartOptions.type === ProportionalWidgetChartTypes.VerticalBarChart) { this.scales.y.formatters.tick = (value) => this.unitConversionPipe.transform(value); } this.chartTypeSubscription$?.unsubscribe(); this.chartTypeSubscription$ = this.chartAssist.chart .getEventBus() .getStream(SELECT_DATA_POINT_EVENT) .subscribe((event) => { // event payload is a data point from the chart - since we display one data point for every series, // we convert the data point to the original series const series = this.widgetData.find((s) => s.id === event.data.seriesId); this.onInteraction(series); }); } handleGridFlowOnResize() { this.proportionalWidgetResizeObserver = new ResizeObserver(() => this.onResize()); this.ngZone.runOutsideAngular(() => { this.proportionalWidgetResizeObserver.observe(this.gridContainer.nativeElement); }); } applyTickLabelMaxWidths() { const gridConfigAxis = this.chartAssist.chart.getGrid().config().axis; gridConfigAxis.left.tickLabel.maxWidth = this.configuration.chartOptions.horizontalBarTickLabelConfig ?.maxWidth?.left ?? ProportionalWidgetComponent.TICK_LABEL_MAX_WIDTH; gridConfigAxis.right.tickLabel.maxWidth = this.configuration.chartOptions.horizontalBarTickLabelConfig ?.maxWidth?.right ?? ProportionalWidgetComponent.TICK_LABEL_MAX_WIDTH; } onResize() { if (this.isContainerInNoSwitchLayoutInterval()) { return; } switch (this.configuration.chartOptions.legendPlacement) { case LegendPlacement.Bottom: this.prioritizedGridRows.bottom = this.containerHasRowLayoutWidth(); break; case LegendPlacement.Right: this.prioritizedGridRows.right = this.containerHasRowLayoutWidth(); break; } if (this.differ.diff(this.prioritizedGridRows)) { this.changeDetector.detectChanges(); } } isContainerInNoSwitchLayoutInterval() { const containerWidth = this.gridContainer.nativeElement.getBoundingClientRect().width; return (containerWidth > ProportionalWidgetComponent.MAX_ROW_LAYOUT_SIZE - ProportionalWidgetComponent.NO_SWITCH_LAYOUT_INTERVAL_SIZE / 2 && containerWidth < ProportionalWidgetComponent.MAX_ROW_LAYOUT_SIZE + ProportionalWidgetComponent.NO_SWITCH_LAYOUT_INTERVAL_SIZE / 2); } containerHasRowLayoutWidth() { const containerWidth = this.gridContainer.nativeElement.getBoundingClientRect().width; return containerWidth < ProportionalWidgetComponent.MAX_ROW_LAYOUT_SIZE; } /** Builds the chart */ updateChart() { this.chartAssist.update(CategoryChartUtilService.buildChartSeries(this.widgetData, this.accessors, this.renderer, this.scales)); } updateChartColors() { let colorProvider; const dataColors = this.widgetData?.map((v) => v.color); const configurationColors = this.configuration.chartColors; if (!this.configuration.prioritizeWidgetColors && some(dataColors)) { colorProvider = this.getDataDriverColorProvider(this.widgetData); } else if (configurationColors) { colorProvider = this.getConfigurationColorProvider(configurationColors); } else { colorProvider = defaultColorProvider(); } this.chartPalette = new ChartPalette(colorProvider); this.buildChart(this.configuration?.chartOptions.type); if (this.chartAssist) { this.chartAssist.palette = this.chartPalette; this.updateChart(); } } getDataDriverColorProvider(widgetData) { let colorProvider; const dataColors = widgetData?.map((v) => v.color).filter((v) => !!v); if (dataColors.length === widgetData.length) { const colorMap = widgetData.reduce((acc, next) => { acc[next.id] = next.color; return acc; }, {}); colorProvider = new MappedValueProvider(colorMap); } else { const widgetDataWithColor = widgetData.filter((series) => series.color); this.logger.warn(`Not all series have colors set, setting default pallette. Current series color config: ${JSON.stringify(widgetDataWithColor)}`); colorProvider = defaultColorProvider(); } return colorProvider; } getConfigurationColorProvider(configurationColors) { let colorProvider; // remove data colors since nui-chart takes them into consideration no matter what if (this.configuration.prioritizeWidgetColors && this.widgetData) { this.widgetData = this.widgetData.map((origin) => { const series = { ...origin }; if (series.color) { delete series.color; } return series; }); } if (Array.isArray(configurationColors)) { colorProvider = new SequentialColorProvider(configurationColors); } else { const setupColorsLength = Object.keys(configurationColors).length; if (setupColorsLength === this.widgetData?.length) { colorProvider = new MappedValueProvider(configurationColors); } else { // eslint-disable-next-line max-len this.logger.warn(`Not all series have colors set, setting default pallette. Current series color config: ${JSON.stringify(configurationColors)}`); colorProvider = defaultColorProvider(); } } return colorProvider; } get isEmpty() { return (!this.widgetData || this.widgetData.length === 0 || !this.chartAssist); } /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ProportionalWidgetComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i0.KeyValueDiffers }, { token: PIZZAGNA_EVENT_BUS }, { token: DATA_SOURCE }, { token: i1.LoggerService }, { token: i1.UnitConversionService }], target: i0.ɵɵFactoryTarget.Component }); } /** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ProportionalWidgetComponent, selector: "nui-proportional-widget", inputs: { widgetData: "widgetData", configuration: "configuration", elementClass: "elementClass", seriesToIconMap: "seriesToIconMap" }, host: { properties: { "class": "this.elementClass" } }, viewQueries: [{ propertyName: "gridContainer", first: true, predicate: ["gridContainer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #gridContainer\n class=\"w-100 nui-chart-layout flex-grow-1 p-3\"\n [ngClass]=\"{\n 'prioritize-rows__right': prioritizedGridRows.right,\n 'prioritize-rows__bottom': prioritizedGridRows.bottom,\n 'empty-grid': !(widgetData && widgetData.length > 0 && chartAssist)\n }\"\n>\n <div class=\"d-flex chart has-overlay\" *ngIf=\"!isEmpty\">\n <nui-chart class=\"w-100\" [chart]=\"chartAssist.chart\"></nui-chart>\n <nui-chart-donut-content\n *ngIf=\"donutContentPlugin\"\n class=\"nui-proportional-widget__chart-donut-content\"\n [plugin]=\"donutContentPlugin\"\n >\n <div\n class=\"h-100 w-100 d-flex justify-content-center align-items-center text-center\"\n >\n <div class=\"d-inline-block\" nuiZoomContent [useZoom]=\"false\">\n <!-- OLD CONFIG HERE -->\n <ng-container\n *ngIf=\"!configuration?.chartOptions?.donutContentConfig\"\n >\n <ng-container\n nuiComponentPortal\n [componentType]=\"\n contentFormatter?.componentType ||\n 'DonutContentRawFormatterComponent'\n \"\n [properties]=\"contentFormatterProperties\"\n #componentPortal2=\"nuiComponentPortal\"\n >\n <ng-template\n [cdkPortalOutlet]=\"componentPortal2.portal\"\n (attached)=\"componentPortal2.attached($event)\"\n ></ng-template>\n </ng-container>\n </ng-container>\n\n <!-- NEW CONFIG HERE -->\n <ng-container\n *ngIf=\"configuration?.chartOptions?.donutContentConfig\"\n >\n <nui-proportional-donut-content\n [widgetData]=\"widgetData\"\n [donutConfig]=\"\n configuration?.chartOptions?.donutContentConfig\n \"\n [chartAssist]=\"chartAssist\"\n ></nui-proportional-donut-content>\n </ng-container>\n </div>\n </div>\n </nui-chart-donut-content>\n </div>\n <div\n class=\"w-100 d-flex legend\"\n [ngClass]=\"[\n legendShouldBeAlignedRight() ? 'scrollable' : 'legend-bottom'\n ]\"\n *ngIf=\"hasLegend() && chartAssist\"\n >\n <nui-legend\n class=\"w-100 m-auto\"\n [interactive]=\"false\"\n (mouseleave)=\"chartAssist.resetVisibleSeries()\"\n [orientation]=\"\n legendShouldBeAlignedRight() ? 'vertical' : 'horizontal'\n \"\n >\n <nui-legend-series\n class=\"proportional-chart__legend-series\"\n *ngFor=\"\n let legendSeries of chartAssist.legendSeriesSet;\n trackBy: chartAssist.seriesTrackByFn;\n let i = index\n \"\n [ngClass]=\"\n (prioritizedGridRows.right || prioritizedGridRows.bottom) &&\n 'description-min-width--unset'\n \"\n [isSelected]=\"!chartAssist.isSeriesHidden(legendSeries.id)\"\n (isSelectedChange)=\"\n chartAssist.toggleSeries(legendSeries.id, $event)\n \"\n [seriesRenderState]=\"\n chartAssist.renderStatesIndex[legendSeries.id]?.state\n \"\n (mouseenter)=\"chartAssist.emphasizeSeries(legendSeries.id)\"\n (click)=\"onInteraction(legendSeries)\"\n [class.nui-proportional-widget__legend--interactive]=\"\n interactive\n \"\n >\n <div\n *ngIf=\"\n legendFormatter && legendFormatter.componentType;\n else legendDefaultTile\n \"\n >\n <div description>\n <ng-container\n nuiComponentPortal\n [componentType]=\"legendFormatter.componentType\"\n [properties]=\"{ data: widgetData[i] }\"\n #componentPortal=\"nuiComponentPortal\"\n >\n <ng-template\n [cdkPortalOutlet]=\"componentPortal.portal\"\n (attached)=\"componentPortal.attached($event)\"\n ></ng-template>\n </ng-container>\n </div>\n </div>\n <ng-template #legendDefaultTile>\n <nui-rich-legend-tile\n [value]=\"\n computeLegendTileValue(legendSeries.data[0])\n | nuiDashboardUnitConversion\n \"\n [unitLabel]=\"legendUnitLabel\"\n [color]=\"\n chartAssist.palette.textColors.get(legendSeries.id)\n \"\n [backgroundColor]=\"\n chartAssist.palette.standardColors.get(\n legendSeries.id\n )\n \"\n >\n </nui-rich-legend-tile>\n\n <nui-icon\n legendTransclusion\n *ngIf=\"\n seriesToIconMap && seriesToIconMap[legendSeries.id]\n \"\n class=\"align-items-center\"\n [icon]=\"seriesToIconMap[legendSeries.id]\"\n ></nui-icon>\n\n <div\n description\n class=\"description-container d-flex flex-column nui-text-small justify-content-center\"\n >\n <div\n *ngIf=\"!legendSeries.link\"\n class=\"description description-primary\"\n [ngClass]=\"{\n 'nui-text-link-small link': interactive\n }\"\n >\n {{ legendSeries.name }}\n </div>\n <a\n class=\"description description-primary nui-text-link-small link\"\n (click)=\"$event.stopPropagation()\"\n *ngIf=\"legendSeries.link\"\n [href]=\"legendSeries.link\"\n rel=\"noopener noreferrer\"\n [title]=\"legendSeries.name\"\n >\n {{ legendSeries.name }}\n </a>\n </div>\n </ng-template>\n </nui-legend-series>\n </nui-legend>\n </div>\n <div *ngIf=\"isEmpty\" class=\"is-empty\">\n <nui-image image=\"no-data-to-show\"></nui-image>\n </div>\n</div>\n", styles: [".nui-proportional-widget__chart-donut-content{position:absolute}.nui-proportional-widget__legend--interactive{cursor:pointer!important}.has-overlay{overflow:auto}.nui-chart-layout{grid-template:\"axis-label-left axis-label-right .\" 0fr \"chart chart legend\" minmax(60%,auto) \"axis-label-bottom axis-label-bottom .\" 0fr \"legend-bottom legend-bottom .\" minmax(auto,min-content) / 1fr 1fr auto}.nui-chart-layout .legend-bottom{overflow:auto;align-self:auto;padding-left:0}.prioritize-rows__right .nui-legend-series,.prioritize-rows__bottom .nui-legend-series{max-width:unset;width:100%}.prioritize-rows .legend{justify-self:start}.prioritize-rows__right{grid-template:\"chart chart .\" 60% \"legend legend .\" 40%}.prioritize-rows__bottom{grid-template:\"chart chart .\" 60% \"legend-bottom legend-bottom .\" 40%}.empty-grid{grid-template-columns:1fr;grid-template-rows:1fr}.proportional-chart__legend-series .link.description-primary{color:var(--nui-color-text-link,#0079aa)}.is-empty{display:grid;place-content:center;height:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.LegendComponent, selector: "nui-legend", inputs: ["active", "interactive", "orientation", "seriesColor", "seriesIcon", "seriesUnitLabel"] }, { kind: "component", type: i3.LegendSeriesComponent, selector: "nui-legend-series", inputs: ["active", "interactive", "isSelected", "descriptionPrimary", "descriptionSecondary", "icon", "color", "seriesRenderState"], outputs: ["isSelectedChange"] }, { kind: "component", type: i3.RichLegendTileComponent, selector: "nui-rich-legend-tile", inputs: ["unitLabel", "value", "backgroundColor", "color"] }, { kind: "component", type: i3.ChartComponent, selector: "nui-chart", inputs: ["chart"] }, { kind: "component", type: i3.ChartDonutContentComponent, selector: "nui-chart-donut-content", inputs: ["plugin"] }, { kind: "component", type: i1.IconComponent, selector: "nui-icon", inputs: ["iconColor", "brushType", "iconHoverColor", "iconSize", "cssClass", "fillContainer", "status", "childStatus", "icon", "counter"] }, { kind: "component", type: i1.ImageComponent, selector: "nui-image", inputs: ["image", "description", "float", "margin", "isWatermark", "width", "height", "autoFill"] }, { kind: "directive", type: i4.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "directive", type: i5.ComponentPortalDirective, selector: "[nuiComponentPortal]", inputs: ["componentId", "componentType", "properties", "providers", "outputs"], outputs: ["output"], exportAs: ["nuiComponentPortal"] }, { kind: "directive", type: i1.ZoomContentDirective, selector: "[nuiZoomContent]", inputs: ["zoomRatio", "minZoomDifference", "maxZoom", "minZoom", "useZoom", "margin", "minScale", "maxScale", "scaleIN$", "scaleOUT$"] }, { kind: "component", type: i6.ProportionalDonutContentComponent, selector: "nui-proportional-donut-content", inputs: ["widgetData", "donutConfig", "chartAssist"] }, { kind: "pipe", type: i7.DashboardUnitConversionPipe, name: "nuiDashboardUnitConversion" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ProportionalWidgetComponent, decorators: [{ type: Component, args: [{ selector: "nui-proportional-widget", encapsulation: ViewEncapsulation.Emulated, template: "<div\n #gridContainer\n class=\"w-100 nui-chart-layout flex-grow-1 p-3\"\n [ngClass]=\"{\n 'prioritize-rows__right': prioritizedGridRows.right,\n 'prioritize-rows__bottom': prioritizedGridRows.bottom,\n 'empty-grid': !(widgetData && widgetData.length > 0 && chartAssist)\n }\"\n>\n <div class=\"d-flex chart has-overlay\" *ngIf=\"!isEmpty\">\n <nui-chart class=\"w-100\" [chart]=\"chartAssist.chart\"></nui-chart>\n <nui-chart-donut-content\n *ngIf=\"donutContentPlugin\"\n class=\"nui-proportional-widget__chart-donut-content\"\n [plugin]=\"donutContentPlugin\"\n >\n <div\n class=\"h-100 w-100 d-flex justify-content-center align-items-center text-center\"\n >\n <div class=\"d-inline-block\" nuiZoomContent [useZoom]=\"false\">\n <!-- OLD CONFIG HERE -->\n <ng-container\n *ngIf=\"!configuration?.chartOptions?.donutContentConfig\"\n >\n <ng-container\n nuiComponentPortal\n [componentType]=\"\n contentFormatter?.componentType ||\n 'DonutContentRawFormatterComponent'\n \"\n [properties]=\"contentFormatterProperties\"\n #componentPortal2=\"nuiComponentPortal\"\n >\n <ng-template\n [cdkPortalOutlet]=\"componentPortal2.portal\"\n (attached)=\"componentPortal2.attached($event)\"\n ></ng-template>\n </ng-container>\n </ng-container>\n\n <!-- NEW CONFIG HERE -->\n <ng-container\n *ngIf=\"configuration?.chartOptions?.donutContentConfig\"\n >\n <nui-proportional-donut-content\n [widgetData]=\"widgetData\"\n [donutConfig]=\"\n configuration?.chartOptions?.donutContentConfig\n \"\n [chartAssist]=\"chartAssist\"\n ></nui-proportional-donut-content>\n </ng-container>\n </div>\n </div>\n </nui-chart-donut-content>\n </div>\n <div\n class=\"w-100 d-flex legend\"\n [ngClass]=\"[\n legendShouldBeAlignedRight() ? 'scrollable' : 'legend-bottom'\n ]\"\n *ngIf=\"hasLegend() && chartAssist\"\n >\n <nui-legend\n class=\"w-100 m-auto\"\n [interactive]=\"false\"\n (mouseleave)=\"chartAssist.resetVisibleSeries()\"\n [orientation]=\"\n legendShouldBeAlignedRight() ? 'vertical' : 'horizontal'\n \"\n >\n <nui-legend-series\n class=\"proportional-chart__legend-series\"\n *ngFor=\"\n let legendSeries of chartAssist.legendSeriesSet;\n trackBy: chartAssist.seriesTrackByFn;\n let i = index\n \"\n [ngClass]=\"\n (prioritizedGridRows.right || prioritizedGridRows.bottom) &&\n 'description-min-width--unset'\n \"\n [isSelected]=\"!chartAssist.isSeriesHidden(legendSeries.id)\"\n (isSelectedChange)=\"\n chartAssist.toggleSeries(legendSeries.id, $event)\n \"\n [seriesRenderState]=\"\n chartAssist.renderStatesIndex[legendSeries.id]?.state\n \"\n (mouseenter)=\"chartAssist.emphasizeSeries(legendSeries.id)\"\n (click)=\"onInteraction(legendSeries)\"\n [class.nui-proportional-widget__legend--interactive]=\"\n interactive\n \"\n >\n <div\n *ngIf=\"\n legendFormatter && legendFormatter.componentType;\n else legendDefaultTile\n \"\n >\n <div description>\n <ng-container\n nuiComponentPortal\n [componentType]=\"legendFormatter.componentType\"\n [properties]=\"{ data: widgetData[i] }\"\n #componentPortal=\"nuiComponentPortal\"\n >\n <ng-template\n [cdkPortalOutlet]=\"componentPortal.portal\"\n (attached)=\"componentPortal.attached($event)\"\n ></ng-template>\n </ng-container>\n </div>\n </div>\n <ng-template #legendDefaultTile>\n <nui-rich-legend-tile\n [value]=\"\n computeLegendTileValue(legendSeries.data[0])\n | nuiDashboardUnitConversion\n \"\n [unitLabel]=\"legendUnitLabel\"\n [color]=\"\n chartAssist.palette.textColors.get(legendSeries.id)\n \"\n [backgroundColor]=\"\n chartAssist.palette.standardColors.get(\n legendSeries.id\n )\n \"\n >\n </nui-rich-legend-tile>\n\n <nui-icon\n legendTransclusion\n *ngIf=\"\n seriesToIconMap && seriesToIconMap[legendSeries.id]\n \"\n class=\"align-items-center\"\n [icon]=\"seriesToIconMap[legendSeries.id]\"\n ></nui-icon>\n\n <div\n description\n class=\"description-container d-flex flex-column nui-text-small justify-content-center\"\n >\n <div\n *ngIf=\"!legendSeries.link\"\n class=\"description description-primary\"\n [ngClass]=\"{\n 'nui-text-link-small link': interactive\n }\"\n >\n {{ legendSeries.name }}\n </div>\n <a\n class=\"description description-primary nui-text-link-small link\"\n (click)=\"$event.stopPropagation()\"\n *ngIf=\"legendSeries.link\"\n [href]=\"legendSeries.link\"\n rel=\"noopener noreferrer\"\n [title]=\"legendSeries.name\"\n >\n {{ legendSeries.name }}\n </a>\n </div>\n </ng-template>\n </nui-legend-series>\n </nui-legend>\n </div>\n <div *ngIf=\"isEmpty\" class=\"is-empty\">\n <nui-image image=\"no-data-to-show\"></nui-image>\n </div>\n</div>\n", styles: [".nui-proportional-widget__chart-donut-content{position:absolute}.nui-proportional-widget__legend--interactive{cursor:pointer!important}.has-overlay{overflow:auto}.nui-chart-layout{grid-template:\"axis-label-left axis-label-right .\" 0fr \"chart chart legend\" minmax(60%,auto) \"axis-label-bottom axis-label-bottom .\" 0fr \"legend-bottom legend-bottom .\" minmax(auto,min-content) / 1fr 1fr auto}.nui-chart-layout .legend-bottom{overflow:auto;align-self:auto;padding-left:0}.prioritize-rows__right .nui-legend-series,.prioritize-rows__bottom .nui-legend-series{max-width:unset;width:100%}.prioritize-rows .legend{justify-self:start}.prioritize-rows__right{grid-template:\"chart chart .\" 60% \"legend legend .\" 40%}.prioritize-rows__bottom{grid-template:\"chart chart .\" 60% \"legend-bottom legend-bottom .\" 40%}.empty-grid{grid-template-columns:1fr;grid-template-rows:1fr}.proportional-chart__legend-series .link.description-primary{color:var(--nui-color-text-link,#0079aa)}.is-empty{display:grid;place-content:center;height:100%}\n"] }] }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: i0.KeyValueDiffers }, { type: i1.EventBus, decorators: [{ type: Inject, args: [PIZZAGNA_EVENT_BUS] }] }, { type: undefined, decorators: [{ type: Inject, args: [DATA_SOURCE] }] }, { type: i1.LoggerService }, { type: i1.UnitConversionService }], propDecorators: { widgetData: [{ type: Input }], configuration: [{ type: Input }], elementClass: [{ type: Input }, { type: HostBinding, args: ["class"] }], seriesToIconMap: [{ type: Input }], gridContainer: [{ type: ViewChild, args: ["gridContainer", { static: true }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvcG9ydGlvbmFsLXdpZGdldC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbGliL2NvbXBvbmVudHMvcHJvcG9ydGlvbmFsLXdpZGdldC9wcm9wb3J0aW9uYWwtd2lkZ2V0LmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvY29tcG9uZW50cy9wcm9wb3J0aW9uYWwtd2lkZ2V0L3Byb3BvcnRpb25hbC13aWRnZXQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEseURBQXlEO0FBQ3pELEVBQUU7QUFDRiwrRUFBK0U7QUFDL0UsNEVBQTRFO0FBQzVFLDhFQUE4RTtBQUM5RSwrRUFBK0U7QUFDL0UsOEVBQThFO0FBQzlFLDREQUE0RDtBQUM1RCxFQUFFO0FBQ0YsNkVBQTZFO0FBQzdFLHVEQUF1RDtBQUN2RCxFQUFFO0FBQ0YsNkVBQTZFO0FBQzdFLDRFQUE0RTtBQUM1RSwrRUFBK0U7QUFDL0UsMEVBQTBFO0FBQzFFLGlGQUFpRjtBQUNqRiw2RUFBNkU7QUFDN0UsaUJBQWlCO0FBRWpCLE9BQU8sRUFFSCxpQkFBaUIsRUFDakIsU0FBUyxFQUNULFVBQVUsRUFDVixXQUFXLEVBQ1gsTUFBTSxFQUNOLEtBQUssRUFFTCxlQUFlLEVBQ2YsTUFBTSxFQUlOLFNBQVMsRUFDVCxpQkFBaUIsR0FDcEIsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxPQUFPLE1BQU0sZ0JBQWdCLENBQUM7QUFDckMsT0FBTyxJQUFJLE1BQU0sYUFBYSxDQUFDO0FBRy9CLE9BQU8sRUFDSCxRQUFRLEVBR1IsYUFBYSxFQUNiLHFCQUFxQixHQUN4QixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQ0gsS0FBSyxFQUNMLFdBQVcsRUFDWCx1QkFBdUIsRUFDdkIsWUFBWSxFQUNaLG9CQUFvQixFQUtwQixtQkFBbUIsRUFHbkIsdUJBQXVCLEVBQ3ZCLHVCQUF1QixHQUUxQixN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