reports_kit 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.travis.yml +20 -0
- data/README.md +17 -19
- data/app/assets/javascripts/reports_kit/lib/_init.js +2 -1
- data/app/assets/javascripts/reports_kit/lib/chart.js +25 -16
- data/app/assets/javascripts/reports_kit/lib/report.js +42 -25
- data/app/assets/javascripts/reports_kit/lib/table.js +37 -2
- data/app/assets/stylesheets/reports_kit/reports.css.sass +3 -0
- data/docs/dimensions.md +26 -34
- data/docs/display_options.md +12 -15
- data/docs/filters.md +54 -63
- data/docs/measures.md +3 -4
- data/lib/reports_kit.rb +12 -10
- data/lib/reports_kit/base_controller.rb +1 -2
- data/lib/reports_kit/configuration.rb +19 -4
- data/lib/reports_kit/entity.rb +3 -0
- data/lib/reports_kit/helper.rb +17 -21
- data/lib/reports_kit/model_configuration.rb +1 -1
- data/lib/reports_kit/report_builder.rb +11 -11
- data/lib/reports_kit/reports/{abstract_measure.rb → abstract_series.rb} +1 -1
- data/lib/reports_kit/reports/{composite_measure.rb → composite_series.rb} +12 -8
- data/lib/reports_kit/reports/data/add_table_aggregations.rb +105 -0
- data/lib/reports_kit/reports/data/{composite_aggregation.rb → aggregate_composite.rb} +28 -27
- data/lib/reports_kit/reports/data/{one_dimension.rb → aggregate_one_dimension.rb} +9 -7
- data/lib/reports_kit/reports/data/{two_dimensions.rb → aggregate_two_dimensions.rb} +9 -8
- data/lib/reports_kit/reports/data/chart_options.rb +6 -11
- data/lib/reports_kit/reports/data/format_one_dimension.rb +56 -36
- data/lib/reports_kit/reports/data/format_table.rb +65 -0
- data/lib/reports_kit/reports/data/format_two_dimensions.rb +10 -8
- data/lib/reports_kit/reports/data/generate.rb +51 -28
- data/lib/reports_kit/reports/data/generate_for_properties.rb +52 -30
- data/lib/reports_kit/reports/data/populate_one_dimension.rb +30 -12
- data/lib/reports_kit/reports/data/populate_two_dimensions.rb +31 -31
- data/lib/reports_kit/reports/data/utils.rb +28 -24
- data/lib/reports_kit/reports/dimension.rb +4 -0
- data/lib/reports_kit/reports/{dimension_with_measure.rb → dimension_with_series.rb} +8 -9
- data/lib/reports_kit/reports/filter.rb +4 -0
- data/lib/reports_kit/reports/filter_types/base.rb +4 -3
- data/lib/reports_kit/reports/filter_types/boolean.rb +4 -4
- data/lib/reports_kit/reports/filter_types/datetime.rb +13 -3
- data/lib/reports_kit/reports/filter_types/number.rb +5 -5
- data/lib/reports_kit/reports/{filter_with_measure.rb → filter_with_series.rb} +7 -7
- data/lib/reports_kit/reports/generate_autocomplete_results.rb +2 -2
- data/lib/reports_kit/reports/inferrable_configuration.rb +6 -6
- data/lib/reports_kit/reports/{measure.rb → series.rb} +28 -15
- data/lib/reports_kit/reports_controller.rb +25 -5
- data/lib/reports_kit/value.rb +3 -0
- data/lib/reports_kit/version.rb +1 -1
- data/spec/fixtures/generate_inputs.yml +116 -63
- data/spec/fixtures/generate_outputs.yml +64 -0
- data/spec/reports_kit/report_builder_spec.rb +10 -12
- data/spec/reports_kit/reports/data/generate_spec.rb +559 -140
- data/spec/reports_kit/reports/{dimension_with_measure_spec.rb → dimension_with_series_spec.rb} +5 -7
- data/spec/reports_kit/reports/{filter_with_measure_spec.rb → filter_with_series_spec.rb} +3 -3
- data/spec/spec_helper.rb +5 -5
- data/spec/support/config.rb +31 -1
- data/spec/support/helpers.rb +6 -2
- metadata +17 -14
- data/lib/reports_kit/reports/data/entity.rb +0 -7
- data/lib/reports_kit/reports/data/value.rb +0 -7
@@ -11,13 +11,13 @@ module ReportsKit
|
|
11
11
|
self.additional_params = additional_params
|
12
12
|
end
|
13
13
|
|
14
|
-
def check_box(filter_key, options={})
|
14
|
+
def check_box(filter_key, options = {})
|
15
15
|
filter = validate_filter!(filter_key)
|
16
16
|
checked = filter.normalized_properties[:criteria][:value] == 'true'
|
17
17
|
check_box_tag(filter_key, '1', checked, options)
|
18
18
|
end
|
19
19
|
|
20
|
-
def date_range(filter_key, options={})
|
20
|
+
def date_range(filter_key, options = {})
|
21
21
|
filter = validate_filter!(filter_key)
|
22
22
|
defaults = { class: 'form-control input-sm date_range_picker' }
|
23
23
|
options = defaults.deep_merge(options)
|
@@ -26,11 +26,11 @@ module ReportsKit
|
|
26
26
|
text_field_tag(filter_key, value, options)
|
27
27
|
end
|
28
28
|
|
29
|
-
def multi_autocomplete(filter_key, options={})
|
29
|
+
def multi_autocomplete(filter_key, options = {})
|
30
30
|
validate_filter!(filter_key)
|
31
|
-
filter =
|
31
|
+
filter = series_filters.find { |f| f.key == filter_key.to_s }
|
32
32
|
reports_kit_path = Rails.application.routes.url_helpers.reports_kit_path
|
33
|
-
path = "#{reports_kit_path}reports_kit/resources/measures/#{filter.
|
33
|
+
path = "#{reports_kit_path}reports_kit/resources/measures/#{filter.series.key}/filters/#{filter_key}/autocomplete?"
|
34
34
|
path += additional_params.to_query if additional_params.present?
|
35
35
|
scope = options.delete(:scope)
|
36
36
|
params = {}
|
@@ -49,7 +49,7 @@ module ReportsKit
|
|
49
49
|
select_tag(filter_key, nil, options)
|
50
50
|
end
|
51
51
|
|
52
|
-
def string_filter(filter_key, options={})
|
52
|
+
def string_filter(filter_key, options = {})
|
53
53
|
filter = validate_filter!(filter_key)
|
54
54
|
defaults = { class: 'form-control input-sm' }
|
55
55
|
options = defaults.deep_merge(options)
|
@@ -66,11 +66,11 @@ module ReportsKit
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def filters
|
69
|
-
ui_filters +
|
69
|
+
ui_filters + series_filters
|
70
70
|
end
|
71
71
|
|
72
|
-
def
|
73
|
-
|
72
|
+
def series_filters
|
73
|
+
serieses.map(&:filters).flatten
|
74
74
|
end
|
75
75
|
|
76
76
|
def ui_filters
|
@@ -80,8 +80,8 @@ module ReportsKit
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
def
|
84
|
-
Reports::
|
83
|
+
def serieses
|
84
|
+
Reports::Series.new_from_properties!(properties, context_record: nil)
|
85
85
|
end
|
86
86
|
|
87
87
|
def default_date_range_value
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
|
-
class
|
3
|
+
class CompositeSeries < AbstractSeries
|
4
4
|
attr_accessor :properties
|
5
5
|
|
6
6
|
def initialize(properties)
|
@@ -19,24 +19,28 @@ module ReportsKit
|
|
19
19
|
properties[:composite_operator]
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def limit
|
23
|
+
properties[:limit]
|
24
|
+
end
|
25
|
+
|
26
|
+
def serieses
|
27
|
+
@serieses ||= Reports::Series.new_from_properties!(properties, context_record: nil)
|
24
28
|
end
|
25
29
|
|
26
30
|
def filters
|
27
|
-
|
31
|
+
serieses.map(&:filters).flatten
|
28
32
|
end
|
29
33
|
|
30
|
-
def
|
31
|
-
|
34
|
+
def primary_series
|
35
|
+
serieses.first
|
32
36
|
end
|
33
37
|
|
34
38
|
def dimensions
|
35
|
-
|
39
|
+
primary_series.dimensions
|
36
40
|
end
|
37
41
|
|
38
42
|
def model_class
|
39
|
-
|
43
|
+
primary_series.model_class
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module ReportsKit
|
2
|
+
module Reports
|
3
|
+
module Data
|
4
|
+
class AddTableAggregations
|
5
|
+
attr_accessor :data, :report_options
|
6
|
+
|
7
|
+
VALID_AGGREGATION_OPERATORS = [:sum]
|
8
|
+
|
9
|
+
def initialize(data, report_options:)
|
10
|
+
self.data = data
|
11
|
+
self.report_options = report_options || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
data_with_aggregations
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def data_with_aggregations
|
21
|
+
return data if row_aggregation_configs.blank? && column_aggregation_configs.blank?
|
22
|
+
{
|
23
|
+
entities: entities,
|
24
|
+
datasets: datasets
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def entities
|
29
|
+
column_aggregation_entities = column_aggregation_configs.map do |config|
|
30
|
+
ReportsKit::Entity.new(config[:label], config[:label], config[:label])
|
31
|
+
end
|
32
|
+
entities_with_row_aggregations + column_aggregation_entities
|
33
|
+
end
|
34
|
+
|
35
|
+
def datasets
|
36
|
+
datasets_with_row_aggregations.map do |dataset|
|
37
|
+
column_aggregation_configs.each do |config|
|
38
|
+
value = aggregate_array(dataset[:values].map(&:formatted), config[:operator])
|
39
|
+
dataset[:values] << ReportsKit::Value.new(value, value)
|
40
|
+
end
|
41
|
+
dataset
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def entities_with_row_aggregations
|
46
|
+
@entities_with_row_aggregations ||= begin
|
47
|
+
return original_entities if row_aggregation_configs.blank?
|
48
|
+
row_aggregation_entities = row_aggregation_configs.map do |config|
|
49
|
+
ReportsKit::Entity.new(config[:label], config[:label], config[:label])
|
50
|
+
end
|
51
|
+
original_entities + row_aggregation_entities
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def datasets_with_row_aggregations
|
56
|
+
@datasets_with_row_aggregations ||= begin
|
57
|
+
return original_datasets if row_aggregation_configs.blank?
|
58
|
+
row_aggregation_datasets = row_aggregation_configs.map do |config|
|
59
|
+
values = original_datasets.map { |dataset| dataset[:values].map(&:formatted) }.transpose
|
60
|
+
aggregated_values = aggregate_array_of_arrays(values, config[:operator])
|
61
|
+
values = aggregated_values.map { |value| ReportsKit::Value.new(value, value) }
|
62
|
+
{
|
63
|
+
entity: ReportsKit::Entity.new(config[:label], config[:label], config[:label]),
|
64
|
+
values: values
|
65
|
+
}
|
66
|
+
end
|
67
|
+
original_datasets + row_aggregation_datasets
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def original_entities
|
72
|
+
data[:entities]
|
73
|
+
end
|
74
|
+
|
75
|
+
def original_datasets
|
76
|
+
data[:datasets]
|
77
|
+
end
|
78
|
+
|
79
|
+
def row_aggregation_configs
|
80
|
+
return [] if report_options[:aggregations].blank?
|
81
|
+
report_options[:aggregations].select { |config| config[:from] == 'rows' }
|
82
|
+
end
|
83
|
+
|
84
|
+
def column_aggregation_configs
|
85
|
+
return [] if report_options[:aggregations].blank?
|
86
|
+
report_options[:aggregations].select { |config| config[:from] == 'columns' }
|
87
|
+
end
|
88
|
+
|
89
|
+
def aggregate_array(values, operator)
|
90
|
+
operator = operator.try(:to_sym)
|
91
|
+
raise ArgumentError.new("Invalid aggregation operator: #{operator}") unless operator.in?(VALID_AGGREGATION_OPERATORS)
|
92
|
+
if values.first.is_a?(Numeric)
|
93
|
+
values.public_send(operator)
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def aggregate_array_of_arrays(array_of_arrays, operator)
|
100
|
+
array_of_arrays.map { |values| aggregate_array(values, operator) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
3
|
module Data
|
4
|
-
class
|
5
|
-
attr_accessor :
|
4
|
+
class AggregateComposite
|
5
|
+
attr_accessor :composite_series, :context_record
|
6
|
+
|
7
|
+
delegate :composite_operator, :properties, to: :composite_series
|
6
8
|
|
7
9
|
OPERATORS_METHODS = {
|
8
10
|
'+' => :+,
|
@@ -10,7 +12,7 @@ module ReportsKit
|
|
10
12
|
'*' => :*,
|
11
13
|
'/' => :/,
|
12
14
|
'%' => -> (values) {
|
13
|
-
raise ArgumentError.new(
|
15
|
+
raise ArgumentError.new('Percentage composite aggregations must have exactly two series') if values.length != 2
|
14
16
|
numerator, denominator = values
|
15
17
|
return 0 if denominator == 0
|
16
18
|
((numerator.to_f / denominator) * 100).round(1)
|
@@ -18,43 +20,42 @@ module ReportsKit
|
|
18
20
|
}
|
19
21
|
|
20
22
|
def initialize(properties, context_record:)
|
21
|
-
self.
|
22
|
-
self.composite_operator = properties[:composite_operator]
|
23
|
+
self.composite_series = CompositeSeries.new(properties)
|
23
24
|
self.context_record = context_record
|
24
25
|
end
|
25
26
|
|
26
27
|
def perform
|
27
|
-
return
|
28
|
-
return
|
29
|
-
raise ArgumentError.new("Composite aggregations'
|
28
|
+
return serieses_results_for_one_dimension if dimension_count == 1
|
29
|
+
return serieses_results_for_two_dimensions if dimension_count == 2
|
30
|
+
raise ArgumentError.new("Composite aggregations' series can only have 1-2 dimensions")
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
33
34
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
sorted_dimension_keys_values = sort_dimension_keys_values(
|
35
|
+
def serieses_results_for_one_dimension
|
36
|
+
serieses_results = Hash[serieses.map { |series| [series, AggregateOneDimension.new(series).perform] }]
|
37
|
+
serieses_results = Data::PopulateOneDimension.new(serieses_results, context_record: context_record, properties: properties).perform
|
38
|
+
sorted_dimension_keys_values = sort_dimension_keys_values(serieses_results)
|
38
39
|
value_lists = sorted_dimension_keys_values.map(&:values)
|
39
40
|
composited_values = value_lists.transpose.map { |data| reduce(data) }
|
40
41
|
dimension_keys = sorted_dimension_keys_values.first.keys
|
41
|
-
composited_keys_values =
|
42
|
-
composited_keys_values
|
42
|
+
composited_keys_values = dimension_keys.zip(composited_values)
|
43
|
+
Hash[composited_keys_values]
|
43
44
|
end
|
44
45
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
value_lists =
|
46
|
+
def serieses_results_for_two_dimensions
|
47
|
+
serieses_results = Hash[serieses.map { |series| [series, AggregateTwoDimensions.new(series).perform] }]
|
48
|
+
serieses_results = Data::PopulateTwoDimensions.new(serieses_results).perform
|
49
|
+
value_lists = serieses_results.values.map(&:values)
|
49
50
|
composited_values = value_lists.transpose.map { |data| reduce(data) }
|
50
|
-
dimension_keys =
|
51
|
-
composited_keys_values =
|
52
|
-
composited_keys_values
|
51
|
+
dimension_keys = serieses_results.values.first.keys
|
52
|
+
composited_keys_values = dimension_keys.zip(composited_values)
|
53
|
+
Hash[composited_keys_values]
|
53
54
|
end
|
54
55
|
|
55
56
|
# Before performing a composition of values, we need to make sure that the values are sorted by the same dimension key.
|
56
|
-
def sort_dimension_keys_values(
|
57
|
-
dimension_keys_values_list =
|
57
|
+
def sort_dimension_keys_values(serieses_results)
|
58
|
+
dimension_keys_values_list = serieses_results.values
|
58
59
|
sorted_dimension_keys_values = dimension_keys_values_list.map do |dimension_keys_values|
|
59
60
|
dimension_keys_values = dimension_keys_values.sort_by do |dimension_key, value|
|
60
61
|
is_boolean = dimension_key.is_a?(TrueClass) || dimension_key.is_a?(FalseClass)
|
@@ -82,13 +83,13 @@ module ReportsKit
|
|
82
83
|
end
|
83
84
|
|
84
85
|
def dimension_count
|
85
|
-
unique_dimension_counts =
|
86
|
-
raise ArgumentError.new('All
|
86
|
+
unique_dimension_counts = serieses.map { |series| series.dimensions.length }.uniq
|
87
|
+
raise ArgumentError.new('All series must have the same number of dimensions') if unique_dimension_counts.length > 1
|
87
88
|
unique_dimension_counts.first
|
88
89
|
end
|
89
90
|
|
90
|
-
def
|
91
|
-
@
|
91
|
+
def serieses
|
92
|
+
@serieses ||= Series.new_from_properties!(properties, context_record: context_record)
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
3
|
module Data
|
4
|
-
class
|
5
|
-
attr_accessor :
|
4
|
+
class AggregateOneDimension
|
5
|
+
attr_accessor :series, :dimension
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
self.
|
9
|
-
self.dimension =
|
7
|
+
def initialize(series)
|
8
|
+
self.series = series
|
9
|
+
self.dimension = series.dimensions[0]
|
10
10
|
end
|
11
11
|
|
12
12
|
def perform
|
@@ -16,15 +16,17 @@ module ReportsKit
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def dimension_keys_values
|
19
|
-
relation =
|
19
|
+
relation = series.filtered_relation
|
20
20
|
relation = relation.group(dimension.group_expression)
|
21
21
|
relation = relation.joins(dimension.joins) if dimension.joins
|
22
22
|
relation = relation.limit(dimension.dimension_instances_limit) if dimension.dimension_instances_limit
|
23
23
|
relation = relation.order(order)
|
24
|
-
|
24
|
+
relation = series.edit_relation_method.call(relation) if series.edit_relation_method
|
25
|
+
dimension_keys_values = relation.distinct.public_send(*series.aggregate_function)
|
25
26
|
dimension_keys_values = Utils.populate_sparse_hash(dimension_keys_values, dimension: dimension)
|
26
27
|
dimension_keys_values.delete(nil)
|
27
28
|
dimension_keys_values.delete('')
|
29
|
+
dimension_keys_values = dimension_keys_values.take(series.limit) if series.limit
|
28
30
|
Hash[dimension_keys_values]
|
29
31
|
end
|
30
32
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module ReportsKit
|
2
2
|
module Reports
|
3
3
|
module Data
|
4
|
-
class
|
5
|
-
attr_accessor :
|
4
|
+
class AggregateTwoDimensions
|
5
|
+
attr_accessor :series, :dimension, :second_dimension
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
self.
|
9
|
-
self.dimension =
|
10
|
-
self.second_dimension =
|
7
|
+
def initialize(series)
|
8
|
+
self.series = series
|
9
|
+
self.dimension = series.dimensions[0]
|
10
|
+
self.second_dimension = series.dimensions[1]
|
11
11
|
end
|
12
12
|
|
13
13
|
def perform
|
@@ -17,12 +17,13 @@ module ReportsKit
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def dimension_keys_values
|
20
|
-
relation =
|
20
|
+
relation = series.filtered_relation
|
21
21
|
relation = relation.group(dimension.group_expression, second_dimension.group_expression)
|
22
22
|
relation = relation.joins(dimension.joins) if dimension.joins
|
23
23
|
relation = relation.joins(second_dimension.joins) if second_dimension.joins
|
24
24
|
relation = relation.order(order)
|
25
|
-
|
25
|
+
relation = series.edit_relation_method.call(relation) if series.edit_relation_method
|
26
|
+
dimension_keys_values = relation.distinct.public_send(*series.aggregate_function)
|
26
27
|
dimension_keys_values = Utils.populate_sparse_hash(dimension_keys_values, dimension: dimension)
|
27
28
|
dimension_keys_values.delete(nil)
|
28
29
|
dimension_keys_values.delete('')
|
@@ -67,7 +67,6 @@ module ReportsKit
|
|
67
67
|
set_colors
|
68
68
|
set_chart_options
|
69
69
|
set_dataset_options
|
70
|
-
set_standard_options
|
71
70
|
set_type
|
72
71
|
data
|
73
72
|
end
|
@@ -83,7 +82,7 @@ module ReportsKit
|
|
83
82
|
end
|
84
83
|
|
85
84
|
def set_record_scoped_colors
|
86
|
-
|
85
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map do |dataset|
|
87
86
|
length = dataset[:data].length
|
88
87
|
dataset[:backgroundColor] = DEFAULT_COLORS * (length.to_f / DEFAULT_COLORS.length).ceil
|
89
88
|
dataset
|
@@ -91,7 +90,7 @@ module ReportsKit
|
|
91
90
|
end
|
92
91
|
|
93
92
|
def set_dataset_scoped_colors
|
94
|
-
|
93
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map.with_index do |dataset, index|
|
95
94
|
color = DEFAULT_COLORS[index % DEFAULT_COLORS.length]
|
96
95
|
dataset[:backgroundColor] = color
|
97
96
|
dataset[:borderColor] = color
|
@@ -132,23 +131,19 @@ module ReportsKit
|
|
132
131
|
def set_chart_options
|
133
132
|
merged_options = default_options
|
134
133
|
merged_options = merged_options.deep_merge(chart_options) if chart_options
|
135
|
-
|
134
|
+
data[:chart_data][:options] = merged_options
|
136
135
|
end
|
137
136
|
|
138
137
|
def set_dataset_options
|
139
|
-
return if
|
140
|
-
|
138
|
+
return if data[:chart_data][:datasets].blank? || dataset_options.blank?
|
139
|
+
data[:chart_data][:datasets] = data[:chart_data][:datasets].map do |dataset|
|
141
140
|
dataset.merge(dataset_options)
|
142
141
|
end
|
143
142
|
end
|
144
143
|
|
145
|
-
def set_standard_options
|
146
|
-
self.data[:chart_data][:standard_options] = options[:standard_options] if options[:standard_options].present?
|
147
|
-
end
|
148
|
-
|
149
144
|
def set_type
|
150
145
|
return if type.blank?
|
151
|
-
|
146
|
+
data[:type] = type
|
152
147
|
end
|
153
148
|
|
154
149
|
def donut_or_pie_chart?
|