Gokulnath Kumar
Gokulnath Kumar

Reputation: 169

Auto-truncate long category axis labels but display the full label in legend?

I am using amchart for a graph. Below is the code,

var chart = AmCharts.makeChart("chartdiv", {
    "theme": "light",
    "type": "serial",
    "startDuration": 2,
    "dataProvider": [{
        "country": "This is Sample Data with long label",
        "visits": 4025,
        "color": "#FF0F00"
    }, {
        "country": "This is Sample Data with long label1",
        "visits": 1882,
        "color": "#FF6600"
    }, {
        "country": "This is Sample Data with long label2",
        "visits": 1809,
        "color": "#FF9E01"
    }, {
        "country": "This is Sample Data with long label3",
        "visits": 1322,
        "color": "#FCD202"
    }, {
        "country": "This is Sample Data with long label4",
        "visits": 1122,
        "color": "#F8FF01"
    }, {
        "country": "This is Sample Data with long label5",
        "visits": 1114,
        "color": "#B0DE09"
    }, {
        "country": "This is Sample Data with long label6",
        "visits": 984,
        "color": "#04D215"
    }, {
        "country": "This is Sample Data with long label7",
        "visits": 711,
        "color": "#0D8ECF"
    }, {
        "country": "This is Sample Data with long label8",
        "visits": 665,
        "color": "#0D52D1"
    }, {
        "country": "This is Sample Data with long label9",
        "visits": 580,
        "color": "#2A0CD0"
    }, {
        "country": "This is Sample Data with long label10",
        "visits": 443,
        "color": "#8A0CCF"
    }, {
        "country": "This is Sample Data with long label11",
        "visits": 441,
        "color": "#CD0D74"
    }, {
        "country": "This is Sample Data with long label12",
        "visits": 395,
        "color": "#754DEB"
    }, {
        "country": "This is Sample Data with long label13",
        "visits": 386,
        "color": "#DDDDDD"
    }, {
        "country": "This is Sample Data with long label14",
        "visits": 338,
        "color": "#333333"
    }],
    "valueAxes": [{
        "position": "left",
        "axisAlpha":0,
        "gridAlpha":0
    }],
    "graphs": [{
        "balloonText": "[[category]]: <b>[[value]]</b>",
        "colorField": "color",
        "fillAlphas": 0.85,
        "lineAlpha": 0.1,
        "type": "column",
        "topRadius":1,
        "valueField": "visits"
    }],
    "depth3D": 40,
    "angle": 30,
    "chartCursor": {
        "categoryBalloonEnabled": false,
        "cursorAlpha": 0,
        "zoomable": false
    },
    "categoryField": "country",
    "categoryAxis": {
        "gridPosition": "start",
        "axisAlpha":0,
        "gridAlpha":0

    },
  "labelFunction": function(label, item, axis) {
      var chart = axis.chart;
      if ( (chart.realWidth <= 300 ) && ( label.length > 5 ) )
        return label.substr(0, 5) + '...';
      if ( (chart.realWidth <= 500 ) && ( label.length > 10 ) )
        return label.substr(0, 10) + '...';
      return label;
    },
  "legend": {
    "useGraphSettings": true
  },
    "export": {
        "enabled": true
     }

}, 0);

However the Xaxis label is very lenghy, I wanted to auto truncate the long category axis labels like this example and also enable legend. But enabling legend doesn't work, also auto truncating doesn't seem to work. Could someone help me out here? Thanks in advance.

Here is the link to codepen [1].

[1] https://codepen.io/gknathkumar/pen/OxKGev

Upvotes: 1

Views: 2199

Answers (3)

xorspark
xorspark

Reputation: 16012

As others have stated, the labelFunction is part of the categoryAxis, so it needs to go in there. I'm partial to the method in kuzyn's implementation, but pick whichever you want.

As for the legend, it is generated by graph objects by design. Since there's one graph object, there's only one marker. Adding a marker for each column requires you add custom code that modifies the legend's data array to generate customized markers. AmCharts has a knowledge base article for generating markers for each column. Relevant code below:

