chartjs / Chart.js

Simple HTML5 Charts using the <canvas> tag
https://www.chartjs.org/
MIT License
64.79k stars 11.93k forks source link

how to hide the tooltip of a specific dataset #1889

Closed kerron closed 8 years ago

kerron commented 8 years ago

Is there a way I can hide the tooltip of a specific datasets in v2-beta? I can hide the data using the callback functions, but this still leaves a tooltip, albeit empty.

Is there a way to hide its entirety?

etimberg commented 8 years ago

At the moment, there is no way to do this. I'd be happy to look over a PR adding this.

kerron commented 8 years ago

thanks @etimberg!

etimberg commented 8 years ago

@kerron if you want to try modifying the library yourself, I'd suggest looking at Core.Tooltip.draw or Core.Tooltip.update in https://github.com/nnnick/Chart.js/blob/v2.0-dev/src/core/core.tooltip.js

I'd be happy to look at a PR adding this functionality

rjurado01 commented 8 years ago

Update: see this answer: http://stackoverflow.com/a/37297802/1827284**


I modify the library to do this:

First I add tooltip option to _model in updateElement function of each type (radar, doughnut, line...). Bar example:

Chart.controllers.bar = Chart.DatasetController.extend({
  updateElement: function updateElement(rectangle, index, reset, numBars) {
  ...
  helpers.extend(rectangle, {
    ...
    // Desired view properties
    _model: {
      ...
      // Tooltip
      label: this.chart.data.labels[index],
      datasetLabel: this.getDataset().label,
      tooltip: this.getDataset().tooltip,  // Line added !!
      ...

Then I modify Core.Tooltip.draw to add showTooltip condition to if:

Chart.Tooltip = Chart.Element.extend({
  ...
  draw: function draw() {
    ...
    // Check if show dataset tooltip
    var showTooltip =  (this._active[0] && this._active[0]._model.tooltip !== false);

    if (this._options.tooltips.enabled && showTooltip) {

Finally I define this option in my datasets:

this.myChart = new Chart(this.ctx, {
  type: 'bar',
  data: {
    labels: labels,
    datasets: [
      { type: 'line', data: data3 },
      { type: 'bar', data: data1, backgroundColor: "#3EAFD5" , tooltip: false},
      { type: 'bar', data: data2, backgroundColor: "#D24B47", tooltip: false}
    ]
  }
}
qiluo commented 7 years ago

Hi, I'm on v2.5, I have a multi datasets in a line chart, and I just wanna show tooltip for one dataset, your solution doesn't work for me, here's my code

data: {
    labels: labels,
    datasets: [
      { data: data3 },
      { data: data1, backgroundColor: "#3EAFD5" , tooltip: false},
      { data: data2, backgroundColor: "#D24B47", tooltip: false}
    ]
  }

Any idea?

rjurado01 commented 7 years ago

@qiluo I suggest you using the option filter that you can find in the tooltip documentation:: http://www.chartjs.org/docs/#chart-configuration-tooltip-configuration

andrei-kondakov commented 7 years ago

@qiluo try:

options: {
    tooltips: {
        filter: function (tooltipItem) {
            return tooltipItem.datasetIndex === 0;
        }
    }
}
qiluo commented 7 years ago

hi guys, I successfully hide the tooltip now with above method, thanks

stewbawka commented 7 years ago

I am using the above approach to filter points from the tooltip, but I want to not show the tooltip at all when all points have been filtered out. Right now i'm getting an empty tooltip.

IE: I have a line chart with multiple datasets and a feature to highlight one line by clicking on it and when a line is highlighted only tooltips for that dataset are visible.

thomasjoscht commented 7 years ago

Hey guys, the solution of @andrei-kondakov was not enough in my case. Also a custom tooltip must be added which resets some tooltip styles like

tooltips: {
      custom: function(tooltipModel) {
        // EXTENSION: filter is not enough! Hide tooltip frame
        if (!tooltipModel.body || tooltipModel.body.length < 1) {
          tooltipModel.caretSize = 0;
          tooltipModel.xPadding = 0;
          tooltipModel.yPadding = 0;
          tooltipModel.cornerRadius = 0;
          tooltipModel.width = 0;
          tooltipModel.height = 0;
        }
      },
      // Hide tooltip body
      filter: function(tooltipItem, data) {
        return !data.datasets[tooltipItem.datasetIndex].tooltipHidden; // custom added prop to dataset
      }

Have a look at my sample https://jsfiddle.net/Lq3aptph/

Evertvdw commented 7 years ago

The solution of @thomasjoscht works perfectly! Only thing I have to add is that you need to add hoverRadius: 0 to the dataset that you are hiding the tooltips from to prevent the increase in size upon hovering.

This is only the case when you want to have that dataset visible ofcourse, in the fiddle the dataset is completely opaque, making this unnecessary.

KangYoosam commented 6 years ago

@andrei-kondakov you saved my life. Thank you!

tonix-tuft commented 4 years ago

@Evertvdw Where did you add hoverRadius: 0? I am using @thomasjoscht hack for line charts as well as for pie charts...

benmccann commented 4 years ago

You can search the docs for hoverRadius: https://www.chartjs.org/docs/latest/configuration/elements.html#point-configuration

ericrippetoe commented 1 year ago

I thought I'd contribute some. I have 3 datasets and I created a 4th one as a trendline, but I didn't want to show that in the legend or in the basic tooltip. Using this code, I was able to get what I wanted. Legend and the default tooltip only show the first 3 datasets, but the line is still there on the line chart.

plugins: {
      legend: {
        align: 'center',
        position: 'bottom',
        labels: {
          padding: 20,
          boxWidth: 30,
          color: theme.palette.text.primary,
          usePointStyle: true,
          filter: function(item, chart) {
            // Logic to remove a particular legend item goes here
            return !item.text.includes('Trend');
        }}
      },
      tooltip: {
        filter: function (tooltipItem) {
          return [0, 1, 2].includes(tooltipItem.datasetIndex);
        },
        callbacks: {
          label: function(context) {
            let label = context.dataset.label || '';
            if (label) {
              label = '  ' + label; // Add a space at the beginning
            }
            if (context.parsed.y !== null) {
              label += ': ' + abbreviateNumber(`${context.parsed.y}`);
            }
            return label;
        }
      }
    },
    }
iamexe commented 1 year ago

Just in case someone comes across this like me and has some questions / it's still not working / not a perfect solution, I have found the perfect solution for me. I will be more verbose to reduce questions.

"dependencies": { "bootstrap": "^5.3.2", "chart.js": "^4.4.0", "core-js": "^3.8.3", "superagent": "^8.1.2", "vue": "^3.2.13", "vue-chartjs": "^5.2.0" },


App.vue relevant snippet:

    <BarChart v-if="dataLoaded" :items="items" dataset="Service" id="service-chart" />

    <BarChart v-if="dataLoaded" :items="items" dataset="Customer" id="service-chart" />

Relevant to the question of filtering values within datasets:


 plugins: {
          tooltip: {
            filter: function (tooltipItem) { // we don't need 'data'
              return tooltipItem.dataset.data[tooltipItem.dataIndex] > 0; // i am displaying all values greater than 0. zero is filtered.
            },
            callbacks: {
              footer: helpers.tooltipFooter, // the helpers.tooltipFooter is defined in helpers.js
            },
          }
        },

And this code for me was within the BarChart.vue component:

<script>
import { Bar } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
import helpers from './../helpers';

ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

export default {
  name: 'BarChart',
  components: { Bar },
  props: {
    items: {},
    dataset: String, // this is used to "swap" datasets and labels
  },
  data() {
    return {
      data: {
        labels: {},
        datasets: {}  
      },
      dataLoaded: false, // chart will be empty because of async data loading from a db or api.
      options: {
        interaction: {
          intersect: false,
          mode: 'x', // when hovering over a x-value, tooltip will display regardless of being on the bar or not.
        },
        plugins: {
          tooltip: {
            filter: function (tooltipItem) { // we don't need 'data'
              return tooltipItem.dataset.data[tooltipItem.dataIndex] > 0; // i am displaying all values greater than 0. zero is filtered.
            },
            callbacks: {
              footer: helpers.tooltipFooter, // the helpers.tooltipFooter is defined in helpers.js
            },
          }
        },
        responsive: true,
        scales: {
          x: {
            stacked: true,
          },
          y: {
            stacked: true,
            beginAtZero: true,
            title: {
              display: true,
              text: 'CHF', // CHF is the Swiss currency symbol. Use USD, EUR, MXN, GBP, BTC, ...
            },
          }
        }
      },
    }
  },
  mounted() {
    if (this.dataset === "Customer") {
      // Create an object to store prices for all services dynamically
      const pricesByService = {};

      // Populate prices for all services
      this.items.forEach((item) => {
        const customerName = item.companyName;
        const serviceName = item.serviceName;
        const price = item.price;

        // Create an object for the service if it doesn't exist
        if (!pricesByService[serviceName]) {
          pricesByService[serviceName] = {};
        }

        // Add the price for the customer of the service
        pricesByService[serviceName][customerName] = price;
      });

      // Get unique customer names and sort them alphabetically
      const customerNames = Array.from(
        new Set(this.items.map((item) => item.companyName))
      ).sort();

      // Create datasets for all services
      const datasets = Object.keys(pricesByService).map((service) => ({
        label: service,
        backgroundColor: helpers.stringToColor(service),
        data: customerNames.map((customerName) =>
          pricesByService[service][customerName] || 0
        ),
      }));

      this.data.labels = customerNames;
      this.data.datasets = datasets;
      this.dataLoaded = true; // the data is loaded, so now the component can render the chart. notice v-if="dataLoaded"
    }

    if (this.dataset === "Service") {  
    //  ... basically the same code as "Customer" again but swaped data.labels and data.datasets with the magic of chatgeepetee ...
    }
  },
}
</script>

'./../helpers.js'


const tooltipFooter = (tooltipItems) => {
  let sum = 0;
  tooltipItems.forEach(function(tooltipItem) {
    sum += tooltipItem.parsed.y;
  });
  return 'CHF ' + sum;
};

// stringToColor such that the datasets backgroundcolor values persist over time yet are derrived from the data.
// because md5 hashes are hexadecimal in nature, we can just use the first 6 values with slice after a '#'

const stringToColor = (str) => {
  let md5string = md5(str);
  let color =  `#${md5string.slice(0, 6)}`;
  return color;
}

// md5 function by [dkellner](https://stackoverflow.com/users/1892607/dkellner) is exactly 42 lines long and has a size of 4000. 

const md5 = (inputString) => {
  var hc="0123456789abcdef";
  function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
  function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
  function rl(n,c)            {return (n<<c)|(n>>>(32-c));}
  function cm(q,a,b,x,s,t)    {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);}
  function ff(a,b,c,d,x,s,t)  {return cm((b&c)|((~b)&d),a,b,x,s,t);}
  function gg(a,b,c,d,x,s,t)  {return cm((b&d)|(c&(~d)),a,b,x,s,t);}
  function hh(a,b,c,d,x,s,t)  {return cm(b^c^d,a,b,x,s,t);}
  function ii(a,b,c,d,x,s,t)  {return cm(c^(b|(~d)),a,b,x,s,t);}
  function sb(x) {
      var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i<nblk*16;i++) blks[i]=0;
      for(i=0;i<x.length;i++) blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);
      blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
  }
  var i,x=sb(""+inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
  for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
      a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17,  606105819);
      b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
      c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22,  -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
      d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17,     -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
      a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12,  -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
      b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
      c=gg(c,d,a,b,x[i+11],14,  643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
      d=gg(d,a,b,c,x[i+10], 9,   38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
      a=gg(a,b,c,d,x[i+ 9], 5,  568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
      b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9,  -51403784);
      c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4,    -378558);
      d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23,  -35309556);
      a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
      b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4,  681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
      c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23,   76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
      d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16,  530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
      a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
      b=ii(b,c,d,a,x[i+ 5],21,  -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
      c=ii(c,d,a,b,x[i+10],15,   -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
      d=ii(d,a,b,c,x[i+15],10,  -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
      a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15,  718787259);
      b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
  }
  return rh(a)+rh(b)+rh(c)+rh(d);
}

export default { stringToColor, md5, tooltipFooter };

Result: image

ussef11 commented 7 months ago
         plugins: {
                    tooltip: {
                        callbacks:{
                            label :((tooltipItem ,data)=>{
                                console.log("label" , tooltipItem.dataset.label)
                                if(tooltipItem.dataset.label=== 'poids' && this.unitepoids === 'mm'){
                                    return ''
                                }

                            })
                        }
                    }, 
                 }