diff --git a/projects/swimlane/ngx-charts/src/lib/common/base-chart.component.ts b/projects/swimlane/ngx-charts/src/lib/common/base-chart.component.ts index 1fb86981b..bd2d67607 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/base-chart.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/base-chart.component.ts @@ -23,6 +23,7 @@ import { isDate } from '../utils/types'; import { Color } from '../utils/color-sets'; import { ScaleType } from './types/scale-type.enum'; import { ViewDimensions } from './types/view-dimension.interface'; +import { PieChartService } from '../pie-chart/pie-chart.service'; @Component({ selector: 'base-chart', @@ -47,7 +48,8 @@ export class BaseChartComponent implements OnChanges, AfterViewInit, OnDestroy, protected chartElement: ElementRef, protected zone: NgZone, protected cd: ChangeDetectorRef, - @Inject(PLATFORM_ID) public platformId: any + @Inject(PLATFORM_ID) public platformId: any, + protected pieChartSvc: PieChartService, ) {} ngOnInit() { diff --git a/projects/swimlane/ngx-charts/src/lib/common/tooltip/position/position.ts b/projects/swimlane/ngx-charts/src/lib/common/tooltip/position/position.ts index daa144cd8..a80a25c02 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/tooltip/position/position.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/tooltip/position/position.ts @@ -1,3 +1,4 @@ +import { PieChartService } from '@swimlane/ngx-charts/pie-chart/pie-chart.service'; import { PlacementTypes } from './placement-type.enum'; const caretOffset = 7; @@ -210,27 +211,50 @@ export class PositionHelper { return { top, left }; } - /** + /** * Position content * * @memberOf PositionHelper */ - static positionContent(placement, elmDim, hostDim, spacing, alignment): any { + static positionContent(placement, elmDim, hostDim, spacing, alignment, pieChartDoughnout?, pieChartSvc?: PieChartService): any { let top = 0; let left = 0; - if (placement === PlacementTypes.Right) { - left = hostDim.left + hostDim.width + spacing; - top = PositionHelper.calculateVerticalAlignment(hostDim, elmDim, alignment); + //TODO: Implement positioning adjustment + if (pieChartDoughnout) { + top = PositionHelper.calculateTopPosition(hostDim, pieChartSvc.radius.outerRadius, pieChartSvc.radius.innerRadius, spacing) + pieChartSvc.actuallyCentroidCoords.y - spacing * 2 + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment) + pieChartSvc.actuallyCentroidCoords.x + } else { + left = hostDim.left + hostDim.width + spacing; + top = PositionHelper.calculateVerticalAlignment(hostDim, elmDim, alignment); + } } else if (placement === PlacementTypes.Left) { - left = hostDim.left - elmDim.width - spacing; - top = PositionHelper.calculateVerticalAlignment(hostDim, elmDim, alignment); + //TODO: Implement positioning adjustment + if (pieChartDoughnout) { + top = PositionHelper.calculateTopPosition(hostDim, pieChartSvc.radius.outerRadius, pieChartSvc.radius.innerRadius, spacing) + pieChartSvc.actuallyCentroidCoords.y - spacing * 2 + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment) + pieChartSvc.actuallyCentroidCoords.x + } else { + left = hostDim.left - elmDim.width - spacing; + top = PositionHelper.calculateVerticalAlignment(hostDim, elmDim, alignment); + } } else if (placement === PlacementTypes.Top) { - top = hostDim.top - elmDim.height - spacing; - left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment); + if (pieChartDoughnout) { + const OFFSET_TOP = 20 + top = PositionHelper.calculateTopPosition(hostDim, pieChartSvc.radius.outerRadius, pieChartSvc.radius.innerRadius, spacing) + pieChartSvc.actuallyCentroidCoords.y - OFFSET_TOP + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment) + pieChartSvc.actuallyCentroidCoords.x + } else { + top = hostDim.top - elmDim.height - spacing; + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment); + } } else if (placement === PlacementTypes.Bottom) { - top = hostDim.top + hostDim.height + spacing; - left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment); + if (pieChartDoughnout) { + const OFFSET_TOP = 40 + top = PositionHelper.calculateTopPosition(hostDim, pieChartSvc.radius.outerRadius, pieChartSvc.radius.innerRadius, spacing) + pieChartSvc.actuallyCentroidCoords.y + OFFSET_TOP + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment) + pieChartSvc.actuallyCentroidCoords.x + } else { + top = hostDim.top + hostDim.height + spacing; + left = PositionHelper.calculateHorizontalAlignment(hostDim, elmDim, alignment); + } } return { top, left }; @@ -258,4 +282,8 @@ export class PositionHelper { return placement; } + + static calculateTopPosition(hostDim: DOMRect, outerRadius, innerRadius, spacing): number { + return innerRadius <= 0 ? hostDim.top + hostDim.height/2 - 47 : hostDim.top + hostDim.height/2 - (outerRadius - innerRadius) - spacing + } } diff --git a/projects/swimlane/ngx-charts/src/lib/common/tooltip/tooltip.component.ts b/projects/swimlane/ngx-charts/src/lib/common/tooltip/tooltip.component.ts index 03a8de681..d869a2f90 100644 --- a/projects/swimlane/ngx-charts/src/lib/common/tooltip/tooltip.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/common/tooltip/tooltip.component.ts @@ -18,6 +18,7 @@ import { PositionHelper, PlacementTypes } from './position'; import { StyleTypes } from './style.type'; import { isPlatformBrowser } from '@angular/common'; +import { PieChartService } from '@swimlane/ngx-charts/pie-chart/pie-chart.service'; @Component({ selector: 'ngx-tooltip-content', @@ -58,7 +59,7 @@ export class TooltipContentComponent implements AfterViewInit { return clz; } - constructor(public element: ElementRef, private renderer: Renderer2, @Inject(PLATFORM_ID) private platformId: any) {} + constructor(public element: ElementRef, private renderer: Renderer2, @Inject(PLATFORM_ID) private platformId: any, private pieChartSvc: PieChartService) {} ngAfterViewInit(): void { setTimeout(this.position.bind(this)); @@ -70,14 +71,26 @@ export class TooltipContentComponent implements AfterViewInit { } const nativeElm = this.element.nativeElement; - const hostDim = this.host.nativeElement.getBoundingClientRect(); + const hostSvg = this.renderer.parentNode(this.host.nativeElement) + //TODO: Implement positioning adjustment for other placements + const isPieChart = this.renderer.parentNode(this.renderer.parentNode(hostSvg)).classList.contains('pie-chart') && (this.placement === 'bottom' || this.placement === 'top'); + + let hostDim + + //TODO: Implement positioning adjustment for other placements + if (isPieChart) { + const hostSvg = this.renderer.parentNode(this.host.nativeElement) + hostDim = this.renderer.parentNode(hostSvg).getBoundingClientRect(); + } else { + hostDim = this.host.nativeElement.getBoundingClientRect(); + } // if no dims were found, never show if (!hostDim.height && !hostDim.width) return; const elmDim = nativeElm.getBoundingClientRect(); this.checkFlip(hostDim, elmDim); - this.positionContent(nativeElm, hostDim, elmDim); + this.positionContent(nativeElm, hostDim, elmDim, isPieChart); if (this.showCaret) { this.positionCaret(hostDim, elmDim); @@ -87,12 +100,12 @@ export class TooltipContentComponent implements AfterViewInit { setTimeout(() => this.renderer.addClass(nativeElm, 'animate'), 1); } - positionContent(nativeElm: HTMLElement, hostDim: DOMRect, elmDim: DOMRect): void { - const { top, left } = PositionHelper.positionContent(this.placement, elmDim, hostDim, this.spacing, this.alignment); - - this.renderer.setStyle(nativeElm, 'top', `${top}px`); - this.renderer.setStyle(nativeElm, 'left', `${left}px`); - } + positionContent(nativeElm: HTMLElement, hostDim: DOMRect, elmDim: DOMRect, isPieChart: boolean ): void { + const { top, left } = PositionHelper.positionContent(this.placement, elmDim, hostDim, this.spacing, this.alignment, isPieChart, this.pieChartSvc); + + this.renderer.setStyle(nativeElm, 'top', `${top}px`); + this.renderer.setStyle(nativeElm, 'left', `${left}px`); + } positionCaret(hostDim: DOMRect, elmDim: DOMRect): void { const caretElm = this.caretElm.nativeElement; diff --git a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-arc.component.ts b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-arc.component.ts index 8de464efd..bd3bd9cf3 100644 --- a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-arc.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-arc.component.ts @@ -14,6 +14,7 @@ import { arc } from 'd3-shape'; import { id } from '../utils/id'; import { DataItem } from '../models/chart-data.model'; import { BarOrientation } from '../common/types/bar-orientation.enum'; +import { PieChartService } from './pie-chart.service'; @Component({ selector: 'g[ngx-charts-pie-arc]', @@ -57,7 +58,7 @@ export class PieArcComponent implements OnChanges { @Output() activate = new EventEmitter(); @Output() deactivate = new EventEmitter(); @Output() dblclick = new EventEmitter(); - + barOrientation = BarOrientation; element: HTMLElement; @@ -69,7 +70,7 @@ export class PieArcComponent implements OnChanges { private _timeout; - constructor(element: ElementRef) { + constructor(element: ElementRef, private PieChartSvg: PieChartService) { this.element = element.nativeElement; } @@ -90,6 +91,8 @@ export class PieArcComponent implements OnChanges { this.startOpacity = 0.5; this.radialGradientId = 'linearGrad' + id().toString(); this.gradientFill = `url(#${this.radialGradientId})`; + this.PieChartSvg.newCoords(this.calculateCentroid(calc)) + this.PieChartSvg.setRadius(this.outerRadius, this.innerRadius) if (this.animate) { if (this.initialized) { @@ -112,6 +115,12 @@ export class PieArcComponent implements OnChanges { return arc().innerRadius(this.innerRadius).outerRadius(outerRadius).cornerRadius(this.cornerRadius); } + calculateCentroid(arc): any { + //Example: https://d3js.org/d3-shape/arc#arc_centroid + const [centroidX, centroidY] = arc.centroid({innerRadius: this.innerRadius , outerRadius: this.outerRadius , endAngle: this.endAngle , startAngle: this.startAngle}); + return {x: centroidX, y: centroidY} + } + loadAnimation(): void { const node = select(this.element) .selectAll('.arc') diff --git a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.component.ts b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.component.ts index b34783a69..c5813213c 100644 --- a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.component.ts +++ b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.component.ts @@ -6,12 +6,19 @@ import { EventEmitter, ChangeDetectionStrategy, ContentChild, - TemplateRef + TemplateRef, + ElementRef, + NgZone, + ChangeDetectorRef, + PLATFORM_ID, + Inject, + Renderer2 } from '@angular/core'; import { calculateViewDimensions } from '../common/view-dimensions.helper'; import { ColorHelper } from '../common/color.helper'; import { BaseChartComponent } from '../common/base-chart.component'; import { DataItem } from '../models/chart-data.model'; +import { PieChartService } from './pie-chart.service'; import { LegendOptions, LegendPosition } from '../common/types/legend.model'; import { ViewDimensions } from '../common/types/view-dimension.interface'; import { ScaleType } from '../common/types/scale-type.enum'; @@ -83,6 +90,15 @@ export class PieChartComponent extends BaseChartComponent { @ContentChild('tooltipTemplate') tooltipTemplate: TemplateRef; + constructor( protected chartElement: ElementRef, + protected zone: NgZone, + protected cd: ChangeDetectorRef, + protected renderer: Renderer2, + protected pieChartSvc: PieChartService, + @Inject(PLATFORM_ID) public platformId: any) { + super(chartElement, zone, cd, renderer, pieChartSvc); + } + translation: string; outerRadius: number; innerRadius: number; @@ -94,7 +110,7 @@ export class PieChartComponent extends BaseChartComponent { update(): void { super.update(); - + if (this.labels && this.hasNoOptionalMarginsSet()) { this.margins = [30, 80, 30, 80]; } else if (!this.labels && this.hasNoOptionalMarginsSet()) { @@ -177,6 +193,10 @@ export class PieChartComponent extends BaseChartComponent { } this.activeEntries = [item, ...this.activeEntries]; + const selected = this.results.findIndex(d => { + return d === this.activeEntries[0]; + }); + this.pieChartSvc.setSelectedObject(selected) this.activate.emit({ value: item, entries: this.activeEntries }); } diff --git a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.module.ts b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.module.ts index ca15d84fd..b3eb968d3 100644 --- a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.module.ts +++ b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.module.ts @@ -7,6 +7,7 @@ import { PieChartComponent } from './pie-chart.component'; import { PieGridComponent } from './pie-grid.component'; import { PieGridSeriesComponent } from './pie-grid-series.component'; import { PieSeriesComponent } from './pie-series.component'; +import { PieChartService } from './pie-chart.service'; @NgModule({ imports: [ChartCommonModule], @@ -27,6 +28,7 @@ import { PieSeriesComponent } from './pie-series.component'; PieGridComponent, PieGridSeriesComponent, PieSeriesComponent - ] + ], + providers: [PieChartService], }) export class PieChartModule {} diff --git a/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.service.ts b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.service.ts new file mode 100644 index 000000000..686bd0c20 --- /dev/null +++ b/projects/swimlane/ngx-charts/src/lib/pie-chart/pie-chart.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class PieChartService { + centroidCoords = []; + idxSelectedObject = 0; + idxCoords = 0; + innerRadius = 0 + outerRadius = 0 + + newCoords(newCoords) { + if (!this.centroidCoords.some(coord => coord.x === newCoords.x && coord.y === newCoords.y)) { + this.centroidCoords.push(newCoords); + } + this.idxCoords = this.getIdxCoords(newCoords) + } + + clearCoords() { + this.centroidCoords = []; + } + + getIdxCoords(coords){ + return this.centroidCoords.findIndex(d => { + return d.x === coords.x && d.y === coords.y + }); + } + + setSelectedObject(objectSelected) { + this.idxSelectedObject = objectSelected; + } + + setRadius(outerRadius, innerRadius){ + this.innerRadius = innerRadius + this.outerRadius = outerRadius + } + + get radius() : any { + return { innerRadius: this.innerRadius, outerRadius: this.outerRadius } + } + + get idxSelected() : number { + return this.idxSelectedObject + } + + get actuallyCentroidCoords(): {x: number, y: number} { + return this.centroidCoords[this.idxCoords] + } + +} \ No newline at end of file