Wednesday, September 21, 2016

Adding legends to selfmade barchart

Leave a Comment

I know you are probably wondering self-made barchart? Why not an existing library? Well I hate to use files with 20000 lines of code while only 500 are necessary.

Oh and it's fun :) The main objective is that I'll be using this script for an app I'll be making using Phonegap. So the lower the size, the better.

So the idea is to achieve the following: enter image description here

I've been able to to draw the bars, make sure they are of equal width and have their height dependent on the height of the parent container. As you'll see in the code below I also added a font-size to the options. As some chart will expand around 300px of height (which would be using the default 16px for example). And some only 50px with a font-size of 12 or less. So I reduced the bar ChartContainer by 3 x the fontsize (+ rest) to make sure there is enough space for the top (amounts) & bottom (legends + title)

Now I'm not entirely sure how to add and center the amounts. I tried searching existing chart libraries to check on how it all has been rendered, unfortunately they all use canvasses or SVG containers. Any suggestions?

/* dataset     ------------------       add legends     add result / amount     add bottom-border: 8px extra to both sides?     add chart name     */    (function ($) {        var methods = {          init : function(options) {              return this.each(function() {                    var $this = $(this),                      dataset = options.dataset,                      fontSize = options.fontSize,                      widthOfContainer = $this.width(),                      heightOfContainer = $this.height() - (3 * (fontSize + 4)), // make room for title (bottom), legend (bottom), amounts (top)                      widthOfBar = parseInt(widthOfContainer / options.dataset.length) - 2,                      bar;                    $this.bind('draw', function(e) {                      $this.empty();                        var maxValueInDataset = Math.max.apply(Math, dataset.map(function(o){return o.a;})),                          heightPerUnit = parseInt(heightOfContainer / maxValueInDataset);                      for (var i = 0; i < dataset.length; i++) {                          bar = $(document.createElement('div'));                          bar.addClass('bar');                          bar.css({                              'height': parseInt(dataset[i].a * heightPerUnit) + 'px',                              'width': parseInt(widthOfBar) + 'px',                              'margin-left': parseInt(i * 2 + i * widthOfBar) + 'px',                              'bottom': 2 * (fontSize + 4)                          });                          $this.append(bar);                      }                  });                    $this.trigger('draw');              });          },          draw : function(n) {              $(this).trigger('draw');          }      };        $.fn.chart = function(methodOrOptions) {          if ( methods[methodOrOptions] ) {              return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));          } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {              // Default to "init"              return methods.init.apply( this, arguments );          } else {              $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.tooltip' );          }      };        $(document).ready(function(){          $('div.barchart').chart({              // Add font-size?              fontSize: 14,              name: 'mana cost',              dataset: [                  {a: 2, label: '0'},                  {a: 8, label: '1'},                  {a: 9, label: '2'},                  {a: 4, label: '3'},                  {a: 7, label: '4'},                  {a: 3, label: '5'},                  {a: 1, label: '6'},                  {a: 1, label: '7'},                  {a: 2, label: '8'},                  {a: 5, label: '9'}              ]          });      });  }( jQuery ));
/* Barchart     ========================================================================== */    .barchart {    color: black;  }    /* Bar     ========================================================================== */    .bar {    position: absolute;    height: 0px;    width: 0px;    margin-left: 0px;    bottom: 0px;    background: black;  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <div style="padding: 20px;">              <div class="barchart" style="height: 100px; position: relative"></div>          </div>

2 Answers

Answers 1

Nice chart! It looks clean. I know what you mean. I spent months trying to manipulate the layout of bxSlider then I realized it was less code to write my own. Here's an attempt at answering your query. I've made it width responsive by using percentages (don't worry; it's easy to change back), added an extra css class for the legend, values, and count, and modified your plugin to include your name option (called legend). These bits are then just formatted and appended. Hope this helps.

/* dataset     ------------------       add legends     add result / amount     add bottom-border: 8px extra to both sides?     add chart name     */    (function ($) {        var methods = {          init : function(options) {              return this.each(function() {                    var $this = $(this),                      dataset = options.dataset,                      fontSize = options.fontSize,                      legend = options.name,                      widthOfContainer = $this.width(),                      heightOfContainer = $this.height() - (3 * (fontSize + 4)), // make room for title (bottom), legend (bottom), amounts (top)                      widthOfBar = parseInt(widthOfContainer / options.dataset.length) - 2,                      widthOfBarPer = (widthOfBar / widthOfContainer) *100,                      bar;                    $this.bind('draw', function(e) {                      $this.empty();                        var maxValueInDataset = Math.max.apply(Math, dataset.map(function(o){return o.a;})),                          heightPerUnit = parseInt(heightOfContainer / maxValueInDataset);                      for (var i = 0; i < dataset.length; i++) {                      		var dataVal = dataset[i].a;                          bar = $(document.createElement('div'));                          bar.addClass('bar');                          bar.css({                              'height': parseInt( dataVal * heightPerUnit) + 'px',                              'width': widthOfBarPer + '%', // percentages to make more responsive?                              'margin-left': (i + i * widthOfBarPer ) + '%', // no need to parseInt as you have already on widthOfBar. now your chart stretches with the width .                              'bottom': 2 * (fontSize + 4)                          });                          bar.append('<p class="count">'+ i +'</p>');	// defines the bar count, this could be dataset[i].label but if you just use i then you don't need to type it out for each bar?                          bar.append('<p class="value">'+ dataVal +'</p>');	// defines the bar value                          $this.append(bar); // adds the bar count                      }                      var chartHeight = $this.height();                      $('.bar .count').css({ bottom: fontSize - chartHeight * 0.5 });                      $('.bar .value').css({ bottom: chartHeight * 0.5 - fontSize });                      if(legend){  												legend = '<p class="legend">'+legend+'</p>';  											  $this.append(legend);                    //      $this.css({ border: '1px solid #f90'}); // temp to see the current chart size                          $this.find('.legend').css({ top: chartHeight - fontSize });                      }                  });                    $this.trigger('draw');              });          },          draw : function(n) {              $(this).trigger('draw');          }      };        $.fn.chart = function(methodOrOptions) {          if ( methods[methodOrOptions] ) {              return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));          } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {              // Default to "init"              return methods.init.apply( this, arguments );          } else {              $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.tooltip' );          }      };        $(document).ready(function(){          $('div.barchart').chart({              // Add font-size?              fontSize: 14,              name: 'mana cost',              dataset: [                  {a: 2, label: '0'},                  {a: 8, label: '1'},                  {a: 9, label: '2'},                  {a: 4, label: '3'},                  {a: 7, label: '4'},                  {a: 3, label: '5'},                  {a: 1, label: '6'},                  {a: 1, label: '7'},                  {a: 2, label: '8'},                  {a: 5, label: '9'}              ]          });      });  }( jQuery ));
/* Barchart     ========================================================================== */    .barchart {    color: black;  }    /* Bar     ========================================================================== */    .bar {    position: absolute;    height: 0px;    width: 0px;    margin-left: 0px;    bottom: 0px;    background: black;  }    .count, .value{    z-index: 7;    position: absolute;    text-align: center;    width: 100%;  }    .legend{    position: relative;    text-align: center;    width: 100%;  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>  <div style="padding: 20px;">              <div class="barchart" style="height: 100px; position: relative"></div>          </div>

Answers 2

Thumbs up for avoiding unnecessary amounts of code—a clean barchart is a perfect example of not needing a massive library.

If you use a <table> as the basic structure to create your barchart from, you get easy access to the formatting options you want—resulting in less code:

  • One row for the values, one row for the labels
  • Bars can be made by styling the border-bottom of each value cell
  • Text can be centered within table cells
  • Adding a <caption> for the legend, which is centered by default, and is easily positioned below the table with the caption-side property
  • Styling the vertical-align property of the value cells allows values to be positioned directly above the bars (as in the demo below) or lined up on top (as in your illustration)—this is controlled in line 20 in the js demo code below.

After all, a barchart is just a vizualisation of tabular data, so using a table makes sense.

A working demo (in about 30 lines of vanilla js and a few lines of css, which you can easily adapt to your jquery approach if required):

function barchart(containerId, options) {    var i,         html,        valueRow = '',         labelRow = '',        data = options.dataset,        maxBarHeight = 60, /* in px, could be set from options */        barWidth = 20,     /* in px, could be set from options */        maxValue = Math.max.apply(          Math,          data.map(function(o) {            return o.a;          })        );    for(i = 0; i < data.length; i++){      labelRow += '<td>' + data[i].label + '</td>';      valueRow += '<td style="border-bottom:' +          (data[i].a * maxBarHeight / maxValue) +          'px solid black;vertical-align:' +          'bottom' + /* set to 'top' to get value labels lined up */          ';width: ' +          barWidth + 'px">' +          data[i].a + '</td>';    }    html = '<table class="barchart" ' +       'style="font-size:' + options.fontSize + 'px">' +      '<caption>' + options.name + '</caption>' +       '<tr>' + valueRow + '</tr>' +       '<tr>' + labelRow + '</tr>' +       '</table>';    document.getElementById(containerId)      .innerHTML = html;  }    /* create a barchart */    barchart('testdiv', {    fontSize: 14,    name: 'mana cost',    dataset: [      {a: 2, label: '0'},      {a: 8, label: '1'},      {a: 9, label: '2'},      {a: 4, label: '3'},      {a: 7, label: '4'},      {a: 3, label: '5'},      {a: 1, label: '6'},      {a: 1, label: '7'},      {a: 2, label: '8'},      {a: 5, label: '9'}    ]  });
.barchart td{    text-align: center;  }    .barchart{    font-family: 'arial narrow', sans-serif;    caption-side: bottom;  }
<div id="testdiv"></div>

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment