Beispiel #1
0
var DistView = BaseView.extend({
  baseview: helpers.VIEWS.HIST,
  prepare: function(data) {
    var series = [];
    var col = data.parsed.col || data.parsed.cols[0];

    var total = 0;
    var w_total = 0;
    var max;
    var min;

    var running_sum = [];
    // For each column, need to record a series
    _.each(data.results, function(result) {
      var label = "";
      var bucket = result._id[col];
      var count = result.count;
      var weighted_count = result.weighted_count;
      total += count;
      w_total += weighted_count;

      // pulled off the array above in the calc_cdf func
      series.push([bucket, count, weighted_count]);
    });

    series.sort(function(a, b) {
      return a[0] - b[0];
    });

    var stats = calc_cdf(series, total, w_total);

    // copy copy copy
    stats.parsed = data.parsed;
    return stats;
  },

  finalize: function() {
    var query = this.query;
    if (this.compare_data) {
      _.each(this.compare_data.percentiles, function(series) {
        _.each(series.data, function(pt) {
          pt[0] = pt[0] + query.parsed.compare_delta;
        });
        series.dashStyle = "LongDash";
      });

      this.ks_result = ks_test(this.data.percentiles.slice(50, 950), this.compare_data.percentiles.slice(50, 950), this.data.count, this.compare_data.count);
      this.ks_low_result = ks_test(this.data.percentiles.slice(25, 250), this.compare_data.percentiles.slice(25, 250), this.data.count, this.compare_data.count);
      this.ks_high_result = ks_test(this.data.percentiles.slice(749, 975), this.compare_data.percentiles.slice(749, 975), this.data.count, this.compare_data.count);

    }

    if (!this.data.count && (!this.data || !this.data.count)) {
      return "No Samples";
    }

  },

  render: function() {
    var self = this;
    var xmin = self.data.percentiles[0][1]; // p5
    var xzero = self.data.percentiles[0][1];
    var xmax = self.data.percentiles[960][1]; // p95
    var dataset = this.table; 

    if (self.compare_data) {
      xmin = Math.min(xmin, self.compare_data.percentiles[50][1]);
      xmax = Math.max(xmax, self.compare_data.percentiles[960][1]);
    }

    function render_graphs(xmin, xmax) {

      var outerEl = $("<div>");
      var plot_lines = [];
      _.each([5, 25, 50, 75, 95], function(p) {
        plot_lines.push({
          value : self.data.percentiles[p*10][1],
          label: {
            text: "p" + p
          },
          width: 1,
          color: "#aaa",
          dashStyle: 'dash'
        });

        if (self.compare_data) {
          plot_lines.push({
            value : self.compare_data.percentiles[p*10][1],
            label: {
              text: "p" + p
            },
            width: 1,
            dashStyle: 'dot',
            color: "#aaa"
          });
        }
      });

      var ks_lines = [];
      var ks_line, ks_low_line, ks_high_line;
      if (self.ks_result) {
        ks_line = {
          value: self.ks_result.at,
          label: {
            text: "delta: " + helpers.count_format(self.ks_result.max * 100),
            style: {
              color: "rgba(200, 0, 0, 0.7)"
            }
          },
          width: 1,
          color: "rgba(200, 0, 0, 0.7)",
          dashStyle: "dot"
        };

        ks_lines.push(ks_line);

        if (self.ks_low_result) {
          ks_low_line = {
            value: self.ks_low_result.at,
            label: {
              text: "delta: " + helpers.count_format(self.ks_low_result.max * 100),
              style: {
                color: "rgba(200, 0, 0, 0.7)"
              }
            },
            width: 1,
            color: "rgba(200, 0, 0, 0.7)",
            dashStyle: "dot"
          };
          ks_lines.push(ks_low_line);

        } 

        if (self.ks_high_result) {
          ks_high_line = {
            value: self.ks_high_result.at,
            label: {
              text: "delta: " + helpers.count_format(self.ks_high_result.max * 100),
              style: {
                color: "rgba(200, 0, 0, 0.7)"
              }
            },
            width: 1,
            color: "rgba(200, 0, 0, 0.7)",
            dashStyle: "dot"
          };
          ks_lines.push(ks_high_line);
        }
      }

      var options = {
        height: 500,
        chart: {
          height: 500,
          inverted: true,
          type: "line",
          zoomType: "x",
        },
        series: [
          {
            data: self.data.percentiles,
            name: "Value",
            color: "rgba(0, 0, 200, 0.5)"
          },
        ],
        xAxis: {
          reversed: false,
          type: "linear" // TODO: make self configurable?
        },
        yAxis: {
          min: xmin,
          reversed: false,
          plotLines: plot_lines.concat(ks_lines)
        }
      };

      if (self.compare_data) {
        
        options.series.push({
          data: (self.compare_data && self.compare_data.percentiles) || [],
          name: "Comparison",
          dashStyle: "LongDash",
          color: "rgba(200, 0, 0, 0.5)"
        });
      }

      var cumDensityEl = $("<h2 class='cdf_density'>Cumulative Density Graph</h2>");
      outerEl.append(cumDensityEl);

      var cdfEl = $("<div class='span12'/>");
      cdfEl.css("height", "500px");

      outerEl.append(cdfEl);

      $C(self.graph_component, {skip_client_init: true}, function(cmp) {
        // get rid of query contents...

        cdfEl
          .append(cmp.$el)
          .show();

        // There's a little setup cost to highcharts, maybe?
        cmp.client(options);
      });

      var distEl = $("<div class='span12'/>");
      distEl.css("height", "500px");

      outerEl.append($("<h2 class='mll mtl'>Probability Density</h2>"));
      outerEl.append(distEl);

      var dist_options = {
        height: 500,
        chart: {
          inverted: false,
          type: 'line',
          zoomType: "x"
        },
        plotOptions: {
          series: {
            marker: {
              enabled: true,
            }
          },
        },
        series: [
          {
            data: self.data.dist,
            name: "Density (in %)",
            color: "rgba(0, 0, 200, 0.5)",
          },
        ],
        xAxis: {
          min: xzero,
          max: xmax,
          reversed: false,
          plotLines: plot_lines,
          type: "linear" // TODO: make self configurable?
        },
        yAxis: {
          reversed: false
        },
        tooltip: {
          valueSuffix: "%"
        }
      };

      if (self.compare_data) {
          dist_options.series.push({
            data: (self.compare_data && self.compare_data.dist) || [],
            name: "Comparison",
            dashStyle: "LongDash",
            color: "rgba(200, 0, 0, 0.5)"
          });
      }

      $C(self.graph_component, {skip_client_init: true}, function(cmp) {
        // get rid of query contents...

        distEl
          .append(cmp.$el)
          .show();

        // There's a little setup cost to highcharts, maybe?
        cmp.client(dist_options);
      });

      return outerEl;


    }

    function render_glance(xmin, xmax) {
      var glance_lines = [];
      var glance_data = [ ];
      var glance_compare_data = [ ];

      var odd = true;
      _.each([5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95], function(p) {
        odd = !odd;
        glance_data.push({
          x : self.data.percentiles[p*10][1],
          y: p,
          shape: "square",
          marker: {
            radius: odd ? 4 : 8,
            symbol: odd ? "circle" : "diamond"
          }
        });

        if (self.compare_data) {
          glance_compare_data.push({
            x : self.compare_data.percentiles[p*10][1],
            y: p,
            shape: "square",
            marker: {
              radius: odd ? 4 : 8,
              symbol: odd ? "circle" : "diamond"
            }
          });
        }
      });

      var percentiles = self.data.percentiles;
      var compare_percentiles = self.compare_data && self.compare_data.percentiles;




      var glance_options = {
        legend: {
          enabled: false,
        },
        chart: {
          height: 300,
          type: "line"
        },
        xAxis: {
          min: xmin,
          max: xmax,
          reversed: false,
          type: "linear" // TODO: make self configurable?
        },
        plotOptions: {
          series: {
            dataLabels: {
              enabled: false
            },
            marker: {
              enabled: true,
              states: {
                hover: {
                  enabled: false
                }
              }
            }
          }
        },
        tooltip: {
          backgroundColor: 'white',
          borderWidth: 0,
          borderRadius: 0,
          headerFormat: '{point.key} ',
          shared: false,
          positioner: function () {
            return { x: 10, y: 0 };
          },

          formatter: function () {

            var ret = "percentile:" + helpers.count_format(this.y) + " " +
              "value: " + helpers.count_format(this.x) + ' ';

            if (compare_percentiles) {
              var percentile = this.y;
              var delta = Math.abs(compare_percentiles[percentile*10][1] - percentiles[percentile*10][1]);

              ret += " original - compare = " + helpers.count_format(delta);
            }

            return ret;
          },
        },
        series: [
          {
            data: glance_data,
            name: "Percentile",
            marker: {
              enabled : true,
              shape: "square"
            },
            color: "rgba(0, 0, 200, 0.5)"
          },
        ],
        yAxis: {
          enabled: false,
          reversed: false
        }
      };

      if (self.compare_data) {
        glance_options.series.push({
          data: glance_compare_data,
          name: "Percentile (comparison)",
          marker: {
            enabled : true
          },
          color: "rgba(200, 0, 0, 0.5)"
        });
      }

      var glanceEl = $("<h2>At a glance</h2>")

      var samplesEl, countEl, compareCountEl;
      samplesEl = $("<span style='font-size: 80%'>");
      samplesEl.addClass("rfloat");
      samplesEl.html("Samples: ");
      countEl = $("<span />");
      countEl.html(helpers.count_format(self.data.count));
      countEl.css({color: "rgba(0, 0, 200, 0.5)"});
      samplesEl.append(countEl);
      if (self.compare_data) {
        samplesEl.append("/");
        compareCountEl = $("<span />");
        compareCountEl.html(helpers.count_format(self.compare_data.count));
        compareCountEl.css({color: "rgba(200, 0, 0, 0.5)"});
        samplesEl.append(compareCountEl);
      }
      glanceEl.append(samplesEl);

      $C(self.graph_component, {skip_client_init: true}, function(cmp) {
        // get rid of query contents...

        glanceEl
          .append(cmp.$el)
          .show();

        // There's a little setup cost to highcharts, maybe?
        cmp.client(glance_options);
      });

      return glanceEl;
    }

    function render_percentiles(xmin, xmax) {
      // render this business
      var outerEl = $("<div>");
      var shortStatsEl = $el.find(".short_stats");
      var percentiles = self.data.percentiles;
      var compare_percentiles = self.compare_data && self.compare_data.percentiles;

      function render_stats_overview(stats, headers, row) {
        headers = headers || [];
        row = row || [];

        _.each(stats, function(p) {
          headers.push("p" + parseInt(p, 10));
          p = p * 10;
          var cell;

          if (compare_percentiles) {
            cell = helpers.build_compare_cell(percentiles[p][1], compare_percentiles[p][1]);
          } else {
            cell = $("<div>").html(helpers.count_format(percentiles[p][1]));
          }

          row.push(cell);
        });

        var table = helpers.build_table(dataset, headers, [row]);

        // only class attr.
        table.attr("class",  "table percentile_table");
        return table;
      }

      outerEl.append($("<h2>Moments</h2>"));

      var headers = ["count", "average", "trimean", "", ""];
      var row = [];

      function trimean(data) {
        return (data.percentiles[500][1] * 2 + data.percentiles[250][1] + data.percentiles[750][1]) / 4;
      }

      if (self.compare_data) {
        row.push(helpers.build_compare_cell(self.data.count, self.compare_data.count));
        if (self.data.weighted_count) {
          row.push(helpers.build_compare_cell(self.data.w_average, self.compare_data.w_average));
        } else {
          row.push(helpers.build_compare_cell(self.data.average, self.compare_data.average));
        }
        row.push(helpers.build_compare_cell(trimean(self.data), trimean(self.compare_data)));

      } else {
        row.push(helpers.count_format(self.data.count));
        if (self.data.weighted_count) {
          row.push(helpers.count_format(self.data.w_average));
        } else {
          row.push(helpers.count_format(self.data.average));
        }
        row.push(helpers.count_format(trimean(self.data)));
      }

      if (self.data.weighted_count) {
        headers.unshift("weighted count");
        if (compare_percentiles) {
          row.unshift(helpers.build_compare_cell(self.data.weighted_count, self.compare_data.weighted_count));
        } else {
          row.unshift(helpers.count_format(self.data.weighted_count));
        }
      }



      var table = helpers.build_table(dataset, headers, [row]);

      // only class attr.
      table.attr("class",  "table moment_table");
      outerEl.append(table);

      outerEl.append($("<h2>Percentiles</h2>"));
      outerEl.append(render_stats_overview([5, 25, 50, 75, 95]));
      outerEl.append($("<h2>Outliers</h2>"));
      outerEl.append(render_stats_overview([95, 96, 97, 98, 99]));

      return outerEl;
    }

    function render_notices(xmin, xmax) {
      var range = Math.abs(xmax - xmin);
      var bucket_count = range / self.data.parsed.hist_bucket;
      var warning;
      var hist_bucket = self.data.parsed.hist_bucket;
      var warningEl = "";

      if (bucket_count > 5000) {
        warning = "Your bucket size (<b>" + hist_bucket + "</b>), may be too small for the data range (<b>" + helpers.count_format(bucket_count) + "</b> buckets in the range <b>" + helpers.count_format(xmin) + " &mdash; " + helpers.count_format(xmax) + "</b>) and cause artifacts in the distribution. Try raising it to a value that creates 1,000 - 5,000 buckets.";
      } else if (bucket_count < 10) {
        warning = "Your bucket size (<b>" + hist_bucket + "</b>), may be too large for the data range (<b>" + bucket_count + "</b> buckets in the range <b>" + helpers.count_format(xmin) + " &mdash; " + helpers.count_format(xmax) + "</b>) and cause artifacts in the distribution. Try lowering it to a value that creates 1,000 - 5,000 buckets.";
      }

      if (warning) {
        var warningEl = $("<div class='alert alert-warning lfloat'> </div>");
        warningEl.html(warning);
      }

      return warningEl;
    }


    var $el = self.$el;
    var outerEl = $("<div class='span12 prl pll'>");
    outerEl.append(render_glance(xmin, xmax));
    outerEl.append($("<hr />"));
    $el.append(outerEl);

    var graph_target = $("<div>");
    var tabs = {
      "Numbers" : render_percentiles(xmin, xmax),
      "Graphs" : graph_target
    };


    var diffEl = $("<div class='mtl' style='text-align: right'>");

    if (self.compare_data) {
      diffEl.css("color", "rgba(0, 0, 0, 0.7)");
      if (self.ks_result && self.ks_result.p) {
        diffEl.html("(distributions are <b>similar</b>, p-val:" + helpers.count_format(self.ks_result.p) + ")</small>");
      } else {
        diffEl.html("(distributions are <b>not similar</b>)</small>");
      }

      outerEl.append(diffEl);
    }

    var tabEl = $("<div>");
    outerEl.append(tabEl);
    outerEl.css("padding-bottom", "100px");

    var not_rendered = true;
    $C("tabs", { tabs: tabs, active: "Numbers" } , function(cmp) {
      tabEl.prepend(cmp.$el);
      cmp.$el.find('a[data-toggle]').on('shown.bs.tab', function (e) {
        var tab_id = $(e.target).attr("data-target");
        if (!tab_id) { return; }

        tab_id = tab_id.replace(/^#tab_/, '');
        if (tab_id === "Graphs" && not_rendered) {
          var graphEl = render_graphs(xmin, xmax);
          cmp.getTab("Graphs").append(graphEl);
          not_rendered = false;
        }
      })
    });

  }

}, {
  icon: "noun/dist.svg"
});
Beispiel #2
0
var TimeView = BaseView.extend({
  baseview: helpers.VIEWS.TIME,
  prepare: function(data) {

    var dataset = this.table;
    var series = {};
    var is_compare = this.compare_query === data;

    // For each column, need to record a series
    var group_by = data.parsed.dims || [];
    group_by.sort();
    _.each(data.results, function(result) {
      var label = "";

      _.each(result, function(value, field) {
        if (field === "_id") { return; }

        if (data.parsed.agg === "$count" || data.parsed.agg === "$distinct") {
          if (field !== "count") { return; }
        } else {
          if (field === "count") { return; }
          if (field === "weighted_count") {
            return;
          }
        }

        var formatter = presenter.get_field_number_formatter(dataset, field);
        if (formatter) {
          value = formatter(value, value);
        }



        var dims = _.map(group_by, function(g) {
          return result._id[g];
        });

        var group_label = dims.join(",");
        var field_label = group_label + " " + presenter.get_field_name(dataset, field);
        var full_label = field_label;

        _labels[full_label] = group_label || full_label;

        if (!series[field_label]) {
          series[field_label] = {
            data: [],
            name: full_label,
            color: helpers.get_color(group_label || full_label)
          };
        }


        // denormalize the time bucket into ms for highcharts benefit
        var pt = {
          x: result._id.time_bucket * 1000,
          y: parseInt(value, 10),
          samples: result.count,
          compare: is_compare
        };

        if (pt.y !== null && !_.isNaN(pt.y)) {
          series[field_label].data.push(pt);
        }
      });
    });

    _.each(series, function(serie) {
      serie.data.sort(function(a, b) {
        return a.x - b.x;
      });
    });

    // map the series into an array instead of a dictionary
    return _.map(series, function(s) {
      return s;
    });
  },

  finalize: function() {
    var query = this.query;
    if (this.compare_data) {
      _.each(this.compare_data, function(series) {
        _.each(series.data, function(pt) {
          if (query.parsed.compare_delta) {
            pt.x = pt.x - query.parsed.compare_delta;

          }
        });
        series.dashStyle = "LongDash";
      });
    }

    var data = this.data.concat(this.compare_data || []);

    if (!data.length) {
      return "No samples";
    }

  },

  getChartOptions: function() {
    var _hovered;

    var options = {
      chart: {
        zoomType: "x",
        type: this.chart_type || 'line'
      },
      legend: {enabled: true},
      tooltip: {
        useHTML: true,
        formatter: function() {
          var s = "";
          var now = this.x;
          var el = $("<div><b>" + Highcharts.dateFormat('%a %d %b %H:%M:%S', this.x) + "</b></div>");

          _.each(this.points, function(point) {
            var ptDiv = $("<div>");

            var name = point.series.name;
            if (point.point.compare) {
              name += " (compare)";
            }
            ptDiv.append(
              $("<span />")
                .css("color", helpers.get_color(_labels[point.series.name]))
                .html(name));

            if (point.series.name === _hovered) {
              ptDiv.css("font-weight", "bold");
            }

            ptDiv.append(":");
            var valDiv = $("<div class='pull-right mlm' />")
                          .html(helpers.number_format(point.y));
            ptDiv.append(valDiv);

            el.append(ptDiv);


            var samples = point.point.samples;
            if (samples) {
              valDiv.append($("<div class='mlm pull-right' />").html("(" + samples + "samples)"));
            }
          });

          return el.html();
        }
      },
      plotOptions: {
        series: {
          point: {
              events: {
                  mouseOver: function (evt) {
                      _hovered = this.series.name;
                      var chart = this.series.chart;
                      chart.tooltip.refresh(chart.hoverPoints);
                  }
              }
          }
        }
      }

    };

    return options;

  },
  // TODO: figure out rendering strategy. For now, we hold the graph until both
  // are ready
  render: function() {
    // render with this.series
    var data = this.data.concat(this.compare_data || []);

    var options = this.getChartOptions();
    var $el = this.$el;
    var table = this.table;

    options.series = data;

    function getValue(item, key) {
      return $(item).find(key).text();
    }

    function doRSSFeed(cb) {

      window.SF.socket().emit("load_annotations", table, function(annotations) {
        var feed = annotations.rss;
        var items = [];
        if (feed) {
          var parsedFeed = $.parseXML(feed);

          $(parsedFeed).find("item").each(function() {
            var timeStamp = new Date(getValue(this, 'pubDate'));
            var item = {
              title: getValue(this, 'title'),
              link: getValue(this, 'link'),
              description: getValue(this, 'description'),
              time: +timeStamp,
              timestamp: getValue(this, 'pubDate')
            };

            items.push(item);
          });
        }

        if (annotations.items) {
          items = items.concat(annotations.items);
        }

        cb(items);
      });
    }

    function doDrawGraph() {
      $C("highcharter", {skip_client_init: true}, function(cmp) {
        // get rid of query contents...
        $el
          .append(cmp.$el)
          .show();

        var annotations = $("<div class='annotations' />");
        annotations.css({
          height: "200px"
        });

        $el.append(annotations);

        // There's a little setup cost to highcharts, maybe?
        cmp.client(options);
      });
    }

    doRSSFeed(function(items) {
      if (items && items.length) {
        if (!options.yAxis) {
          options.xAxis = {};
        }

        console.log(items);

        options.xAxis.plotLines = [];
        _.each(items, function(item) {
          options.xAxis.plotLines.push({
            color: "rgba(240, 240, 240, 80)",
            value: item.time,
            width: "10",
            events: {
              mouseover: function() {

                var details = $("<div />");
                details.append($("<h2>").html(item.title));
                details.append($("<a>").html(item.timestamp).attr("href", item.link));
                details.append($("<p class='mtl'>").html(item.description));

                $el.find(".annotations").html(details);
              }
            }
          });
        });
      }

      doDrawGraph();
    });

  }
}, {
  icon: "noun/line.svg"
});
Beispiel #3
0
var SessionView = BaseView.extend({
  baseview: helpers.VIEWS.SAMPLES,
  events: {
  },
  build_overview: function(rows, field, tableEl, headers) {
    var self = this;
    var plotted_vals = [];
    var grouped_series = {};

    // this is for old timeline queries sake
    var parsed = self.query.parsed;
    var color_field = parsed.event_field || parsed.custom.event_field;

    var formatter = presenter.get_field_number_formatter(self.table, field);

    _.each(rows, function(result) {
      var count = result.integer.weighted_count || result.integer.count || 1;
      var of_total = Math.max(count / rows.length, 0.15);
      var color = (result.string && result.string[color_field]) || "default";
      var value = result.integer[field];

      if (!grouped_series[color]) {
        
        grouped_series[color] = { data: [], key: color, color: helpers.get_rgba(color) }
      }



      if (formatter) {
        value = parseInt(formatter(value, value), 10);
      }

      value = value * 1000; // expect the timestamp to be in seconds


      grouped_series[color].data.push({
        x: value,
        name: color,
        key: color,
        color: helpers.get_rgba(color, of_total),
        result: result,
        y: 2,
        marker: {
          radius: 10,
          fillColor: helpers.get_rgba(color, of_total)
        }
      });
    });

    var options = {
      chart: {
          type: 'time_scatter',
          zoomType: 'x',
      },
      xAxis: {
        type: "datetime",
        min: self.query.parsed.start_ms,
        max: self.query.parsed.end_ms ,
        events: {
          setExtremes: function (e) {
            var timeIndex = _.indexOf(self.data.headers, field);
            var xmin, xmax;
            if (e.min || e.max) {
              xmin = e.min / 1000;
              xmax = e.max / 1000;
            }

            var rows = tableEl.find("tbody tr");
            _.each(rows, function(row) {
                var td_stamp = $(row).find("td").get(timeIndex);
                var row_stamp = $(td_stamp).find("div[data-transform]").attr("data-transform");
                if (!row_stamp) { row_stamp = $(td_stamp).find("div[data-value]").attr("data-value"); }
                if (!row_stamp) { row_stamp = parseInt($(td_stamp).html(), 10); }

                if (!xmax || (row_stamp >= xmin && row_stamp <= xmax)) {
                  $(row).show();
                } else {
                  $(row).hide();
                }
            });
          },

        }
      },
      yAxis: {
        enabled: false,
        labels: {
          enabled: false
        }
      },
      tooltip: {
        formatter: function() {
          var el = $("<div><b>" + Highcharts.dateFormat('%a %d %b %H:%M:%S', this.x) + "</b></div>");
          el.append("<br />" + this.key);
          return el.html();
        }
      },
      plotOptions: {
        series: {
          tooltip: {
            enabled: false
          },
          point: {
            events: {
              click: function() {
                var details = "<pre class='sample_details'>" + JSON.stringify(this.result, null, 2) + "</pre>";
                $C("modal", {title: "Sample details", body: details}, function(modal) {
                  modal.show();
                });
              }
            }
          },
          marker: {
            enabled: true,
            states: {
              hover: {
                enabled: false
              }
            }
          }
        }
      },
      series: _.values(grouped_series),
    };

    var $el = $("<div />");
    $C(self.graph_component, {skip_client_init: true}, function(cmp) {
      $el.append(cmp.$el);

      cmp.$el.height("100px");
      cmp.$el.width("100%");
      cmp.$el.css("display", "inline-block");
      options.height = "100px";
      options.width = "100%";

      // There's a little setup cost to highcharts, maybe?
      cmp.client(options);
    });

    return $el;
  },
  prepare: function(data) {
    // Session are broken up into raw samples of
    // integer, string, set types
    var cols = {
      "integer" : {},
      "string" : {},
      "set" : {}
    };

    var lookup = {};
    var self = this;

    var fields = SF.controller().get_fields(this.table);

    _.each(fields, function(field) {
      lookup[field.name] = field.type_str;
      if (cols[field.type_str]) {
        cols[field.type_str][field.name] = true;
      }
    });

    var headers = [];
    var integer_cols = Object.keys(cols.integer);
    var string_cols = Object.keys(cols.string);
    var set_cols = Object.keys(cols.set);
    var dataset = this.table;

    integer_cols.sort();
    set_cols.sort();
    string_cols.sort();

    var dims = data.parsed.dims;


    var all_cols = string_cols.concat(integer_cols).concat(set_cols);
    _.each(all_cols, function(col) {
      headers.push(presenter.get_field_name(dataset, col));
    });

    var rows = {};
    var samples = {};
    _.each(data.results, function(result) {
      var group_by = helpers.result_key(dims, result);

      var row = [];
      _.each(all_cols, function(field) {
        var types = result[lookup[field]];
        if (!types) {
          row.push("");
          return;
        }

        var value = result[lookup[field]][field];

        row.push(value);
      });

      rows[group_by] = rows[group_by] || [];
      rows[group_by].push(row);
      samples[group_by] = samples[group_by] || [];
      samples[group_by].push(result);
    });

    return {
      rows: rows,
      samples: samples,
      headers: headers
    };
  },

  finalize: function() {
    if (!_.keys(this.data.rows).length) {
      return "No Timlines";
    }
  },

  render: function() {
    var self = this;
    var fields = SF.controller().get_fields(self.table);
    var row_names = Object.keys(self.data.rows);
    row_names = _.sortBy(row_names, function(row) {
      return -self.data.rows[row].length;
    });

    self.$el.append("Total Samples: " + self.query.results.length);
    self.$el.append("<div class='clearfix' />");

    var time_field;
    _.each(fields, function(field) {
      if (field.time_col) { 
        time_field = field.name;
      }
    });

    if (!time_field) {
      console.log("COULDNT FIND TIME FIELD FOR SAMPLES!");
    }


    _.each(row_names, function(group_by) {
      var rows = self.data.rows[group_by];
      var samples = self.data.samples[group_by];

      var tableEl = helpers.build_table(self.table, self.data.headers, rows, fields);
      var tableWrapper = $("<div class='session_table' />").append(tableEl);
      tableWrapper.hide();

      var overEl = self.build_overview(samples, time_field, tableEl);

      var tableName = $("<h3 class='mtl' />").html(group_by || "Timeline");
      var tableButton = $("<a class='lfloat mrl' href='#'>See Samples</div>");
      tableButton.on('click', function() {
        tableWrapper.toggle();
      });

      var sample_count = samples.length + " samples, ";
      var percent_str = parseInt(samples.length / self.query.results.length * 100, 10) + "% of total";
      var countEl = $("<div />")
        .html(sample_count + percent_str);

      self.$el
        .append(tableName)
        .append(countEl)
        .append(tableButton)
        .append(overEl)
        .append(tableWrapper)
        .append("<hr />");

    });
    self.$el.fadeIn();
  }

}, {
  icon: "noun/pin.svg"
});