BuZz
BuZz

Reputation: 17485

Using Angular components to populate tooltips triggered from D3 visuals

I have an Angular CLI application that displays a number of D3.v5 visuals. Some of the tooltips I would like to add on those D3 displays are fairly non-trivial, and I would like to make use of the power of Angular's templating and data binding.

The only remotely connected resource I could find is https://github.com/andyperlitch/ngx-d3-tooltip. I checked out the code. It is fairly outdated, relying on D3 v4 and Angular v5.

How would you go about using Angular components as data-driven tooltips from a D3 context ?

Upvotes: 1

Views: 2068

Answers (1)

bryan60
bryan60

Reputation: 29335

I'm not a d3 expert, so I started with this: http://bl.ocks.org/williaster/af5b855651ffe29bdca1

and worked my way from there.

Here is the working blitz: https://stackblitz.com/edit/angular-ivy-vrq5p6?file=src%2Fapp%2Fchart.component.ts

So I wrote a basic component that take in some data and renders a scatter plot with d3.

The component has a tooltip container above the chart container, that looks like this:

<div class="tooltip" *ngIf="toolTipTmp && hovered" [ngStyle]="ttPos">
  <ng-container *ngTemplateOutlet="toolTipTmp.tmp; context: {$implicit: hovered}"></ng-container>
</div>

the component has corresponding properties:

  @ContentChild(ChartTooltipDirective)
  toolTipTmp: ChartTooltipDirective

  hovered?: any
  ttPos = {
    "left.px": 0,
    "top.px": 0
  }

the content child here is a simple directive that exposes a template:

@Directive({
  selector: "[chart-tooltip]"
})
export class ChartTooltipDirective {
  constructor(public tmp: TemplateRef<any>) {}
}

then the render function links into these on the mouse enter and mouse leave functions:

  var tipMouseover = (d) => {
      this.hovered = d;
      this.ttPos["left.px"] = d3.event.pageX + 15;
      this.ttPos["top.px"] = d3.event.pageY - 28;
  };
  // tooltip mouseout event handler
  var tipMouseout = (d) => {
      this.hovered = undefined;
  };

  ....

    .on("mouseover", tipMouseover)
    .on("mouseout", tipMouseout);

basically it just sets the hovered element and the position of the tooltip.

The concept here is that you set whatever element is hovered and set the tooltip's position on the component, and use that info to reveal and position the tooltip, then you feed the hovered element into the the template context, and use some template defined as a content child of your chart component, so you can define whatever template you like.

you use it in your template pretty easily:

<my-chart [data]="data" xLabel="Sugar" yLabel="Calories">
  <ng-template chart-tooltip let-d>
    {{d.cereal}}
    <br>
    <span>{{d.manufacturer}}</span>
    <br>
    <b>{{d.sugar}}</b> sugar, <b>{{d.calories}}</b> calories
  </ng-template>
</my-chart>

just apply your directive and write up your tooltip template. This way, you have pretty much the full power of angular templating and components for your tooltips.

I tried to make this as generic as possible, but again, not a d3 expert.

EDIT:

here it is adapted to the chart you provided:

https://stackblitz.com/edit/angular-deviation-chart-gradient-x23a9r?file=src%2Fapp%2Fdeviation-chart%2Fdeviation-chart.component.ts

Upvotes: 2

Related Questions