/*
  Plugin to generate legend markers based on category
  and fillColor/lineColor/color field from the chart data by using 
  the legend's custom data array. Also allows for toggling markers
  by completely removing/adding columns from the chart

  The plugin assumes there is  only one graph object. 
*/
AmCharts.addInitHandler(function(chart) { 

  //method to handle removing/adding columns when the marker is toggled
  function handleCustomMarkerToggle(legendEvent) {
      var dataProvider = legendEvent.chart.dataProvider;
      var itemIndex; //store the location of the removed item

      //Set a custom flag so that the dataUpdated event doesn't fire infinitely, in case you have
      //a dataUpdated event of your own
      legendEvent.chart.toggleLegend = true; 
      // The following toggles the markers on and off.
      // The only way to "hide" a column and reserved space on the axis is to remove it
      // completely from the dataProvider. You'll want to use the hidden flag as a means
      // to store/retrieve the object as needed and then sort it back to its original location
      // on the chart using the dataIdx property in the init handler
      if (undefined !== legendEvent.dataItem.hidden && legendEvent.dataItem.hidden) {
        legendEvent.dataItem.hidden = false;
        dataProvider.push(legendEvent.dataItem.storedObj);
        legendEvent.dataItem.storedObj = undefined;
        //re-sort the array by dataIdx so it comes back in the right order.
        dataProvider.sort(function(lhs, rhs) {
          return lhs.dataIdx - rhs.dataIdx;
        });
      } else {
        // toggle the marker off
        legendEvent.dataItem.hidden = true;
        //get the index of the data item from the data provider, using the 
        //dataIdx property.
        for (var i = 0; i < dataProvider.length; ++i) { 
          if (dataProvider[i].dataIdx === legendEvent.dataItem.dataIdx) {
            itemIndex = i;
            break;
          }
        }
        //store the object into the dataItem
        legendEvent.dataItem.storedObj = dataProvider[itemIndex];
        //remove it
        dataProvider.splice(itemIndex, 1);
      }
      legendEvent.chart.validateData(); //redraw the chart
  }

  //check if legend is enabled and custom generateFromData property
  //is set before running
  if (!chart.legend || !chart.legend.enabled || !chart.legend.generateFromData) {
    return;
  }

  var categoryField = chart.categoryField;
  var colorField = chart.graphs[0].lineColorField || chart.graphs[0].fillColorsField || chart.graphs[0].colorField;
  var legendData =  chart.dataProvider.map(function(data, idx) {
    var markerData = {
      "title": data[categoryField] + ": " + data[chart.graphs[0].valueField], 
      "color": data[colorField],
      "dataIdx": idx //store a copy of the index of where this appears in the dataProvider array for ease of removal/re-insertion
    };
    if (!markerData.color) {
      markerData.color = chart.graphs[0].lineColor;
    }
    data.dataIdx = idx; //also store it in the dataProvider object itself
    return markerData;
  });

  chart.legend.data = legendData;

  //make the markers toggleable
  chart.legend.switchable = true;
  chart.legend.addListener("clickMarker", handleCustomMarkerToggle);

}, ["serial"]);

This plugin requires that you set a custom generateFromData flag to true in your legend and nothing else (useGraphSettings is not compatible):

  "legend": { 
    "generateFromData": true //custom property for the plugin
  },

Here's a demo that leverages kuzyn's trim method and the aforementioned plugin:

