files

index.htmlwith an internal stylesheet
bulleT-chart.jschart definition
bulleTs-chart.jsmixin
bulleT-app.jssetup data, chart width and hight, etc...

source code

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>D3.chart.BulleT example</title>
  <script src="d3.v3.min.js"></script>
  <script src="d3.chart.min.js"></script> <!-- d3.chart: v0.1.2 ( MIT Expat; 2013-07-30 ) -->
  <style type="text/css">
    body {
      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
      margin: auto;
      padding-top: 40px;
      position: relative;
      width: 960px;
    }
    
    table{
      border: none;
      margin-left:auto; 
      margin-right:auto;
    }
    td{text-align:center;}
    td.wide{width:350px;} 
    td.narrow{width:60px;}

    .BulleT { font: 10px sans-serif; margin-left:auto;margin-right:auto;}
    .BulleT .marker { stroke: #4D4D4D; stroke-width: 2px;}
    .BulleT .marker.Sample { fill-opacity:0; stroke: #999999; stroke-width: 2px; }
    .BulleT .marker.Subject { fill-opacity:0; stroke: #000; stroke-width: 3px; }
    .BulleT .tick line { stroke: #666; stroke-width: .5px; }

    .BulleT .range.s0 { fill: #005C7A; }
    .BulleT .range.s1 { fill: #29A3CC; }
    .BulleT .range.s2 { fill: #c6dbef; }
    .BulleT .range.s3 { fill: #29A3CC; } 
    .BulleT .range.s4 { fill: #005C7A; }
    .BulleT .range.line { stroke: #000; stroke-width: 2px; }

    .BulleT .measure.s0 { fill: #4D4D4D; }
    .BulleT .measure.s1 { fill: #999999; } 
    .BulleT .measure.s2 { fill: #eeeeee; }
    .BulleT .measure.s3 { fill: #999999; }
    .BulleT .measure.s4 { fill: #4D4D4D; }

    .BulleT .title { font-size: 12px; font-weight: bold; }
    .BulleT .subtitle.s04 { fill: #000000; font-size: 14px; font-weight: bold;} 
    .BulleT .subtitle.s13 { fill: #4D4D4D; font-size: 12px; font-weight: bold;}
    .BulleT .subtitle.s2  { fill: #999999; font-size: 10px;}
  </style>
</head>
<body>

  <table> 
    <tr>
      <td colspan="3">
        <button id="Randomize">Randomize</button>
        <button id="Reset">Reset</button>
      </td>
    </tr>
    <tr>
      <td class="wide">
        <div id="BulleT_vertical"></div>
      </td>
      <td class="wide">
        <div id="BulleT_horizontal"></div>
      </td>
      <td class="narrow">
        <div id="BulleT_vertical_Humble"></div>
      </td>
    </tr>
  </table>
  
  <script src="bulleT-chart.js"></script>
  <script src="bulleTs-chart.js"></script>
  <script src="bulleT-app.js"></script>
</body>
</html>
bulleT-chart.js
// Chart design for T scores based on original bullet chart by Mike Bostock:
// http://bl.ocks.org/mbostock/4061961
// with d3.chart.js (0.1.2)
// http://misoproject.com/d3-chart/
d3.chart("BulleT", {
  initialize: function() {
    var chart = this; 
    this.xScale = d3.scale.linear();
    this.base.classed("BulleT", true);
    this.titleGroup = this.base.append("g");
    this.title = this.titleGroup.append("text")
      .attr("class", "title"); 
    this.dimension = this.titleGroup.append("text")
      .attr("class", "subtitle s2");
    this.subtitle = this.titleGroup.append("text")
      .attr("class", "subtitle s2");
    // Default configuration
    this._margin = { top: 0, right: 0, bottom: 0, left: 0 };
    this.orientation("");
    this.duration(0);
    this.markers(function(d) {
      return d.markers;
    });
    this.measures(function(d) {
      return d.measures;
    });
    this.width(100);
    this.height(100);
    this.tickFormat(this.xScale.tickFormat(d3.format(".0d")));
    this.reverse(false);
    this.orient("left");
    this.terjedelem(function(d) {
      return d.terjedelem;
    });
    this.ranges(function(d) {
      return d.ranges;
    });
    this.rangesLine(function(d) {
      return d.rangesLine;
    });

    this.layer("ranges", this.base.append("g").classed("ranges", true), {
      dataBind: function(data) {
      var data_ranges = new Array();
        // @CodeXmonk: a bit of hack too - ToDo later.
        // This layer operates on "ranges" data, ranges[2] not needed
        data_ranges[0] = data.ranges[0];
        data_ranges[1] = data.ranges[1];
        data_ranges[2] = data.ranges[3];
        data_ranges[3] = data.ranges[4];
        terjedelem = data.terjedelem;
        data_ranges.unshift(terjedelem[1]);
        return this.selectAll("rect.range").data(data_ranges);
      },
      insert: function() {
        return this.append("rect");
      },
      events: {
        enter: function(d, i) {
          var orientation = chart.orientation();
          var terjedelem = chart.terjedelem;
          this.attr("class", function(d, i) { return "range s" + i; })
            .attr("width", chart.xScale)
            .attr("height", function(){
              if( orientation == "vertical" ){ 
                return chart.width();
              }else{
                return chart.height();
              }
            })
          .attr("x", this.chart()._reverse ? chart.xScale :  terjedelem[0]);
        },
        "merge:transition": function() {
          var terjedelem = chart.terjedelem;
          this.duration(chart.duration())
            .attr("width", chart.xScale)
            .attr("x", chart._reverse ? chart.xScale :  terjedelem[0]);
        },
        exit: function() {
          this.remove();
        }
      }
    });
/******************************************************************************/
    this.layer("rangesLine", this.base.append("g").classed("rangesLine", true), {
      dataBind: function(data) {
        // @CodeXmonk: This layer operates on "ranges" data
        data_rangesLine = data.rangesLine;
        return this.selectAll("line.range").data(data_rangesLine);
      },
      insert: function() {
        return this.append("line");
      },
      events: {
        enter: function(d, i) {  
          var orientation = chart.orientation();
          this.attr("class", function(d, i) { return "range line"; })
            .attr("x1", chart.xScale)
            .attr("x2", chart.xScale)
            .attr("y1", 0)
            .attr("y2", function(){
              if( orientation == "vertical" ){
                return chart.width();
              }else{
                return chart.height();
              }   
            });
        },
        "merge:transition": function() {
          var orientation = chart.orientation();
          this.attr("class", function(d, i) { return "range line"; })
            .attr("x1", chart.xScale)
            .attr("x2", chart.xScale)
            .attr("y1", 0)
            .attr("y2", function(){
              if( orientation == "vertical" ){
                return chart.width();
              }else{
                return chart.height();
              }   
            });
        },
        exit: function() {
          this.remove();
        }
      }
    });
/******************************************************************************/
    this.layer("measures", this.base.append("g").classed("measures", true), {
      dataBind: function(data) {
        // @CodeXmonk: This layer operates on "measures" data
        data_measures = data.measures;
        var terjedelem = data.terjedelem;
        data_measures.unshift(terjedelem[1]);
        return this.selectAll("rect.measure").data(data_measures);
      },
      insert: function() {
        return this.append("rect");
      },
      events: {
        enter: function() {
          var orientation = chart.orientation();
          var hy;
          if( orientation == "vertical" ){
            hy = chart.width() / 2;
          }else{
            hy = chart.height() / 2;
          }
          var terjedelem = chart.terjedelem();
          this.attr("class", function(d, i) { return "measure s" + i; })
            .attr("width", chart.xScale)
            .attr("height", hy)
            .attr("x", terjedelem[0])
            .attr("y", hy/2);
        },
        "merge:transition": function() {
          var terjedelem = chart.terjedelem;
          this.duration(chart.duration())
            .attr("width", chart.xScale)
            .attr("x", terjedelem[0])
        }
      }
    });
/******************************************************************************/
    this.layer("markerSample", this.base.append("g").classed("markerSample", true), {
      dataBind: function(data) {
        // @CodeXmonk: This layer operates on "markerSample" datum
        data = data.markers;
        return this.selectAll("line.marker").data(data.slice(0,1));
      },
      insert: function() {
        return this.append("line");
      },
      events: {
        enter: function() {
          var orientation = chart.orientation();
          var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
          this.attr("class", "marker Sample")
            .attr("x1", chart.xScale)
            .attr("x2", chart.xScale)
            .attr("y1", whichOne / 4)
            .attr("y2", whichOne - (whichOne/4) );
        },
        "merge:transition": function() {
          var orientation = chart.orientation();
          var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
          this.duration(chart.duration())
            .attr("x1", chart.xScale)
            .attr("x2", chart.xScale)
            .attr("y1", whichOne / 4)
            .attr("y2", whichOne - (whichOne/4) );
        }
      }
    });
/******************************************************************************/
    this.layer("markerSubject", this.base.append("g").classed("markerSubject", true), {
      dataBind: function(data) {
        // @CodeXmonk: This layer operates on "markerSubject" datum
        data = data.markers;
          return this.selectAll("rect.marker").data(data.slice(1,2));
      },
      insert: function() {
        return this.append("rect");
      },
      events: {
        enter: function() {
          var orientation = chart.orientation();
          var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
          this.attr("class", "marker Subject")
            .attr("width", 6)
            .attr("y", -(whichOne/10)) 
            .attr("height",function(d) {return whichOne+(whichOne/5);})
            .attr("x", chart.xScale)
            .attr("transform", "translate(-3,0)");
        },
        "merge:transition": function() {
          var orientation = chart.orientation();
          var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
          this.duration(chart.duration())
            .attr("width", 6)
            .attr("y", -(whichOne/10))
            .attr("height",function(d) {return whichOne+(whichOne/5);})
            .attr("x", chart.xScale)
            .attr("transform", "translate(-3,0)");
        }
      }
    });
/******************************************************************************/
    this.layer("ticks", this.base.append("g").classed("ticks", true), {
      dataBind: function() {
        var format = this.chart().tickFormat();
        return this.selectAll("g.tick").data(this.chart().xScale.ticks(8), function(d) {
          return this.textContent || format(d);
        });
      },
      insert: function() {
        var orientation = chart.orientation();
        var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
        var tick = this.append("g").attr("class", "tick");
        var height = whichOne;
        var format = chart.tickFormat();
        tick.append("line")
          .attr("y1", whichOne)
          .attr("y2", whichOne * 7 / 6);
        tick.append("text")
          .attr("text-anchor", "middle")
          .attr("dy", "1em")
          .attr("y", whichOne * 7 / 6)
          .attr("transform", function(){
            if( orientation == "vertical" ){
              return "translate("+(whichOne+whichOne/2)+","+(whichOne+whichOne/2)+")rotate(90)";
            } /* @CodeXmonk: if vertical BUT its not exact yet - ToDo */   
          })
          .text(format);
        return tick;
      },
      events: {
        enter: function() {
          this.attr("transform", function(d) {
            return "translate(" + chart.xScale(d) + ",0)";
          })
          .style("opacity",1);
        },
        "merge:transition": function() { 
          var orientation = chart.orientation();
          var whichOne;
          if( orientation == "vertical" ){
            whichOne = chart.width();
          }else{
            whichOne = chart.height();
          }
          this.duration(chart.duration())
            .attr("transform", function(d) {
              return "translate(" + chart.xScale(d) + ",0)";
            })
          .style("opacity", 1);
          this.select("line")
            .attr("y1", whichOne)
            .attr("y2", whichOne * 7 / 6);
          this.select("text")
            .attr("y", whichOne * 7 / 6);
        },
        "exit:transition": function() {
          this.duration(chart.duration())
            .attr("transform", function(d) {
              return "translate(" + chart.xScale(d) + ",0)";
            })
            .style("opacity", 1e-6)
            .remove();
        }
      }
    });
    d3.timer.flush();
  },                                                                     

  transform: function(data) {
    var orientation = this.orientation(); 
    var height = this.height();
    if( orientation == "vertical" ){
      this.base.attr("transform","translate(15," + ( height + 10 ) + ")rotate(-90)");
    }
    // misoproject: Copy data before sorting
    var newData = {
      title: data.title,
      dimension: data.dimension,
      randomizer: data.randomizer,
      terjedelem: data.terjedelem.slice(),
      ranges: data.ranges.slice().sort(d3.descending),
      rangesLine: data.rangesLine.slice(),
      measures: data.measures.slice().sort(d3.descending),
      markers: data.markers.slice()
    };
    this.xScale.domain([newData.terjedelem[0], newData.terjedelem[1]]);
    this.titleGroup
      .style("text-anchor", function(){
        if( orientation == "vertical" ){
          return "middle";
        }else{
          return "end";
        }   
      });
    if( orientation == "vertical" ){
      this.titleGroup.attr("transform", function(){ return "translate(-15,10)rotate(90)"});
    }   
    this.dimension
      .attr("dy", function(){
        if( orientation == "vertical" ){
          return "12px";
        }else{
          return "1.2em";
        }   
      });
    this.subtitle
      .attr("dy", function(){
        if( orientation == "vertical" ){
          return "26px";
        }else{
          return "2.4em";
        }   
      });
    this.title.text(data.title);
    this.dimension.text(data.dimension+"[T]");
    this.subtitle.text(data.markers[1]);

    this.subtitle.attr("class",function(d) {
      switch (true){
        case ( (data.markers[1] < 30) || (70 < data.markers[1]) ): 
          return "subtitle s04";
          break;
        case ( (30 <= data.markers[1]) && (data.markers[1] < 40) ):
          return "subtitle s13";
          break;
        case ( (40 <= data.markers[1]) && (data.markers[1] <= 60) ):
          return "subtitle s2";
          break;
        case ( (60 < data.markers[1]) && (data.markers[1] <= 70) ):
          return "subtitle s13";
          break;
      }
    });
    return newData;
  },
  // misoproject: reverse or not
  reverse: function(x) {
    if (!arguments.length) return this._reverse;
    this._reverse = x;
    return this;
  },
  // misoproject: left, right, top, bottom
  orient: function(x) {
    if (!arguments.length) return this._orient;
    this._orient = x;
    this._reverse = this._orient == "right" || this._orient == "bottom";
    return this;
  }, 
  // @CodeXmonk: terjedelem set (min,max) of the scale
  terjedelem: function(x) {
    if (!arguments.length) return this._terjedelem;
    this._terjedelem = x;
    return this;
  },
  // misoproject: ranges (bad, satisfactory, good)
  ranges: function(x) {
    if (!arguments.length) return this._ranges;
    this._ranges = x;
    return this;
  }, 
  // @CodeXmonk: for population mean T=50
  rangesLine: function(x) {
    if (!arguments.length) return this._ranges;
    this._ranges = x;
    return this;
  },
  // misoproject: markers (previous, goal)
  markers: function(x) {
    if (!arguments.length) return this._markers;
    this._markers = x;
    return this;
  },
  // misoproject: measures (actual, forecast)
  measures: function(x) {
    if (!arguments.length) return this._measures;
    this._measures = x;
    return this;
  },
  
  width: function(x) {
    var margin, width_tmp;
    if (!arguments.length) {
      return this._width;
    }
    margin = this.margin(); 
    width_tmp = x[0];
    width_tmp = width_tmp - (margin.left + margin.right);
    this._width = width_tmp;
    if (x.length == 1){ /* @CodeXmonk: Scale needs to be put here when it's horizontal */ 
      this.xScale.range(this._reverse ? [width_tmp, 0] : [0, width_tmp]);
    }
    this.base.attr("width", width_tmp);
    return this;
  },
  
  height: function(x) {
    var margin, height_tmp;
    if (!arguments.length) {
      return this._height;
    }
    margin = this.margin();
    height_tmp = x[0];
    height_tmp = height_tmp - (margin.top + margin.bottom);
    this._height = height_tmp;
    if (x.length != 1){ /* @CodeXmonk: Scale needs to be put here when it's vertical */
      this.xScale.range(this._reverse ? [height_tmp, 0] : [0, height_tmp]);
    }
    this.base.attr("height", height_tmp);
    this.titleGroup.attr("transform", "translate(-16," + height_tmp / 3 + ")");
    return this;
  },
  
  margin: function(margin) {
    if (!margin) {
      return this._margin;
    }
    var margin_tmp = margin;
    ["top", "right", "bottom", "left"].forEach(function(dimension) {
      if (dimension in margin_tmp) {
        this._margin[dimension] = margin_tmp[dimension];
      }
    }, this);
    this.base.attr("transform", "translate(" + this._margin.left + "," + this._margin.top + ")");
    return this;
  },

  tickFormat: function(x) {
    if (!arguments.length) return this._tickFormat;
    this._tickFormat = x;
    return this;
  },  

  orientation: function(x) {
    if (!arguments.length) return this._orientation;
    this._orientation = x;
    return this;
  },

  duration: function(x) {
    if (!arguments.length) return this._duration;
    this._duration = x;
    return this;
  }
});
bulleTs-chart.js
// Chart design for T scores based on original bullet chart by Mike Bostock:
// http://bl.ocks.org/mbostock/4061961
// with d3.chart.js (0.1.2)
// http://misoproject.com/d3-chart/
d3.chart("BulleTs", {
  initialize: function(options) {
    var mixins = this.mixins = [];
    var idx, len, mixin;
    if (options && options.seriesCount) {
      for (idx = 0, len = options.seriesCount; idx < len; ++idx) {
        this._addSeries(idx);
      }
    }
  },
  _addSeries: function(idx) {
    var mixin = this.mixin("BulleT", this.base.append("svg").append("g"));
    // misoproject:
    // Cache the prototype's implementation of `transform` so that it may
    // be invoked from the overriding implementation. This is admittedly a
    // bit of a hack, and it may point to a future improvement for d3.chart
    var t = mixin.transform;

    mixin.transform = function(data) {
      return t.call(mixin, data[idx]);
    };

    this.mixins.push(mixin);
  },
  orientation: function(orientation) { 
    if (!arguments.length) {
      return this._orientation;
    }
    this.mixins.forEach(function(mixin) {
      mixin.orientation(orientation);
    });
    return this;
  },
  width: function(width) {
    var width, width_tmp;
    if (!arguments.length) {
      return this._width;
    }
    if (width.length == 1){ /* @CodeXmonk: now its horizontal */
      width_tmp = width[0];
    }else{ /* @CodeXmonk: more space needed for titles when it's vertical */
      width_tmp = width[0]+10;
    }
    this._width = width_tmp; 
    this.base.attr("width", width_tmp);
    this.base.selectAll("svg").attr("width", width_tmp);
    this.mixins.forEach(function(mixin) {
      mixin.width(width);
    });
    return this;
  },
  height: function(height) {
    var height, height_tmp;
    if (!arguments.length) {
      return this._height;
    }
    if (height.length == 1){ /* @CodeXmonk: now its horizontal */
      height_tmp = height[0];
    }else{ /*@CodeXmonk:  only needed when it's vertical */
      height_tmp = height[0]-100;
    }
    this._height = height_tmp; 
    this.base.selectAll("svg").attr("height", height_tmp );
    if( (height.length != 1) ){ /*@CodeXmonk:  there are more elements? then its vertical */
      this.base.selectAll("svg").attr("transform", function(){ return "rotate(-90)";});
    }   
    this.mixins.forEach(function(mixin) {
      mixin.height(height);
    });
    return this;
  },
  duration: function(duration) {
    if (!arguments.length) {
      return this._duration;
    }
    this._duration = duration;
    this.mixins.forEach(function(mixin) {
    mixin.duration(duration);
    });
  },
  margin: function(margin) {
    this.mixins.forEach(function(mixin) {
      mixin.margin(margin);
    });
    return this;
  }
});
bulleT-app.js
(function() {

"use strict";

function HumbleData(){
  this.original = [
    { "title":"Humble",
      "dimension":"data",
      "terjedelem":[20,80],
      "ranges":[30,40,50,60,70],
      "rangesLine":[50],
      "measures":[30,40,60,70],
      "markers":[50,50]
    }
  ]
}

function Data(){
  this.original = [
    { "title":"Man",
      "dimension":"height",
      "terjedelem":[20,80],
      "ranges":[30,40,50,60,70],
      "rangesLine":[50],
      "measures":[38.93,47.15,63.59,71.81],
      "markers":[55.37,50]
    },
    { "title":"Woman",
      "dimension":"height",
      "terjedelem":[20,80],
      "ranges":[30,40,50,60,70],
      "rangesLine":[50],
      "measures":[27.02,35.82,53.44,62.24],
      "markers":[44.63,50]
    },
    { "title":"Man",
      "dimension":"weight",
      "terjedelem":[20,80],
      "ranges":[30,40,50,60,70],
      "rangesLine":[50],
      "measures":[39.70,48.31,65.54,74.15],
      "markers":[56.93,50]
    },
    { "title":"Woman",
      "dimension":"weight",
      "terjedelem":[20,80],
      "ranges":[30,40,50,60,70],
      "rangesLine":[50],
      "measures":[29.24,36.21,50.14,57.11],
      "markers":[43.18,50]
    }
  ]
}

var getData = new Data(); /* data with sample statistics */
var data = getData.original; /* be resetable */
var getHumbleData = new HumbleData(); /* data without sample statistics */
var dataHumble = getHumbleData.original; /* be resetable */

// this one was the original d3.json version for start - when bulleTs-data.json is in the same directory
// d3.json("bulleTs-data.json", function(error, data) {
// and this is mine for getting data from the above function Data() but it needs to be called again, as it is done below: bulleT();
function bulleT() {
  var myChartVertical = d3.select("#BulleT_vertical").chart("BulleTs", {
    seriesCount: data.length
  });
  myChartVertical.margin({ top: 40, right: 20, bottom: 120, left: 5 })
    .orientation("vertical")
    .width([45,"vertical"]) /*  bit of a hack. if array.length > 1 it means */                        
    .height([350,"vertical"])/* that it is for vertical chart*/
    .duration(1000);
  myChartVertical.draw(data);    
  
  var myChartHorizontal = d3.select("#BulleT_horizontal").chart("BulleTs", {
    seriesCount: data.length
  });
  myChartHorizontal.margin({ top: 5, right: 40, bottom: 20, left: 120 })
    .orientation("horizontal")
    .width([350])/* as writen before. now array.length == 1, so it is for horizontal */
    .height([45])
    .duration(1000);
  myChartHorizontal.draw(data);
  
  /* as we don't know anything about sample statistics only subjet's score will change*/
  /* that's what I call humble data */
  var myChartVerticalHumble = d3.select("#BulleT_vertical_Humble").chart("BulleTs", {
    seriesCount: dataHumble.length
  });
  myChartVerticalHumble.margin({ top: 40, right: 20, bottom: 120, left: 5 })
    .orientation("vertical")
    .width([45,"vertical"])                        
    .height([350,"vertical"])
    .duration(1000);
  myChartVerticalHumble.draw(dataHumble);
  
  d3.selectAll("button#Randomize").on("click", function() {
    data.forEach(randomize);
    myChartVertical.draw(data);
    myChartHorizontal.draw(data);
    dataHumble.forEach(randomizeHumble);
    myChartVerticalHumble.draw(dataHumble);
  });
  
  d3.selectAll("button#Reset").on("click", function() {
    getData = new Data();
    data = getData.original;
    myChartVertical.draw(data);
    myChartHorizontal.draw(data);
    getHumbleData = new HumbleData();
    dataHumble = getHumbleData.original;
    myChartVerticalHumble.draw(dataHumble);
  });
}

function randomize(d) {
  var k = 10;
  if (!d.randomizer) d.randomizer = randomizer(d);
  d.markers[0] = d.randomizer(d.markers[0],40,60);
  d.markers[1] = d.randomizer(d.markers[1],21,78);
  k = d.randomizer(k,4,9);
  d.measures[0] = d.markers[0] - 2*k;
  d.measures[1] = d.markers[0] - k;
  d.measures[2] = d.markers[0] + k;
  d.measures[3] = d.markers[0] + 2*k;
  return d;
}

function randomizeHumble(d) {
  var k = 10;
  if (!d.randomizer) d.randomizer = randomizer(d);
  d.markers[1] = d.randomizer(d.markers[1],21,78);
  k = d.randomizer(k,4,9);
  return d;
}

// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomizer(d,min,max) {
  return function(d,min,max) {
    return getRandomInt(min,max);
  };
}

bulleT();

})();