class RailsDataExplorer::Chart::BoxPlotGroup
Responsibilities:
* Render a group of box plots for bivariate analysis of a categorical and a numerical data series. One box plot is rendered for each distinct categorical value.
Collaborators:
* DataSet
Resources:
Public Class Methods
new(_data_set, options = {})
click to toggle source
# File lib/rails_data_explorer/chart/box_plot_group.rb, line 24 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/box_plot_group.rb, line 29 def compute_chart_attrs x_candidates = @data_set.data_series.find_all { |ds| (ds.chart_roles[Chart::BoxPlotGroup] & [:x, :any]).any? } y_candidates = @data_set.data_series.find_all { |ds| (ds.chart_roles[Chart::BoxPlotGroup] & [: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 values_hash values_hash = y_ds.uniq_vals.inject({}) { |m,y_val| m[y_val] = [] m } # populate values hash y_ds.values.each_with_index { |y_val, idx| next if (y_val.nil? || Float::NAN == y_val) values_hash[y_val] << x_ds.values[idx] } y_sorted_keys = y_ds.uniq_vals.sort( &y_ds.label_sorter( nil, lambda { |a,b| a <=> b } ) ) sorted_values = y_sorted_keys.map { |y_val| values_hash[y_val] } # Compute min and max values based on interquartile range of each # boxplot. Objective is to normalize boxplots so that the widest chart # uses almost the entire space available. # Iterate over all individual boxplots global_min = Float::INFINITY global_max = -Float::INFINITY sorted_values.each { |x_vals| ds = DataSeries.new('_', x_vals) desc_stats = ds.descriptive_statistics # compute first and third quartile. Use min and max if they are nil # for very small data series with only one or two entries. q1 = desc_stats.detect { |e| '25%ile' == e[:label] }[:value] || x_vals.min q3 = desc_stats.detect { |e| '75%ile' == e[:label] }[:value] || x_vals.max iqr = (q3 - q1) * 1.5 local_min = [x_vals.min, q1 - iqr].max global_min = [global_min, local_min].min local_max = [x_vals.max, q3 + iqr].min global_max = [global_max, local_max].max } { values: sorted_values, category_labels: y_sorted_keys, min: global_min, max: global_max, base_width: 100, base_height: 960, axis_tick_format: x_ds.axis_tick_format, num_box_plots: y_ds.uniq_vals_count, axis_scale: DataSeries.new('_', [global_min, global_max]).axis_scale(:d3) } end
render()
click to toggle source
# File lib/rails_data_explorer/chart/box_plot_group.rb, line 91 def render return '' unless render? ca = compute_chart_attrs return '' unless ca svg_trs = ca[:category_labels].map { |cat_label| %( <tr> <td style="vertical-align: middle;">#{ cat_label }</td> <td style="vertical-align: middle; width: 100%"> <svg class="box" style="height: #{ ca[:base_width] }px; width: 100%;"></svg> </td> </tr> ) }.join.html_safe %( <div id="#{ dom_id }" class="rde-chart rde-box-plot-group"> <table>#{ svg_trs }</table> <script type="text/javascript"> (function() { var base_width = #{ ca[:base_width] }, base_height = #{ ca[:base_height] }, margin = { top: 10, right: 40, bottom: 10, left: 40 }, width = base_width - margin.left - margin.right, height = base_height - margin.top - margin.bottom; var min = #{ ca[:min] }, max = #{ ca[:max] }; var chart = d3.box() .whiskers(iqr(1.5)) .width(width) .height(height) .tickFormat(#{ ca[:axis_tick_format] }); var data = #{ ca[:values].to_json }; chart.domain([min, max]); chart.scale(#{ ca[:axis_scale] }); var svg = d3.select("##{ dom_id }").selectAll("svg") .data(data) .append("g") .attr("transform", "rotate(90) translate(" + margin.left + " -" + (height + margin.bottom) + ")") .call(chart); // Function to compute the interquartile range. function iqr(k) { return function(d, i) { var q1 = d.quartiles[0], q3 = d.quartiles[2], iqr = (q3 - q1) * k, i = -1, j = d.length; while (d[++i] < q1 - iqr); while (d[--j] > q3 + iqr); return [i, j]; }; } })(); </script> </div> ) end