/*
  Plugin to generate legend markers based on category
  and fillColor/lineColor/color field from the chart data by using 
  the legend's custom data array. Also allows for toggling markers
  by completely removing/adding columns from the chart
  
  The plugin assumes there is  only one graph object. 
*/
AmCharts.addInitHandler(function(chart) { 
  
  //method to handle removing/adding columns when the marker is toggled
  function handleCustomMarkerToggle(legendEvent) {
      var dataProvider = legendEvent.chart.dataProvider;
      var itemIndex; //store the location of the removed item

      //Set a custom flag so that the dataUpdated event doesn't fire infinitely, in case you have
      //a dataUpdated event of your own
      legendEvent.chart.toggleLegend = true; 
      // The following toggles the markers on and off.
      // The only way to "hide" a column and reserved space on the axis is to remove it
      // completely from the dataProvider. You'll want to use the hidden flag as a means
      // to store/retrieve the object as needed and then sort it back to its original location
      // on the chart using the dataIdx property in the init handler
      if (undefined !== legendEvent.dataItem.hidden && legendEvent.dataItem.hidden) {
        legendEvent.dataItem.hidden = false;
        dataProvider.push(legendEvent.dataItem.storedObj);
        legendEvent.dataItem.storedObj = undefined;
        //re-sort the array by dataIdx so it comes back in the right order.
        dataProvider.sort(function(lhs, rhs) {
          return lhs.dataIdx - rhs.dataIdx;
        });
      } else {
        // toggle the marker off
        legendEvent.dataItem.hidden = true;
        //get the index of the data item from the data provider, using the 
        //dataIdx property.
        for (var i = 0; i < dataProvider.length; ++i) { 
          if (dataProvider[i].dataIdx === legendEvent.dataItem.dataIdx) {
            itemIndex = i;
            break;
          }
        }
        //store the object into the dataItem
        legendEvent.dataItem.storedObj = dataProvider[itemIndex];
        //remove it
        dataProvider.splice(itemIndex, 1);
      }
      legendEvent.chart.validateData(); //redraw the chart
  }

  //check if legend is enabled and custom generateFromData property
  //is set before running
  if (!chart.legend || !chart.legend.enabled || !chart.legend.generateFromData) {
    return;
  }
  
  var categoryField = chart.categoryField;
  var colorField = chart.graphs[0].lineColorField || chart.graphs[0].fillColorsField || chart.graphs[0].colorField;
  var legendData =  chart.dataProvider.map(function(data, idx) {
    var markerData = {
      "title": data[categoryField] + ": " + data[chart.graphs[0].valueField], 
      "color": data[colorField],
      "dataIdx": idx //store a copy of the index of where this appears in the dataProvider array for ease of removal/re-insertion
    };
    if (!markerData.color) {
      markerData.color = chart.graphs[0].lineColor;
    }
    data.dataIdx = idx; //also store it in the dataProvider object itself
    return markerData;
  });
  
  chart.legend.data = legendData;
  
  //make the markers toggleable
  chart.legend.switchable = true;
  chart.legend.addListener("clickMarker", handleCustomMarkerToggle);
  
}, ["serial"]);


// keep the data object separate from the call
var dataProvider = [
  {
    country: "This is Sample Data with long label",
    visits: 4025,
    color: "#FF0F00"
  },
  {
    country: "This is Sample Data with long label1",
    visits: 1882,
    color: "#FF6600"
  },
  {
    country: "This is Sample Data with long label2",
    visits: 1809,
    color: "#FF9E01"
  },
  {
    country: "This is Sample Data with long label3",
    visits: 1322,
    color: "#FCD202"
  }
];

var chart = AmCharts.makeChart(
  "chartdiv",
  {
    theme: "light",
    type: "serial",
    startDuration: 2,
    dataProvider: dataProvider,
    valueAxes: [
      {
        position: "left",
        axisAlpha: 0,
        gridAlpha: 0
      }
    ],
    graphs: [
      {
        balloonText: "[[category]]: <b>[[value]]</b>",
        colorField: "color",
        fillAlphas: 0.85,
        lineAlpha: 0.1,
        type: "column",
        topRadius: 1,
        valueField: "visits"
      }
    ],
    depth3D: 40,
    angle: 30,
    chartCursor: {
      categoryBalloonEnabled: false,
      cursorAlpha: 0,
      zoomable: false
    },
    categoryField: "country",
    categoryAxis: {
      gridPosition: "start",
      axisAlpha: 0,
      gridAlpha: 0,
      labelFunction: trimLabel,
    },
    legend: { 
      generateFromData: true //custom property for the plugin
    },
    export: {
      enabled: true
    }
  },
  0
);

