Change style of hover and tooltip in chartjs or ng2-charts

9.9k views Asked by At

is there any way to change style of hover as well as the tooltip with chartjs or ng2-charts? I want to hide the hover points and only display them whenever I hover on them along with the indicator as the line. Here is the exact chart model I want to build:

https://interactive-bitcoin-price-chart-foxkmkynmg.now.sh/

Thank you in advance for your tips.

EDIT: I followed instructions of GRUNT to apply this chart option into my Angular app, the full chart is shown with tooltip whenever I hover, but the line-trace indicator is not. Here are my codes:

plugin-hoverline.ts:

export class PluginHoverline {
   posX: null;
   isMouseOut:boolean = false;

   drawLine(chart, posX) {
      const ctx = chart.ctx,
         x_axis = chart.scales['x-axis-0'],
         y_axis = chart.scales['y-axis-0'],
         x = posX,
         topY = y_axis.top,
         bottomY = y_axis.bottom;
      if (posX < x_axis.left || posX > x_axis.right) return;
      // draw line
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(x, topY);
      ctx.lineTo(x, bottomY);
      ctx.lineWidth = chart.options.lineOnHover.lineWidth;
      ctx.strokeStyle = chart.options.lineOnHover.lineColor;
      ctx.stroke();
      ctx.restore();
   };

   beforeInit(chart) {
      chart.options.events.push('mouseover');
   };

   afterEvent(chart, event) {
      if (!chart.options.lineOnHover || !chart.options.lineOnHover.enabled) return;
      if (event.type !== 'mousemove' && event.type !== 'mouseover') {
         if (event.type === 'mouseout') this.isMouseOut = true;
         chart.clear();
         chart.draw();
         return;
      }
      this.posX = event.x;
      this.isMouseOut = false;
      chart.clear();
      chart.draw();
      this.drawLine(chart, this.posX);

      var metaData = chart.getDatasetMeta(0).data,
         radius = chart.data.datasets[0].pointHoverRadius,
         posX = metaData.map(e => e._model.x);
      posX.forEach(function(pos, posIndex) {
         if (this.posX < pos + radius && this.posX > pos - radius) {
            chart.updateHoverStyle([metaData[posIndex]], null, true);
            chart.tooltip._active = [metaData[posIndex]];
         } else chart.updateHoverStyle([metaData[posIndex]], null, false);
      }.bind(this));
      chart.tooltip.update();
   };

   afterDatasetsDraw(chart, ease) {
      if (!this.posX) return;
      if (!this.isMouseOut) this.drawLine(chart, this.posX);
   };
}

banner.component.ts: (chart component)

import { Component, OnInit } from '@angular/core';
import { HistoricalBpiService } from '../../services/historical-bpi.service';
import { PluginHoverline } from './plugin-hoverline';

@Component({
  selector: 'app-banner',
  templateUrl: './banner.component.html',
  styleUrls: ['./banner.component.scss']
})
export class BannerComponent implements OnInit {

  private dataUrl: string = 'historical/close.json';

  constructor(
    private historicalBpiService:HistoricalBpiService
  ) {}

  // lineChart
  public lineChartData:any = [
    { data:[], label: 'Bitcoin price' }
  ];

  public lineChartLabels:Array<any> = [];

  public lineChartOptions:any = {
    responsive: true,
    maintainAspectRatio: false,
    layout: {
      padding: 0
    },
    lineOnHover: {
     enabled: true,
     lineColor: '#bbb',
     lineWidth: 1
   },
    scales: {
      yAxes: [{
        display: true,
        scaleLabel: {
          display: false,
          labelString: 'USD'
        },
        ticks: {
          display: false
        },
        gridLines: {
          display: true,
          tickMarkLength: 0
        }
      }],
      xAxes: [{
        ticks: {
          display: false
        },
        gridLines: {
          display: false,
          tickMarkLength: 0
        }
      }]
    },
    elements: {
      point: {
        radius: 3
      },
      line: {
        tension: 0.4, // 0 disables bezier curves
      }
    },
    hover: {
      mode: 'nearest',
      intersect: false
    },
    tooltips: {
      mode: 'nearest',
      intersect: false,
      backgroundColor: 'rgb(95,22,21)',
      callbacks: {
        label: function (tooltipItems, data) {
          return data.datasets[tooltipItems.datasetIndex].label + ' : ' + '$' + tooltipItems.yLabel.toLocaleString();
        },
        labelColor: function(tooltipItem, chart) {
          var dataset = chart.config.data.datasets[tooltipItem.datasetIndex];
          return {
            backgroundColor : dataset.backgroundColor
          }
        }
      }
    }
  };
  public lineChartColors:Array<any> = [
    {
      backgroundColor: 'rgba(199,32,48,0.8',
      borderColor: 'rgb(95,22,21);',
      pointBackgroundColor: 'rgba(218,208,163,0.8)',
      pointHoverBackgroundColor: 'rgba(218,208,163,0.8)',
      pointHoverBorderColor: 'rgb(218,208,163)',
      pointHoverRadius: 6,
      steppedLine: false

    }
  ];
  public lineChartLegend:boolean = false;
  public lineChartType:string = 'line';


  // events
  public chartClicked(e:any):void {
    console.log(e);
  }

  public chartHovered(e:any):void {
    console.log(e);
  }

  ngOnInit(){
    this.historicalBpiService.getBpiData(this.dataUrl)
      .subscribe(
        res => {
          //this.lineChartData = Object.keys(res.bpi).map(function (key) { return res.bpi[key];});
          this.lineChartData[0].data = Object.values(res.bpi);
          this.lineChartLabels = Object.keys(res.bpi);
          //console.log(this.lineChartData,this.lineChartLabels);
        }
      )
  }
}

template:

<div class="chart">
      <canvas baseChart height="360px"
        [datasets]="lineChartData"
        [labels]="lineChartLabels"
        [options]="lineChartOptions"
        [colors]="lineChartColors"
        [legend]="lineChartLegend"
        [chartType]="lineChartType"
        (chartHover)="chartHovered($event)"
        (chartClick)="chartClicked($event)"></canvas>
    </div>
2

There are 2 answers

8
ɢʀᴜɴᴛ On BEST ANSWER

Unfortunately there is no built-in functionality for this yet. However you can use this chart plugin (once created for my own purpose) to achieve your goal.

To utilize the plugin, set the following option in your chart­'s options config :

lineOnHover: {
   enabled: true,
   lineColor: '#bbb',
   lineWidth: 1
}

also, make sure to set the following properties for your dataset :

pointRadius: 0,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'white'

see - live demo

0
Kiong On

This answer is to build on top of GRUNT's answer above to be able to display if its a multiline graph:


Just change the following (in the code snippet he provide here):

posX.forEach(function(pos, posIndex) {
  if (this.posX < pos + radius && this.posX > pos - radius) {
    chart.updateHoverStyle([metaData[posIndex]], null, true);
    chart.tooltip._active = [metaData[posIndex]];
  } else chart.updateHoverStyle([metaData[posIndex]], null, false);
}.bind(this));

to:

posX.forEach(function(pos, posIndex) {
  var metaDatum = []
  if (this.posX < pos + radius && this.posX > pos - radius) {
    chart.data.datasets.forEach((dataset, ind) => {
      metaDatum.push(chart.getDatasetMeta(ind).data[posIndex]);
    })

    chart.updateHoverStyle(metaDatum, null, true);
    chart.tooltip._active = metaDatum;
  } else {
    metaDatum.push(metaData[posIndex]);
    chart.updateHoverStyle(metaDatum, null, false);
  }
}.bind(this));