見出し画像

Angular_Highchartsのtooltipを自作する #435

今回の記事は、仕事で参考にした偉大な先人のコードを紹介させていただきます。

Highchartsでtooltipを使う際、そのカスタマイズに苦労されたことはないでしょうか?

Highchartsはグラフ描画の素晴らしいライブラリである一方、要件による微調整はやりにくい時があります。私もどうしても満たしたいTooltipの仕様が満たせずに苦労していました。

Highcharts-tooltipのソースコードです↓


偉大な先人は、このソースコードを読み解いて以下のように実装することで、自作tooltipをHighchartsのtooltipとして使うことを実現していました。

以下のようにHTMLでHighchartsを呼び出す方式で、「options」に渡す「ChartData」でTooltipの設定を行います。

<highcharts-chart
  [Highcharts]="Highcharts"
  [options]="chartData"
>
</highcharts-chart>

まずchartDataから見ていきます。ここではベン図を作成していて、ベン図にマウスをホバーした際のTooltipを設定します。

constしているtooltipComponentが重要なポイントです。ここでComponentへのアクセスを作成していて、それをHighchartsのtooltip欄に設定することで、自作Componentをtooltipとして使うことができます。下記コードのformatterで設定しています。

また、Highchartsの設定内では「this」が使いにくいので、refThisComponentで自身のComponentへアクセスできるようにしています。

[sample-venn-graph-component.ts]

import {Compiler, Injector} from '@angular/core';
import venn from 'highcharts/modules/venn';
venn(Highcharts);
import { ComponentFactoryClass } from '@shared/utils/component-factory.util';
import { SampleTooltipComponent } from './your-pass';
import { SampleTooltipModule } from './your-pass';


@Component({
  selector: 'app-sample-app',
  templateUrl: './sample-app.component.html',
  styleUrls: ['./sample-app.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class SampleVennGraphComponent implements OnInit, OnChanges {

  constructor(private injector: Injector, private compiler: Compiler) {}


  private _buildVennChartData(): void {
    const refThisComponent: SampleVennGraphComponent = this;
    const tooltipComponent: ComponentRef<SampleTooltipComponent> = new ComponentFactoryClass<
      SampleTooltipModule,
      SampleTooltipComponent
    >(this.injector, this.compiler).createComponent(SampleTooltipModule, SampleTooltipComponent);

    this.chartData = {
      chart: {
        type: 'venn',
        width: 700,
        spacingRight: 50
      },
      credits: {
        enabled: false // クレジットを非表示にする
      },
      exporting: {
        enabled: false // エクスポートメニューを無効にする
      },
      title: {
        text: null
      },
      tooltip: {
        useHTML: true, // HTMLを使用する設定を有効にする
        outside: true,
        backgroundColor: null,
        borderWidth: 0,
        borderRadius: 0,
        style: {
          fontFamily: 'Noto Sans JP', // フォントファミリーの設定
          color: '#545454', // フォントの色
          textOutline: 'none',
          textAlign: 'center'
        },
        formatter: function () {
          tooltipComponent.instance.nameSets = this.point.sets;
          tooltipComponent.instance.sampleValue = this.point.value.toFixed(1);
          tooltipComponent.instance.setsLine = refThisComponent.buildTooltipTitle(this.point.sets);
          tooltipComponent.changeDetectorRef.detectChanges();  // 変化を検知して再描画する
          return tooltipComponent.location.nativeElement.outerHTML;
        }
      },
      plotOptions: {
        venn: {
          states: {
            hover: {
              borderColor: 'transparent', // ホバー時の枠線を透明にする
              borderWidth: 0 // 枠線の太さを0にする
            }
          }
        }
      },
      series: [
        {
          data: this.convertToVennData(this.totalUniqueReachSet, 0)
        }
      ]
    };
  }
 
  private buildTooltipTitle(sets: string[]): string {
    switch (sets.length) {
      case 3:
        return '全て重複';
      case 2:
        return `${sets[1]}${sets[0]}`;
      default:
        return `${sets[0]}全体`;
    }
  }

次は、Componentを作成するクラスです。先人が作ってくれていたもので、神掛かっていると感じました。

正確には、ComponentRefとあるように、Componentへのアクセスを作成してreturnします。これによってtooltip部分でSampleTooltipComponentにアクセスできるわけです。

[component-factory.util.ts]

import { Compiler, ComponentRef, Injector, Type } from '@angular/core';

export class ComponentFactoryClass<M, C> {
  constructor(private injector: Injector, private compiler: Compiler) {}

  public createComponent = (module: Type<M>, component: Type<C>): ComponentRef<C> => {
    const compiledModule = this.compiler.compileModuleAndAllComponentsSync(module);
    const factory = compiledModule.ngModuleFactory
      .create(this.injector)
      .componentFactoryResolver.resolveComponentFactory(component);
    return factory.create(this.injector);
  };
}

後はSampleTooltipComponentを定義するだけです。親Componentから必要な値を@Inputして、HTMLとCSSを調整すれば好きなようにカスタムしたtooltipを使うことができます(ここではtsファイルのみ記載)。

[sample-tooltip.component.ts]

import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-sample-tooltip',
  templateUrl: './sample-tooltip.component.html',
  styleUrls: ['./sample-tooltip.component.scss']
})
export class SampleTooltipComponent implements OnInit {
  @Input()
  public nameSets: string[] = [];
  @Input()
  public sampleValue = 0;
  @Input()
  public setsLine = '';

  constructor() {}

  ngOnInit(): void {}
}
[sample-tooltip.module.ts]
 
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedModule } from '@shared/shared.module';
import { SampleTooltipComponent } from './sample-tooltip.component';

@NgModule({
  imports: [CommonModule, SharedModule],
  declarations: [SampleTooltipComponent]
})
export class SampleTooltipModule {}


以上になります。

自分でソースコードを読み解いて、こういう応用を効かせられるようになりたいですが、まだまだ道のりは長そうです。

ここまでお読みいただきありがとうございました!


この記事が気に入ったらサポートをしてみませんか?