Search code examples
javascriptamchartsamcharts4

amcharts4 proper way to handle zero values on logarithmic value axis


I want to display some data with high variety in amcharts4, so I like to use the logarithmic scale on the value axis. Unfortunately the data contain some zero values, which of course can't be displayed with logarithmic scale.

I tried to change the zero values to 1 before rendering the chart, which would work, but now the values are not correct any more.

data.forEach(item => {
  for (const key in item) {
    if (item[key] === 0) {
      item[key] = 1;
    }
  }
});

Is there any better way to handle zero values with logarithmic value axis, that I can display the correct data?

Here is a code pen, which shows my current solution.


Solution

  • Edit

    As of version 4.9.34, treatZeroAs is officially supported. Just set it on the value axis to the desired value to remap your zero values to:

    valueAxis.treatZeroAs = 0.1;
    

    Updated codepen.

    The below workaround isn't needed anymore, but you may find the value axis adapter snippet helpful in changing the first label when using treatZeroAs.

    Old method - pre 4.9.34

    There doesn't appear to be a direct equivalent to v3's treatZeroAs property, which automatically handled this sort of thing. Pre-processing the data is one step, but you can also copy the original value into a separate object property and use a series tooltip adapter to dynamically show your actual value:

    data.forEach(item => {
      for (const key in item) {
        if (item[key] <= 0) {
          item[key+"_actual"] = item[key]; //copy the original value into a different property
          item[key] = 1;
        }
      }
    });
    // ...
    
    //display actual data that was re-mapped if it exists
    chart.series.each((series) => {
      series.adapter.add("tooltipText", (text, target) => {
        if (target.dataFields) {
          let valueField = target.dataFields.valueY;
          let tooltipData = target.tooltipDataItem;
          if (tooltipData.dataContext[valueField + "_actual"] !== undefined) { 
            return '{' + valueField + '_actual}';
          }
          else {
            return text;
          }
        }
        else {
          return text;
        }
      })
    });
    

    If you want to fake a zero label, you can use an adapter for that as well since your smallest value in this case will be 1:

    //fake the zero axis label
    valueAxis.renderer.labels.template.adapter.add("text", (text) => {
      if (text === "1") {
        return "0"
      }
      else {
        return text;
      }
    })
    

    Codepen