Results 1 to 6 of 6

Thread: Bullet Charts in ExtJS

  1. #1
    Sencha User
    Join Date
    Apr 2013
    Location
    New Delhi
    Posts
    9

    Default Bullet Charts in ExtJS

    Hi Everyone,

    I'm working on ExtJS version 4.1 / version 4.2.
    Our current requirement is to have a Bullet Chart as shown in the attached screenshot.
    I tried to find it in the ExtJs library but didnt find anything or this sort.

    Has anyone done something like this (Bullet Chart) in ExtJS and can guide me please ?

    Kind Regards,
    Rohit
    Attached Images Attached Images

  2. #2
    Sencha User slemmon's Avatar
    Join Date
    Mar 2009
    Location
    Boise, ID
    Posts
    6,165

    Default

    I've not seen anything like that form the community and I'm afraid does not exist as a chart type in the ExtJS library.

  3. #3
    Sencha User
    Join Date
    Apr 2013
    Location
    New Delhi
    Posts
    9

    Default

    Thanks for the feedback. Are you aware of any other library where rendering bullet chart like this (attached) is possible ?

    Regards,
    Rohit

  4. #4
    Sencha User slemmon's Avatar
    Join Date
    Mar 2009
    Location
    Boise, ID
    Posts
    6,165

    Default

    I know I've seen a chart suite with a bullet chart in it before, but I'm afraid I don't remember which one it was. Let Google be our guide.

  5. #5
    Sencha Premium User
    Join Date
    Nov 2010
    Location
    Chicago
    Posts
    2,425

    Default

    This chart looks reasonably simple. You could create it yourself if you like to dabble in JS programming.

    I don't think you would even need to use the Ext JS drawing package. You could use just plain div's to render the chart.

  6. #6
    Sencha Premium Member
    Join Date
    Oct 2009
    Location
    Aylesbury, Bucks, UK
    Posts
    69

    Default

    I had the same requirement and decided to use http://d3js.org/ library with bullet plugin. It appears to mix safely with ExtJS 4.x
    Include the library, css and plugin files and add a container like this:

    bullet.css:
    Code:
    .bullet { font: 12px sans-serif; }
    .bullet .marker { stroke: #222; stroke-width: 2px; }
    .bullet .tick line { stroke: #666; stroke-width: .5px; }
    .bullet .range.s0 { fill: #eee; }
    .bullet .range.s1 { fill: #ddd; }
    .bullet .range.s2 { fill: #ccc; }
    .bullet .measure.s0 { fill: #696; }
    .bullet .measure.s1 { fill: #c00; }
    .bullet .title { font-size: 14px; font-weight: bold; }
    .bullet .subtitle { fill: #999; }
    bullet.js plugin:
    Code:
    (function() {
    
    
    // Chart design based on the recommendations of Stephen Few. Implementation
    // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
    // http://projects.instantcognition.com/protovis/bulletchart/
    d3.bullet = function() {
      var orient = "left", // TODO top & bottom
          reverse = false,
          duration = 0,
          ranges = bulletRanges,
          markers = bulletMarkers,
          measures = bulletMeasures,
          width = 380,
          height = 30,
          tickFormat = null;
    
    
      // For each small multiple
      function bullet(g) {
        g.each(function(d, i) {
          var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
              markerz = markers.call(this, d, i).slice().sort(d3.descending),
              measurez = measures.call(this, d, i).slice().sort(d3.descending),
              g = d3.select(this);
    
    
          // Compute the new x-scale.
          var x1 = d3.scale.linear()
              .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
              .range(reverse ? [width, 0] : [0, width]);
    
    
          // Retrieve the old x-scale, if this is an update.
          var x0 = this.__chart__ || d3.scale.linear()
              .domain([0, Infinity])
              .range(x1.range());
    
    
          // Stash the new scale.
          this.__chart__ = x1;
    
    
          // Derive width-scales from the x-scales.
          var w0 = bulletWidth(x0),
              w1 = bulletWidth(x1);
    
    
          // Update the range rects.
          var range = g.selectAll("rect.range")
              .data(rangez);
    
    
          range.enter().append("rect")
              .attr("class", function(d, i) { return "range s" + i; })
              .attr("width", w0)
              .attr("height", height)
              .attr("x", reverse ? x0 : 0)
            .transition()
              .duration(duration)
              .attr("width", w1)
              .attr("x", reverse ? x1 : 0);
    
    
          range.transition()
              .duration(duration)
              .attr("x", reverse ? x1 : 0)
              .attr("width", w1)
              .attr("height", height);
    
    
          // Update the measure rects.
          var measure = g.selectAll("rect.measure")
              .data(measurez);
    
    
          measure.enter().append("rect")
          .attr("class", function(d, i) { return d < markerz[0] ? "measure s1" : "measure s0"; })
              .attr("width", w0)
              .attr("height", height / 3)
              .attr("x", reverse ? x0 : 0)
              .attr("y", height / 3)
              .transition()
              .duration(duration)
              .attr("width", w1)
              .attr("x", reverse ? x1 : 0);
    
    
          measure.transition()
              .duration(duration)
                .attr("class", function(d, i) { return d < markerz[0] ? "measure s1" : "measure s0"; })
              .attr("width", w1)
              .attr("height", height / 3)
              .attr("x", reverse ? x1 : 0)
              .attr("y", height / 3);
    
    
          // Update the marker lines.
          var marker = g.selectAll("line.marker")
              .data(markerz);
    
    
          marker.enter().append("line")
              .attr("class", "marker")
              .attr("x1", x0)
              .attr("x2", x0)
              .attr("y1", height / 6)
              .attr("y2", height * 5 / 6)
            .transition()
              .duration(duration)
              .attr("x1", x1)
              .attr("x2", x1);
    
    
          marker.transition()
              .duration(duration)
              .attr("x1", x1)
              .attr("x2", x1)
              .attr("y1", height / 6)
              .attr("y2", height * 5 / 6);
    
    
          // Compute the tick format.
          var format = tickFormat || x1.tickFormat(8);
    
    
          // Update the tick groups.
          var tick = g.selectAll("g.tick")
              .data(x1.ticks(8), function(d) {
                return this.textContent || format(d);
              });
    
    
          // Initialize the ticks with the old scale, x0.
          var tickEnter = tick.enter().append("g")
              .attr("class", "tick")
              .attr("transform", bulletTranslate(x0))
              .style("opacity", 1e-6);
    
    
          tickEnter.append("line")
              .attr("y1", height)
              .attr("y2", height * 7 / 6);
    
    
          tickEnter.append("text")
              .attr("text-anchor", "middle")
              .attr("dy", "1em")
              .attr("y", height * 7 / 6)
              .text(format);
    
    
          // Transition the entering ticks to the new scale, x1.
          tickEnter.transition()
              .duration(duration)
              .attr("transform", bulletTranslate(x1))
              .style("opacity", 1);
    
    
          // Transition the updating ticks to the new scale, x1.
          var tickUpdate = tick.transition()
              .duration(duration)
              .attr("transform", bulletTranslate(x1))
              .style("opacity", 1);
    
    
          tickUpdate.select("line")
              .attr("y1", height)
              .attr("y2", height * 7 / 6);
    
    
          tickUpdate.select("text")
              .attr("y", height * 7 / 6);
    
    
          // Transition the exiting ticks to the new scale, x1.
          tick.exit().transition()
              .duration(duration)
              .attr("transform", bulletTranslate(x1))
              .style("opacity", 1e-6)
              .remove();
        });
        d3.timer.flush();
      }
    
    
      // left, right, top, bottom
      bullet.orient = function(x) {
        if (!arguments.length) return orient;
        orient = x;
        reverse = orient == "right" || orient == "bottom";
        return bullet;
      };
    
    
      // ranges (bad, satisfactory, good)
      bullet.ranges = function(x) {
        if (!arguments.length) return ranges;
        ranges = x;
        return bullet;
      };
    
    
      // markers (previous, goal)
      bullet.markers = function(x) {
        if (!arguments.length) return markers;
        markers = x;
        return bullet;
      };
    
    
      // measures (actual, forecast)
      bullet.measures = function(x) {
        if (!arguments.length) return measures;
        measures = x;
        return bullet;
      };
    
    
      bullet.width = function(x) {
        if (!arguments.length) return width;
        width = x;
        return bullet;
      };
    
    
      bullet.height = function(x) {
        if (!arguments.length) return height;
        height = x;
        return bullet;
      };
    
    
      bullet.tickFormat = function(x) {
        if (!arguments.length) return tickFormat;
        tickFormat = x;
        return bullet;
      };
    
    
      bullet.duration = function(x) {
        if (!arguments.length) return duration;
        duration = x;
        return bullet;
      };
    
    
      return bullet;
    };
    
    
    function bulletRanges(d) {
      return d.ranges;
    }
    
    
    function bulletMarkers(d) {
      return d.markers;
    }
    
    
    function bulletMeasures(d) {
      return d.measures;
    }
    
    
    function bulletTranslate(x) {
      return function(d) {
        return "translate(" + x(d) + ",0)";
      };
    }
    
    
    function bulletWidth(x) {
      var x0 = x(0);
      return function(d) {
        return Math.abs(x(d) - x0);
      };
    }
    
    
    })();
    somewhere in you app:
    Code:
    {
        xtype: 'container',
        html: '<div id="d3sla" style="width:100%;height:100%"/>',
        listeners:
        {
            afterrender:
            {
                fn: function (component, eOpts)
                {
                    // graph settings
                    var data = [
                    {
                        "title": "SLA",
                        "subtitle": "80% in 20s",
                        "ranges": [74, 120],
                        "measures": [101],
                        "markers": [100]
                    }];
    
    
                    var last = 0;
    
    
                    var margin = {
                        top: 10,
                        right: 20,
                        bottom: 20,
                        left: 100
                    },
                        width = 600 - margin.left - margin.right,
                        height = 60 - margin.top - margin.bottom;
    
    
                    var chart = d3.bullet()
                        .width(width)
                        .height(height);
    
    
                    var svg = d3.select("#d3sla").selectAll("svg")
                        .data(data)
                        .enter().append("svg")
                        .attr("class", "bullet")
                        .attr("width", width + margin.left + margin.right)
                        .attr("height", height + margin.top + margin.bottom)
                        .append("g")
                        .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
                        .call(chart);
    
    
                    var title = svg.append("g")
                        .style("text-anchor", "end")
                        .attr("transform", "translate(-6," + Math.floor(height / 2) + ")");
    
    
                    title.append("text")
                        .attr("class", "title")
                        .text(function (d)
                        {
                            return d.title;
                        });
    
    
                    title.append("text")
                        .attr("class", "subtitle")
                        .attr("dy", "1em")
                        .text(function (d)
                        {
                            return d.subtitle;
                        });
    
    
                    // update graph on store load
                    var jpstore = Ext.data.StoreManager.lookup('JPStore');
    
    
                    jpstore.on(
                    {
                        load:
                        {
                            fn: function (store, records, success, options)
                            {
                                data = {
                                    markers: [100],
                                    measures: [100 + records[0].data.sla],
                                    ranges: [last, 120]
                                };
                                last = 100 + records[0].data.sla;
    
    
                                svg.datum(data).call(chart);
                            }
                        }
                    });
                }
            }
        }
    }

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •