4
votes

On a horizontal bar chart, being fed from an external data source, I need to change the data values to text (for display purposes) on the valueAxis labels and tooltip text.

I've managed to find a way to change the valueAxis labels, which seems to work (although not sure if it's best practice?).

But I'm struggling to change the text on the valueAxis tooltip and series tooltip, as shown in the screenshot, where "3" needs to display as "FAIRLY IMPORTANT" as well as the other values when hovering over the chart.

EDIT

I've added this code which calls the amCharts adapter, and returns the valueAxis text as XXXXXX, but the target values are not working as they should.

I can't see where I'm going wrong though?

// This Adapter is to change the text in the valueAxis Tooltip
valueAxis.adapter.add("getTooltipText", (text, target) => {
  if (target.dataItem && (target.dataItem.value == 0)) {
    return "NOT NEEDED";
  }
  if (target.dataItem && (target.dataItem.value == 1)) {
    return "UNSURE";
  }
  if (target.dataItem && (target.dataItem.value == 3)) {
    return "NICE TO HAVE";
  }
  if (target.dataItem && (target.dataItem.value == 5)) {
    return "MUST HAVE";
  }
  else {
    return "XXXXXX";
  }
});

Quick summary below.

Chart Screenshot

The data source comes in as...

    "Name": "Photographer",
    "Category A": 3,
    "Category B": 5,
    "Category C": 1,
    "Category D": 0,
    "Category E": 3

On the chart, I want to display the numeric values as text...

    0 to display as FAIRLY IMPORTANT
    1 to display as NOT SURE
    3 to display as FAIRLY IMPORTANT
    5 to display as VERY IMPORTANT

Here's my code (so far) which I need to clean up a bit once I've finished working on it...

// Create chart instance
var chart = am4core.create("CHARTDIV", am4charts.XYChart);

// base64 encoded version of sample data as a data URI
function dataURI() {
  return "data:application/json;base64,ewogICJyZWNvcmRzIjogWwogICAgewogICAgICAiaWQiOiAxLAogICAgICAiZmllbGRzIjogewogICAgICAgICJOYW1lIjogIlBob3RvZ3JhcGhlciIsCiAgICAgICAgIkNhdGVnb3J5IEEiOiAzLAogICAgICAgICJDYXRlZ29yeSBCIjogNSwKICAgICAgICAiQ2F0ZWdvcnkgQyI6IDEsCiAgICAgICAgIkNhdGVnb3J5IEQiOiAwLAogICAgICAgICJDYXRlZ29yeSBFIjogMwogICAgICB9LAogICAgICAiY3JlYXRlZFRpbWUiOiAiMjAxOC0wNC0xMlQxNzoyNzowNi4wMDBaIgogICAgfQogIF0KfQ==";
}

// Add data
chart.dataSource.url = dataURI(); //fake URL for demonstration purposes

// Always needed when using Airtable JSON output for amCharts compatibility
chart.dataSource.events.on("parseended", function(ev) {
  // parsed data is assigned to data source's `data` property
  ev.target.data = ev.target.data.records.map(function(dataItem) {
    return {
      "Category A": dataItem.fields["Category A"],
      "Category B": dataItem.fields["Category B"],
      "Category C": dataItem.fields["Category C"],
      "Category D": dataItem.fields["Category D"],
      "Category E": dataItem.fields["Category E"],
      "Name": dataItem.fields.Name
    }
  });
}); 

// Needed when the Airtable rows and columns need transposing
chart.dataSource.events.on("parseended", function(ev) {
  var questionMap = {}; //lookup table to map questions to data elements

  ev.target.data.forEach(function(item) {
    Object.keys(item).forEach(function(key) {
      if (key.indexOf('Name') == -1) { //act on non-response keys
        if (!questionMap[key]) {
          questionMap[key] = {
            "Name": key
          }; //create an object containing the name/question pair if it doesn't exist
        }
        questionMap[key][item.Name] = item[key]; // assign response+value to the object (e.g. "Must Have": 75)
      }
    });
  });
  //remap lookup table as array
  ev.target.data = Object.keys(questionMap).map(function(question) {
    return questionMap[question];
  });
});

// Set cell size in pixels
var cellSize = 50;
chart.events.on("datavalidated", function(ev) {

  // Get objects of interest
  var chart = ev.target;
  var categoryAxis = chart.yAxes.getIndex(0);

  // Calculate how we need to adjust chart height
  var adjustHeight = chart.data.length * cellSize - categoryAxis.pixelHeight;

  // get current chart height
  var targetHeight = chart.pixelHeight + adjustHeight;

  // Set it on chart's container
  chart.svgContainer.htmlElement.style.height = targetHeight + "px";
});

// Create category axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;

// Bar or column width for category axis
categoryAxis.renderer.cellStartLocation = 0.1;
categoryAxis.renderer.cellEndLocation = 0.9;

// Create value axes
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
valueAxis.numberFormatter.numberFormat = "#";
valueAxis.min = 0;
valueAxis.max = 6; 
valueAxis.cursorTooltipEnabled = true;

// Create value axes - LABELS DISABLED
valueAxis.renderer.grid.template.disabled = true;
valueAxis.renderer.labels.template.disabled = true;

// Create value axes - DEFINE GRID LINES
function createGrid(value) {
  var range = valueAxis.axisRanges.create();
  range.value = value;
  range.label.text = "";
}

createGrid(0);
createGrid(1);
createGrid(3);
createGrid(5);
createGrid(6);

// Create value axes - DEFINE GRID LABELS
function createLabel(value, label, location) {
  var range = valueAxis.axisRanges.create();
  range.value = value;
  range.grid.strokeOpacity = 0;
  range.label.location = location | 0;
  range.label.dataItem.text = label;
  range.label.fillOpacity = 1;
}

createLabel("0", "NOT IMPORTANT");
createLabel("1", "NOT SURE");
createLabel("3", "FAIRLY IMPORTANT");
createLabel("5", "VERY IMPORTANT");


// This Adapter is to change the text in the valueAxis Tooltip
valueAxis.adapter.add("getTooltipText", (text, target) => {
  if (target.dataItem && (target.dataItem.value >= 0)) {
    return "NOT IMPORTANT";
  }
  if (target.dataItem && (target.dataItem.value == 1)) {
    return "NOT SURE";
  }
  if (target.dataItem && (target.dataItem.value == 3)) {
    return "FAIRLY IMPORTANT";
  }
  if (target.dataItem && (target.dataItem.value == 5)) {
    return "VERY IMPORTANT";
  }
  else {
    return "XXXXXX";
  }
});


// Create series
function createSeries(field, name) {

  // Set up series
  var series = chart.series.push(new am4charts.ColumnSeries());
  series.name = name;
  series.dataFields.valueX = field;
  series.dataFields.categoryY = "Name";
  series.sequencedInterpolation = true;
  
  // Make it stacked
  series.stacked = true;
  
  // Configure columns
  series.tooltip.getFillFromObject = false;
  series.tooltip.background.fill = am4core.color("#272d3a");
  series.tooltip.background.strokeWidth = 0;
  series.tooltipText = "{name}: [bold]{valueX}[/]";
  series.tooltip.paddingTop = -1;
  series.tooltip.fontSize = 16;

  // Add label  
  var bullet = series.bullets.push(new am4charts.CircleBullet());
  bullet.circle.stroke = am4core.color("#fff");
  bullet.circle.strokeWidth = 2;
  
  return series;
}

var series1 = createSeries("Photographer", "Photographer");


// Labels configuration for category axis
var categoryLabel = categoryAxis.renderer.labels.template;
categoryLabel.fill = am4core.color("#272d3a");
categoryLabel.fontSize = 16;
categoryLabel.fontWeight = 700;
categoryLabel.truncate = true;

// Labels configuration for value axis
var valueLabel = valueAxis.renderer.labels.template;
valueLabel.fill = am4core.color("#272d3a");
valueLabel.fontSize = 16;

// Tooltip configuration for category axis
var categoryTooltip = categoryAxis.tooltip;
categoryTooltip.background.fill = am4core.color("#272d3a");
categoryTooltip.fontSize = 16;
categoryTooltip.fontWeight = 700;
categoryTooltip.background.strokeWidth = 0;
categoryTooltip.background.cornerRadius = 3;
categoryTooltip.background.pointerLength = 4;
categoryTooltip.dy = 0;
categoryTooltip.dx = -2;
categoryTooltip.paddingTop = 0;

// Tooltip configuration for value axis
var valueTooltip = valueAxis.tooltip;
valueTooltip.background.fill = am4core.color("#272d3a");
valueTooltip.fontSize = 16;
valueTooltip.background.strokeWidth = 0;
valueTooltip.background.cornerRadius = 3;
valueTooltip.background.pointerLength = 4;
valueTooltip.dy = 2;
valueTooltip.dx = 0;
valueTooltip.paddingTop = 0;

// Tooltip configuration for value axis - DISABLED
valueTooltip.disabled = false;

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "zoomXY";
chart.cursor.lineX.disabled = true;
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>

<div id="CHARTDIV" style="width: 100%; height: 600px;"></div>
2

2 Answers

0
votes

I was struggling with something similar for that category axis, and found this tutorial: https://www.amcharts.com/docs/v4/tutorials/override-categoryaxis-tooltip-content-with-an-adapter/ that might help.

The relevant part:

categoryAxis.tooltip.label.adapter.add("text", function(text, target) {
    if (target.dataItem) {
        return target.dataItem.dataContext.axisLabel;
    }
    return text;
});
0
votes

We were able to solve this like so:

import { ValueAxis } from "@amcharts/amcharts4/charts";

export default class CategoryValueAxis extends ValueAxis {
  formatLabel(value) {
    if (this.labelFunction) {
      return this.labelFunction(value);
    }

    return super.formatLabel(value);
  }
}

Simply define your labelFunction on the CategoryValueAxis when you inject it into the chart axes.