Forcing tooltip position on Amcharts

1.7k views Asked by At

In short, I would like to force tooltip to be shown always above the hovered point on line series, even if it goes outside chart area. Tooltip is joint for all series. Example can be seen here:

const {useRef, useState} = React;
const CHART_CONTAINER = 'campaign-budget-chart';
const CHART_COLORS = {
  value1: '#05a8fa',
  value2: '#ed3434',
  value3: '#0ec76a',
}

function getRandomNumber(max){
  return Math.floor(Math.random() * Math.floor(max))
}

const initialData = [{
  timestamp: new Date(2020, 09, 25),
  value1: 0,
  value2: getRandomNumber(50),
  value3: getRandomNumber(250),
},{
  timestamp: new Date(2020, 09, 26),
  value1: getRandomNumber(100),
  value2: getRandomNumber(50),
  value3: getRandomNumber(250),
},{
  timestamp: new Date(2020, 09, 27),
  value1: getRandomNumber(100),
  value2: getRandomNumber(50),
  value3: getRandomNumber(250),
},
           {
  timestamp: new Date(2020, 09, 28),
  value1: getRandomNumber(100),
  value2: getRandomNumber(50),
  value3: getRandomNumber(250),
}];
let i = 0;

function BudgetChart() {
  const chartRef = useRef(null);
  const [data, setData] = useState(initialData);
  const [cursor, setCursor] = React.useState({ x: 0, y: 0 });
  const [cursorVisible, setCursorVisible] = React.useState(false);

  function createSeries(
    fieldX,
    fieldY,
    name,
    lineColor,
  ) {
    if (!chartRef.current) return;
    console.log('Create series');
    // Init series
    let series = chartRef.current.series.push(new am4charts.LineSeries());
    series.name = name;
    series.dataFields.valueY = fieldY;
    series.dataFields.dateX = fieldX;
    series.strokeWidth = 3;
    series.stroke = am4core.color(lineColor);
    series.tooltip.pointerOrientation = 'down';
    series.tooltip.background.filters.clear(); // remove shadow
    series.tooltip.getFillFromObject = false;
    series.tooltip.background.fill = am4core.color('#2a2b2e');
    series.tooltip.background.stroke = am4core.color('#2a2b2e');
    series.tooltip.label.fontSize = 12;
    series.tooltip.background.pointerLength = 0;
    series.tooltip.dy = -5;
    series.tooltipText = '{valueY}';
    series.tensionX = 0.8;
    series.showOnInit = false;

    // Add bullet for optimization
    let circleBullet = series.bullets.push(new am4charts.CircleBullet());
    circleBullet.circle.radius = 6;
    circleBullet.circle.fill = lineColor;
    circleBullet.circle.stroke = am4core.color('#fff');
    circleBullet.circle.strokeWidth = 3;
    circleBullet.propertyFields.disabled = 'optimizationTooltipDisabled';
      // Set up tooltip
  series.adapter.add("tooltipText", function(ev) {
    var text = "[bold]{dateX}[/]\n"
    chartRef.current.series.each(function(item) {
      text += "[" + item.stroke.hex + "]●[/] " + item.name + ": {" + item.dataFields.valueY + "}\n";
    });
    return text;
  });

    // Bullet shadow
    let shadow = circleBullet.filters.push(new am4core.DropShadowFilter());
    shadow.opacity = 0.1;

  }

  React.useEffect(() => {
    if (!chartRef.current) {
      chartRef.current = am4core.create(CHART_CONTAINER, am4charts.XYChart);

      chartRef.current.paddingLeft = 0;

      // Add date axis
      let dateAxis = chartRef.current.xAxes.push(new am4charts.DateAxis());
      dateAxis.renderer.labels.template.fontSize = 12;
      dateAxis.renderer.labels.template.fill = am4core.color(
        'rgba(183,186,199,0.8)'
      );
      dateAxis.renderer.grid.template.strokeOpacity = 0;

      // Add value axis
      let valueAxis = chartRef.current.yAxes.push(new am4charts.ValueAxis());
      valueAxis.renderer.grid.template.stroke = am4core.color(
        '#f0f2fa'
      );
      valueAxis.renderer.grid.template.strokeOpacity = 1;
      valueAxis.renderer.labels.template.fill = am4core.color(
        'rgba(183,186,199,0.8)'
      );
      valueAxis.renderer.labels.template.fontSize = 12;

      // Add cursor
      chartRef.current.cursor = new am4charts.XYCursor();
      chartRef.current.cursor.maxTooltipDistance = -1;

      // Add legend
      chartRef.current.legend = new am4charts.Legend();
      chartRef.current.legend.position = 'bottom';
      chartRef.current.legend.contentAlign = 'left';
      chartRef.current.legend.paddingTop = 20;

      // Disable axis lines
      chartRef.current.cursor.lineX.disabled = true;
      chartRef.current.cursor.lineY.disabled = true;

      // Disable axis tooltips
      dateAxis.cursorTooltipEnabled = false;
      valueAxis.cursorTooltipEnabled = false;

      // Disable zoom
      chartRef.current.cursor.behavior = 'none';

      chartRef.current.cursor.events.on('cursorpositionchanged', function(ev) {
        let xAxis = ev.target.chart.xAxes.getIndex(0);
        let yAxis = ev.target.chart.yAxes.getIndex(0);
        setCursor({
          x: xAxis.toAxisPosition(ev.target.xPosition),
          y: yAxis.toAxisPosition(ev.target.yPosition),
        });
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  
  // Load data into chart
  React.useEffect(() => {
    console.log('data ', data)
    if (chartRef.current) {
      chartRef.current.data = data;
      
      Object.keys(data[0]).forEach(key => {
        if(key === 'timestamp') return;
        createSeries(
        'timestamp',
        key,
        key,
        CHART_COLORS[key]
      );
      })
    }
  }, [data]);

  // Handle component unmounting, dispose chart
  React.useEffect(() => {
    return () => {
      chartRef.current && chartRef.current.dispose();
    };
  }, []);
  
  function handleRemoveSeries(){
    setData(data.map(item => ({timestamp: item.timestamp, value1: item.value1, value2: item.value2})))
  }

  return (
    <>
      <button onClick={handleRemoveSeries}>Remove 3rd series</button>
      <div
        id={CHART_CONTAINER}
        style={{
          width: '100%',
          height: '350px',
          marginBottom: '50px',
        }}
      />
    </>
  );
}

ReactDOM.render(<BudgetChart />, document.getElementById('app'));

For all values near the top of the chart, tooltip is trying to squeeze itself inside chart area. According to the docs:

IMPORTANT: in some situations, like having multiple tooltips stacked for multiple series, the "up" and "down" values might be ignored in order to make tooltip overlap algorithm work.

Is there a way to disable the tooltip overlap algorithm work?

1

There are 1 answers

0
idjuradj On

As of version 4.10.8 (released after posting the initial question) there is a property ignoreBounds on series tooltip.

More on ignoreBounds on amchart docs