// function to trim the labels
function trimLabel(label, item, axis) {
  var chartWidth = axis.chart.realWidth;
  var maxLabelLength = 15; // not counting the dots...

  // trim when the width of the chart is smalled than 300px
  if (chartWidth <= 300 && label.length > 5)
    return label.substr(0, 5) + "...";

  // trim when the width of the chart is smalled than 500px
  if (chartWidth <= 500 && label.length > 10)
    return label.substr(0, 10) + "...";

  // trim when label is longer than maxLabelLength regardless of chart width
  return label.length >= 15 ? label.substr(0, 14) + "...": label;
}
#chartdiv {
  width: 990px;
  height: 365px;
  border-radius: 3px;
  margin: 0px;
  border: 1px dotted #728FCE;
}
<script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="https://www.amcharts.com/lib/3/serial.js"></script>
<script src="https://www.amcharts.com/lib/3/plugins/export/export.min.js"></script>
<link rel="stylesheet" href="https://www.amcharts.com/lib/3/plugins/export/export.css" type="text/css" media="all" />
<script src="https://www.amcharts.com/lib/3/themes/light.js"></script>
<input type="button" value="Set width to 300px" onclick="document.getElementById('chartdiv').style.width='300px';" />
<input type="button" value="Set width to 500px" onclick="document.getElementById('chartdiv').style.width='500px';" />
<input type="button" value="Set width to 700px" onclick="document.getElementById('chartdiv').style.width='700px';" />
<div id="chartdiv"></div>

Note that if you want the labels on the markers trimmed, you'll have to call trim when creating the markers' titles in the initHandler as well.

Upvotes: 2

kuzyn
kuzyn

Reputation: 1995

There are a couple of small mistake in your code:

  • labelFunction is not in categoryAxis
  • the size of your chart never drops below 500px, hence the labels were never trimmed like in the example
  • some of the code could have been put in variables to make is easier to debug

I've separated some of the code, and added a maximum length (15 characters) for labels regardless of the chart width

View the full example on Codepen

// keep the data object separate from the call
var dataProvider = [
  {
    country: "This is Sample Data with long label",
    visits: 4025,
    color: "#FF0F00"
  },
  {
    country: "This is Sample Data with long label1",
    visits: 1882,
    color: "#FF6600"
  },
  {
    country: "This is Sample Data with long label2",
    visits: 1809,
    color: "#FF9E01"
  },
  {
    country: "This is Sample Data with long label3",
    visits: 1322,
    color: "#FCD202"
  }
];

var chart = AmCharts.makeChart(
  "chartdiv",
  {
    theme: "light",
    type: "serial",
    startDuration: 2,
    dataProvider: dataProvider,
    valueAxes: [
      {
        position: "left",
        axisAlpha: 0,
        gridAlpha: 0
      }
    ],
    graphs: [
      {
        balloonText: "[[category]]: <b>[[value]]</b>",
        colorField: "color",
        fillAlphas: 0.85,
        lineAlpha: 0.1,
        type: "column",
        topRadius: 1,
        valueField: "visits"
      }
    ],
    depth3D: 40,
    angle: 30,
    chartCursor: {
      categoryBalloonEnabled: false,
      cursorAlpha: 0,
      zoomable: false
    },
    categoryField: "country",
    categoryAxis: {
      gridPosition: "start",
      axisAlpha: 0,
      gridAlpha: 0,
      labelFunction: trimLabel,
    },
    legend: {
      useGraphSettings: true
    },
    export: {
      enabled: true
    }
  },
  0
);

// function to trim the labels
function trimLabel(label, item, axis) {
  var chartWidth = axis.chart.realWidth;
  var maxLabelLength = 15; // not counting the dots...

  // trim when the width of the chart is smalled than 300px
  if (chartWidth <= 300 && label.length > 5)
    return label.substr(0, 5) + "...";

  // trim when the width of the chart is smalled than 500px
  if (chartWidth <= 500 && label.length > 10)
    return label.substr(0, 10) + "...";

  // trim when label is longer than maxLabelLength regardless of chart width
  return label.length >= 15 ? label.substr(0, 14) + "...": label;
}

Upvotes: 2

shyammakwana.me
shyammakwana.me

Reputation: 5752

make labelFunction like below:

"labelFunction": function(label, item, axis) {

        var chart = axis.chart;
         console.log("CHART:", chart.realWidth, label.length, label );
          if ( ( label.length > 5 ) ){
            console.log("CHARTLABEL:", label.substr(0, 5) + '...');
            return label.substr(0, 7) + '...';            
          }
          if ( ( label.length > 10 ) ){
            return label.substr(0, 10) + '...';
          }
          return label;
        },

And your code was not working because you have to put label function inside categoryAxis

Final working solution : https://codepen.io/anon/pen/aLerBZ?editors=0010

Upvotes: 2

Related Questions