class RailsDataExplorer::Chart::StackedBarChartCategorical

Responsibilities:

* Render a stacked bar chart for bivariate analysis of two categorical
  data series. Renders absolute frequencies of y-data series.

Collaborators:

* DataSet

Public Class Methods

new(_data_set, options = {}) click to toggle source
# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 14
def initialize(_data_set, options = {})
  @data_set = _data_set
  @options = {}.merge(options)
end

Public Instance Methods

compute_chart_attrs() click to toggle source
# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 19
def compute_chart_attrs
  val_mod = { name: :limit_distinct_values }

  x_candidates = @data_set.data_series.find_all { |ds|
    (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:x, :any]).any?
  }.sort { |a,b| b.uniq_vals_count(val_mod) <=> a.uniq_vals_count(val_mod) }
  y_candidates = @data_set.data_series.find_all { |ds|
    (ds.chart_roles[Chart::StackedBarChartCategoricalPercent] & [:y, :any]).any?
  }

  x_ds = x_candidates.first
  y_ds = (y_candidates - [x_ds]).first
  return false  if x_ds.nil? || y_ds.nil?

  # initialize data_matrix
  data_matrix = { _sum: { _sum: 0 } }
  x_ds.uniq_vals(val_mod).each { |x_val|
    data_matrix[x_val] = {}
    data_matrix[x_val][:_sum] = 0
    y_ds.uniq_vals(val_mod).each { |y_val|
      data_matrix[x_val][y_val] = 0
      data_matrix[:_sum][y_val] = 0
    }
  }
  # populate data_matrix
  x_ds.values(val_mod).length.times { |idx|
    x_val = x_ds.values(val_mod)[idx]
    y_val = y_ds.values(val_mod)[idx]
    data_matrix[x_val][y_val] += 1
    data_matrix[:_sum][y_val] += 1
    data_matrix[x_val][:_sum] += 1
    data_matrix[:_sum][:_sum] += 1
  }
  x_sorted_keys = x_ds.uniq_vals(val_mod).sort(
    &x_ds.label_sorter(
      nil,
      lambda { |a,b| data_matrix[b][:_sum] <=> data_matrix[a][:_sum] }
    )
  )
  y_sorted_keys = y_ds.uniq_vals(val_mod).sort(
    &y_ds.label_sorter(
      nil,
      lambda { |a,b| data_matrix[:_sum][b] <=> data_matrix[:_sum][a] }
    )
  )

  values = case @data_set.dimensions_count
  when 2
    y_sorted_keys.map { |y_val|
      x_sorted_keys.map { |x_val|
        {
          x: x_val,
          y: compute_y_value(data_matrix, x_val, y_val),
          c: y_val
        }
      }
    }.flatten
  else
    raise(ArgumentError.new("Exactly two data series required for contingency table."))
  end
  {
    values: values,
    x_axis_label: x_ds.name,
    x_axis_tick_format: 'function(d) { return d }',
    y_axis_label: compute_y_axis_label(y_ds.name),
    y_axis_tick_format: "d3.format('.1%')",
  }
end
compute_y_axis_label(y_ds_name) click to toggle source

@param y_ds_name [String] name of the y data series

# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 95
def compute_y_axis_label(y_ds_name)
  "Frequency"
end
compute_y_value(data_matrix, x_val, y_val) click to toggle source

Override this method to change how the y value is computed. E.g., to change from absolute values to percentages.

# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 90
def compute_y_value(data_matrix, x_val, y_val)
  data_matrix[x_val][y_val]
end
render() click to toggle source
# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 99
def render
  return ''  unless render?
  ca = compute_chart_attrs
  return ''  unless ca
  render_vega(ca)
end
render_nvd3(ca) click to toggle source
# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 209
def render_nvd3(ca)
  %(
    <div class="rde-chart rde-stacked-bar-chart-categorical-percent">
      <h3 class="rde-chart-title">Stacked Bar Chart</h3>
      <div id="#{ dom_id }", style="height: 200px;">
        <svg></svg>
      </div>
      <script type="text/javascript">
        (function() {
          var data = #{ ca[:values].to_json };

          nv.addGraph(function() {
            var chart = nv.models.multiBarChart()
              ;

            chart.xAxis
              .axisLabel('#{ ca[:x_axis_label] }')
              .tickFormat(#{ ca[:x_axis_tick_format] })
              ;

            chart.yAxis
              .axisLabel('#{ ca[:y_axis_label] }')
              .tickFormat(#{ ca[:y_axis_tick_format] })
              ;

            chart.multibar.stacked(true);
            chart.showControls(false);
            chart.tooltipContent(
              function(key, x, y, e, graph) {
                return '<p>' + key + '</p>' + '<p>' +  y + ' of ' + x + '</p>'
              }
            );


            d3.select('##{ dom_id } svg')
              .datum(data)
              .transition().duration(100)
              .call(chart)
              ;

            nv.utils.windowResize(chart.update);

            return chart;
          });
        })();
      </script>
    </div>
  )
end
render_vega(ca) click to toggle source
# File lib/rails_data_explorer/chart/stacked_bar_chart_categorical.rb, line 106
def render_vega(ca)
  %(
    <div class="rde-chart rde-stacked-bar-chart-categorical-percent">
      <h3 class="rde-chart-title">Stacked Bar Chart</h3>
      <div id="#{ dom_id }"></div>
      <script type="text/javascript">
        (function() {
          var spec = {
            "width": 960,
            "height": 200,
            "padding": {"top": 10, "left": 70, "bottom": 50, "right": 100},
            "data": [
              {
                "name": "table",
                "values": #{ ca[:values].to_json }
              },
              {
                "name": "stats",
                "source": "table",
                "transform": [
                  {"type": "facet", "keys": ["data.x"]},
                  {"type": "stats", "value": "data.y"}
                ]
              }
            ],
            "scales": [
              {
                "name": "x",
                "type": "ordinal",
                "range": "width",
                "domain": {"data": "table", "field": "data.x"}
              },
              {
                "name": "y",
                "type": "linear",
                "range": "height",
                "nice": true,
                "domain": {"data": "stats", "field": "sum"}
              },
              {
                "name": "color",
                "type": "ordinal",
                "range": "category10"
              }
            ],
            "axes": [
              {
                "type": "x",
                "scale": "x",
                "title": "#{ ca[:x_axis_label] }",
                "format": #{ ca[:x_axis_tick_format] },
              },
              {
                "type": "y",
                "scale": "y",
                "title": "#{ ca[:y_axis_label] }",
                "format": #{ ca[:y_axis_tick_format] },
                "titleOffset": 60,
              }
            ],
            "marks": [
              {
                "type": "group",
                "from": {
                  "data": "table",
                  "transform": [
                    {"type": "facet", "keys": ["data.c"]},
                    {"type": "stack", "point": "data.x", "height": "data.y"}
                  ]
                },
                "marks": [
                  {
                    "type": "rect",
                    "properties": {
                      "enter": {
                        "x": {"scale": "x", "field": "data.x"},
                        "width": {"scale": "x", "band": true, "offset": -1},
                        "y": {"scale": "y", "field": "y"},
                        "y2": {"scale": "y", "field": "y2"},
                        "fill": {"scale": "color", "field": "data.c"}
                      },
                    }
                  }
                ]
              }
            ],
            "legends": [
              {
                "fill": "color",
              }
            ],
          };

          vg.parse.spec(spec, function(chart) {
            var view = chart({ el:"##{ dom_id }" }).update();
          });

        })();
      </script>
    </div>
  )
end