javascriptd3.jsplottable

How do I check the original value in label formatter using plottable.js?


I have a relatively simple bar chart using Plottable. All the bars start at 0 but the third bar can be negative and changes colors. To display in this way, for the third bar I use Math.abs(value). The problem is when it is negative I need to have the label actually display that it is negative as well. How would I set the label to display a negative number properly here but keeping the same format of the graph?

note: The script can be run a few times to see both the positive and negative versions of the profit bar.

function HBarChart(key, state) {
  var colors = {
    Sales: '#00c786',
    Expenses: '#f5a623',
    Profit: '#009464',
    Profit_neg: '#ea655d'
  };

  this._eleSelector = 'svg#' + key;
  this.xScale = new Plottable.Scales.Linear().domainMin(0);
  this.yScale = new Plottable.Scales.Category();
  this.xAxis = new Plottable.Axes.Numeric(this.xScale, 'bottom');
  this.yAxis = new Plottable.Axes.Category(this.yScale, 'left')
    .innerTickLength(0)
    .endTickLength(0);
  
  this.plot = new Plottable.Plots.Bar('horizontal')
    .x(function(d) {
      return Math.abs(d.x);
    }, this.xScale)
    .y(function(d) {
      return d.category;
    }, this.yScale)
    .attr("fill", function(d) {
      return colors[d.category + (d.x < 0 ? '_neg' : '')];
    })
    .labelFormatter(function(d) {
      return Intl.NumberFormat('en-US', {
        minimumFractionDigits: 2,
        maximumFractionDigits: 2
      }).format(d);
    })
    .labelsEnabled(true);
  
  this.setData(state);
  this.chart = new Plottable.Components.Table([
    [this.yAxis, this.plot]
  ]);
  
  this.chart.renderTo(this._eleSelector);
}

HBarChart.prototype.setData = function(state) {
  var dummySet = {
    revenue: Math.random() * 10000 + 1000,
    expense: Math.random() * 10000 + 1000
  };
  
  dummySet.profit = dummySet.revenue - dummySet.expense;
  
  var dataSet = [{
    category: 'Sales',
    x: dummySet.revenue
  }, {
    category: 'Expenses',
    x: dummySet.expense
  }, {
    category: 'Profit',
    x: dummySet.profit
  }];
  if (this.dataset) {
    this.plot.removeDataset(this.dataset);
  }
  
  this.dataset = new Plottable.Dataset(dataSet);
  this.plot.addDataset(this.dataset);

}

new HBarChart('test');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/2.0.0/plottable.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/2.0.0/plottable.min.js"></script>

<svg id="test"></svg>


Solution

  • You can override the xScale.scale function to calculate te range value from an absolute domain value:

      var origXScale = this.xScale.scale.bind(this.xScale);
      this.xScale.scale = function(d) { 
        return origXScale(Math.abs(d)); 
      };
    

    and let the x accessor return original values:

     this.plot = new Plottable.Plots.Bar('horizontal')
      .x(function(d) {
        return d.x;
      }, this.xScale)
    

    This way your formatter will also get the original values.

    Full example:

    function HBarChart(key, state) {
      var colors = {
        Sales: '#00c786',
        Expenses: '#f5a623',
        Profit: '#009464',
        Profit_neg: '#ea655d'
      };
    
      this._eleSelector = 'svg#' + key;
      this.xScale = new Plottable.Scales.Linear().domainMin(0);
      var origXScale = this.xScale.scale.bind(this.xScale);
      this.xScale.scale = function(d) { 
           return origXScale(Math.abs(d)); 
      };
      this.yScale = new Plottable.Scales.Category();
      this.xAxis = new Plottable.Axes.Numeric(this.xScale, 'bottom');
      this.yAxis = new Plottable.Axes.Category(this.yScale, 'left')
        .innerTickLength(0)
        .endTickLength(0);
      
      this.plot = new Plottable.Plots.Bar('horizontal')
        .x(function(d) {
          return d.x;
        }, this.xScale)
        .y(function(d) {
          return d.category;
        }, this.yScale)
        .attr("fill", function(d) {
          return colors[d.category + (d.x < 0 ? '_neg' : '')];
        })
        .labelFormatter(function(d) {
          return Intl.NumberFormat('en-US', {
            minimumFractionDigits: 2,
            maximumFractionDigits: 2
          }).format(d);
        })
        .labelsEnabled(true);
      
      this.setData(state);
      this.chart = new Plottable.Components.Table([
        [this.yAxis, this.plot]
      ]);
      
      this.chart.renderTo(this._eleSelector);
    }
    
    HBarChart.prototype.setData = function(state) {
      var dummySet = {
        revenue: Math.random() * 10000 + 1000,
        expense: Math.random() * 10000 + 1000
      };
      
      dummySet.profit = dummySet.revenue - dummySet.expense;
      
      var dataSet = [{
        category: 'Sales',
        x: dummySet.revenue
      }, {
        category: 'Expenses',
        x: dummySet.expense
      }, {
        category: 'Profit',
        x: dummySet.profit
      }];
      if (this.dataset) {
        this.plot.removeDataset(this.dataset);
      }
      
      this.dataset = new Plottable.Dataset(dataSet);
      this.plot.addDataset(this.dataset);
    
    }
    
    new HBarChart('test');
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/2.0.0/plottable.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/2.0.0/plottable.min.js"></script>
    
    <svg id="test"></svg>