gruff 0.15.0-java → 0.16.0-java
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/.github/workflows/ci.yml +17 -4
- data/.rubocop.yml +0 -6
- data/CHANGELOG.md +26 -0
- data/README.md +10 -3
- data/gruff.gemspec +1 -1
- data/lib/gruff/accumulator_bar.rb +1 -1
- data/lib/gruff/area.rb +4 -4
- data/lib/gruff/bar.rb +28 -9
- data/lib/gruff/base.rb +109 -118
- data/lib/gruff/bezier.rb +2 -2
- data/lib/gruff/box_plot.rb +174 -0
- data/lib/gruff/bullet.rb +5 -5
- data/lib/gruff/candlestick.rb +112 -0
- data/lib/gruff/dot.rb +10 -10
- data/lib/gruff/font.rb +3 -0
- data/lib/gruff/helper/bar_conversion.rb +5 -5
- data/lib/gruff/helper/bar_value_label.rb +27 -24
- data/lib/gruff/helper/stacked_mixin.rb +3 -1
- data/lib/gruff/histogram.rb +1 -1
- data/lib/gruff/line.rb +48 -42
- data/lib/gruff/mini/legend.rb +4 -4
- data/lib/gruff/net.rb +7 -7
- data/lib/gruff/patch/string.rb +1 -0
- data/lib/gruff/pie.rb +20 -11
- data/lib/gruff/renderer/dash_line.rb +3 -2
- data/lib/gruff/renderer/dot.rb +3 -1
- data/lib/gruff/renderer/line.rb +1 -3
- data/lib/gruff/renderer/rectangle.rb +6 -2
- data/lib/gruff/scatter.rb +52 -46
- data/lib/gruff/side_bar.rb +43 -14
- data/lib/gruff/side_stacked_bar.rb +20 -27
- data/lib/gruff/spider.rb +46 -13
- data/lib/gruff/stacked_area.rb +11 -6
- data/lib/gruff/stacked_bar.rb +36 -14
- data/lib/gruff/version.rb +1 -1
- data/lib/gruff.rb +9 -9
- metadata +4 -4
- data/lib/gruff/scene.rb +0 -208
- data/lib/gruff/store/custom_data.rb +0 -36
data/lib/gruff/base.rb
CHANGED
@@ -54,20 +54,18 @@ module Gruff
|
|
54
54
|
# { 0 => 2005, 3 => 2006, 5 => 2007, 7 => 2008 }
|
55
55
|
attr_writer :labels
|
56
56
|
|
57
|
-
# Used internally for spacing.
|
58
|
-
#
|
59
|
-
# By default, labels are centered over the point they represent.
|
60
|
-
attr_writer :center_labels_over_point
|
61
|
-
|
62
|
-
# Used internally for horizontal graph types. Default is +false+.
|
63
|
-
attr_writer :has_left_labels
|
64
|
-
|
65
57
|
# Set a label for the bottom of the graph.
|
66
58
|
attr_writer :x_axis_label
|
67
59
|
|
68
60
|
# Set a label for the left side of the graph.
|
69
61
|
attr_writer :y_axis_label
|
70
62
|
|
63
|
+
# Allow passing lambda to format labels for x axis.
|
64
|
+
attr_writer :x_axis_label_format
|
65
|
+
|
66
|
+
# Allow passing lambda to format labels for y axis.
|
67
|
+
attr_writer :y_axis_label_format
|
68
|
+
|
71
69
|
# Set increment of the vertical marking lines.
|
72
70
|
attr_writer :x_axis_increment
|
73
71
|
|
@@ -135,12 +133,6 @@ module Gruff
|
|
135
133
|
# Will be scaled down if graph is smaller than 800px wide.
|
136
134
|
attr_writer :legend_box_size
|
137
135
|
|
138
|
-
# Allow passing lambdas to format labels for x axis.
|
139
|
-
attr_writer :x_axis_label_format
|
140
|
-
|
141
|
-
# Allow passing lambdas to format labels for y axis.
|
142
|
-
attr_writer :y_axis_label_format
|
143
|
-
|
144
136
|
# If one numerical argument is given, the graph is drawn at 4/3 ratio
|
145
137
|
# according to the given width (+800+ results in 800x600, +400+ gives 400x300,
|
146
138
|
# etc.).
|
@@ -159,6 +151,9 @@ module Gruff
|
|
159
151
|
@columns.freeze
|
160
152
|
@rows.freeze
|
161
153
|
|
154
|
+
@has_left_labels = false
|
155
|
+
@center_labels_over_point = true
|
156
|
+
|
162
157
|
initialize_graph_scale
|
163
158
|
initialize_attributes
|
164
159
|
initialize_store
|
@@ -209,8 +204,6 @@ module Gruff
|
|
209
204
|
@no_data_message = 'No Data'
|
210
205
|
|
211
206
|
@hide_line_markers = @hide_legend = @hide_title = @hide_line_numbers = @legend_at_bottom = false
|
212
|
-
@center_labels_over_point = true
|
213
|
-
@has_left_labels = false
|
214
207
|
@label_stagger_height = 0
|
215
208
|
@label_max_size = 0
|
216
209
|
@label_truncation_style = :absolute
|
@@ -429,7 +422,7 @@ module Gruff
|
|
429
422
|
#
|
430
423
|
# Set it after you have given all your data to the graph object.
|
431
424
|
def minimum_value
|
432
|
-
@minimum_value || store.min
|
425
|
+
(@minimum_value || store.min).to_f
|
433
426
|
end
|
434
427
|
attr_writer :minimum_value
|
435
428
|
|
@@ -439,7 +432,7 @@ module Gruff
|
|
439
432
|
# If you use this, you must set it after you have given all your data to
|
440
433
|
# the graph object.
|
441
434
|
def maximum_value
|
442
|
-
@maximum_value || store.max
|
435
|
+
(@maximum_value || store.max).to_f
|
443
436
|
end
|
444
437
|
attr_writer :maximum_value
|
445
438
|
|
@@ -512,8 +505,8 @@ module Gruff
|
|
512
505
|
# Perform data manipulation before calculating chart measurements
|
513
506
|
def setup_data # :nodoc:
|
514
507
|
if @y_axis_increment && !@hide_line_markers
|
515
|
-
self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value
|
516
|
-
self.minimum_value = [minimum_value, (minimum_value
|
508
|
+
self.maximum_value = [@y_axis_increment, maximum_value, (maximum_value / @y_axis_increment).round * @y_axis_increment].max
|
509
|
+
self.minimum_value = [minimum_value, (minimum_value / @y_axis_increment).round * @y_axis_increment].min
|
517
510
|
end
|
518
511
|
end
|
519
512
|
|
@@ -552,7 +545,7 @@ module Gruff
|
|
552
545
|
@marker_count ||= begin
|
553
546
|
count = nil
|
554
547
|
(3..7).each do |lines|
|
555
|
-
if @spread
|
548
|
+
if @spread % lines == 0.0
|
556
549
|
count = lines and break
|
557
550
|
end
|
558
551
|
end
|
@@ -565,8 +558,8 @@ module Gruff
|
|
565
558
|
store.normalize(minimum: minimum_value, spread: @spread)
|
566
559
|
end
|
567
560
|
|
568
|
-
def calculate_spread
|
569
|
-
@spread = maximum_value
|
561
|
+
def calculate_spread
|
562
|
+
@spread = maximum_value - minimum_value
|
570
563
|
@spread = @spread > 0 ? @spread : 1
|
571
564
|
end
|
572
565
|
|
@@ -579,28 +572,24 @@ module Gruff
|
|
579
572
|
end
|
580
573
|
|
581
574
|
def hide_left_label_area?
|
582
|
-
@hide_line_markers
|
575
|
+
@hide_line_markers && @y_axis_label.nil?
|
583
576
|
end
|
584
577
|
|
585
578
|
def hide_bottom_label_area?
|
586
|
-
@hide_line_markers
|
579
|
+
@hide_line_markers && @x_axis_label.nil?
|
587
580
|
end
|
588
581
|
|
589
582
|
##
|
590
583
|
# Calculates size of drawable area, general font dimensions, etc.
|
591
584
|
|
592
585
|
def setup_graph_measurements
|
593
|
-
@marker_caps_height = setup_marker_caps_height
|
594
|
-
@title_caps_height = setup_title_caps_height
|
595
|
-
@legend_caps_height = setup_legend_caps_height
|
596
|
-
|
597
586
|
margin_on_right = graph_right_margin
|
598
587
|
@graph_right = @raw_columns - margin_on_right
|
599
588
|
@graph_left = setup_left_margin
|
600
589
|
@graph_top = setup_top_margin
|
601
590
|
@graph_bottom = setup_bottom_margin
|
602
591
|
|
603
|
-
@graph_width = @
|
592
|
+
@graph_width = @graph_right - @graph_left
|
604
593
|
@graph_height = @graph_bottom - @graph_top
|
605
594
|
end
|
606
595
|
|
@@ -610,9 +599,8 @@ module Gruff
|
|
610
599
|
# X Axis
|
611
600
|
# Centered vertically and horizontally by setting the
|
612
601
|
# height to 1.0 and the width to the width of the graph.
|
613
|
-
x_axis_label_y_coordinate = @graph_bottom + LABEL_MARGIN +
|
602
|
+
x_axis_label_y_coordinate = @graph_bottom + (LABEL_MARGIN * 2) + marker_caps_height
|
614
603
|
|
615
|
-
# TODO: Center between graph area
|
616
604
|
text_renderer = Gruff::Renderer::Text.new(renderer, @x_axis_label, font: @marker_font)
|
617
605
|
text_renderer.add_to_render_queue(@raw_columns, 1.0, 0.0, x_axis_label_y_coordinate)
|
618
606
|
end
|
@@ -620,7 +608,7 @@ module Gruff
|
|
620
608
|
if @y_axis_label
|
621
609
|
# Y Axis, rotated vertically
|
622
610
|
text_renderer = Gruff::Renderer::Text.new(renderer, @y_axis_label, font: @marker_font, rotation: -90)
|
623
|
-
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin +
|
611
|
+
text_renderer.add_to_render_queue(1.0, @raw_rows, @left_margin + (marker_caps_height / 2.0), 0.0, Magick::CenterGravity)
|
624
612
|
end
|
625
613
|
end
|
626
614
|
|
@@ -628,17 +616,17 @@ module Gruff
|
|
628
616
|
def draw_line_markers
|
629
617
|
return if @hide_line_markers
|
630
618
|
|
631
|
-
increment_scaled = @graph_height
|
619
|
+
increment_scaled = @graph_height / (@spread / @increment)
|
632
620
|
|
633
621
|
# Draw horizontal line markers and annotate with numbers
|
634
622
|
(0..marker_count).each do |index|
|
635
|
-
y = @graph_top + @graph_height - index
|
623
|
+
y = @graph_top + @graph_height - (index * increment_scaled)
|
636
624
|
|
637
|
-
|
638
|
-
|
625
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_color).render(@graph_left, y, @graph_right, y)
|
626
|
+
Gruff::Renderer::Line.new(renderer, color: @marker_shadow_color).render(@graph_left, y + 1, @graph_right, y + 1) if @marker_shadow_color
|
639
627
|
|
640
628
|
unless @hide_line_numbers
|
641
|
-
marker_label = BigDecimal(index.to_s) * BigDecimal(@increment.to_s) + BigDecimal(minimum_value.to_s)
|
629
|
+
marker_label = (BigDecimal(index.to_s) * BigDecimal(@increment.to_s)) + BigDecimal(minimum_value.to_s)
|
642
630
|
label = y_axis_label(marker_label, @increment)
|
643
631
|
text_renderer = Gruff::Renderer::Text.new(renderer, label, font: @marker_font)
|
644
632
|
text_renderer.add_to_render_queue(@graph_left - LABEL_MARGIN, 1.0, 0.0, y, Magick::EastGravity)
|
@@ -658,45 +646,46 @@ module Gruff
|
|
658
646
|
|
659
647
|
legend_labels = store.data.map(&:label)
|
660
648
|
legend_square_width = @legend_box_size # small square with color of this item
|
661
|
-
|
649
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
650
|
+
line_height = [legend_caps_height, legend_square_width].max + @legend_margin
|
662
651
|
|
663
|
-
current_x_offset = center(label_widths.first.sum)
|
664
652
|
current_y_offset = begin
|
665
653
|
if @legend_at_bottom
|
666
|
-
@graph_bottom + @legend_margin +
|
654
|
+
@graph_bottom + @legend_margin + legend_caps_height + LABEL_MARGIN + (@x_axis_label ? (LABEL_MARGIN * 2) + marker_caps_height : 0)
|
667
655
|
else
|
668
|
-
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin +
|
656
|
+
hide_title? ? @top_margin + @title_margin : @top_margin + @title_margin + title_caps_height
|
669
657
|
end
|
670
658
|
end
|
671
659
|
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
# Wrap to next line and shrink available graph dimensions
|
697
|
-
current_y_offset += line_height
|
660
|
+
index = 0
|
661
|
+
legend_label_lines.each do |(legend_labels_width, legend_labels_line)|
|
662
|
+
current_x_offset = center(legend_labels_width)
|
663
|
+
|
664
|
+
legend_labels_line.each do |legend_label|
|
665
|
+
unless legend_label.empty?
|
666
|
+
legend_label_width = calculate_width(@legend_font, legend_label)
|
667
|
+
|
668
|
+
# Draw label
|
669
|
+
text_renderer = Gruff::Renderer::Text.new(renderer, legend_label, font: @legend_font)
|
670
|
+
text_renderer.add_to_render_queue(legend_label_width,
|
671
|
+
legend_square_width,
|
672
|
+
current_x_offset + (legend_square_width * 1.7),
|
673
|
+
current_y_offset,
|
674
|
+
Magick::CenterGravity)
|
675
|
+
|
676
|
+
# Now draw box with color of this dataset
|
677
|
+
rect_renderer = Gruff::Renderer::Rectangle.new(renderer, color: store.data[index].color)
|
678
|
+
rect_renderer.render(current_x_offset,
|
679
|
+
current_y_offset,
|
680
|
+
current_x_offset + legend_square_width,
|
681
|
+
current_y_offset + legend_square_width)
|
682
|
+
|
683
|
+
current_x_offset += legend_label_width + (legend_square_width * 2.7)
|
698
684
|
end
|
685
|
+
index += 1
|
699
686
|
end
|
687
|
+
|
688
|
+
current_y_offset += line_height
|
700
689
|
end
|
701
690
|
end
|
702
691
|
|
@@ -704,7 +693,7 @@ module Gruff
|
|
704
693
|
def draw_title
|
705
694
|
return if hide_title?
|
706
695
|
|
707
|
-
metrics =
|
696
|
+
metrics = text_metrics(@title_font, @title)
|
708
697
|
if metrics.width > @raw_columns
|
709
698
|
@title_font.size = @title_font.size * (@raw_columns / metrics.width) * 0.95
|
710
699
|
end
|
@@ -714,19 +703,14 @@ module Gruff
|
|
714
703
|
end
|
715
704
|
|
716
705
|
# Draws column labels below graph, centered over x_offset
|
717
|
-
|
718
|
-
# TODO Allow WestGravity as an option
|
719
|
-
def draw_label(x_offset, index, gravity = Magick::NorthGravity)
|
706
|
+
def draw_label(x_offset, index, gravity = Magick::NorthGravity, &block)
|
720
707
|
draw_unique_label(index) do
|
721
708
|
y_offset = @graph_bottom + LABEL_MARGIN
|
722
|
-
|
723
|
-
# TESTME
|
724
|
-
# FIXME: Consider chart types other than bar
|
725
|
-
# TODO: See if index.odd? is the best strategy
|
726
709
|
y_offset += @label_stagger_height if index.odd?
|
727
710
|
|
728
711
|
if x_offset >= @graph_left && x_offset <= @graph_right
|
729
712
|
draw_label_at(1.0, 1.0, x_offset, y_offset, @labels[index], gravity)
|
713
|
+
yield if block
|
730
714
|
end
|
731
715
|
end
|
732
716
|
end
|
@@ -748,11 +732,10 @@ module Gruff
|
|
748
732
|
end
|
749
733
|
|
750
734
|
# Draws the data value over the data point in bar graphs
|
751
|
-
def draw_value_label(x_offset, y_offset, data_point)
|
735
|
+
def draw_value_label(width, height, x_offset, y_offset, data_point, gravity = Magick::CenterGravity)
|
752
736
|
return if @hide_line_markers
|
753
737
|
|
754
|
-
|
755
|
-
text_renderer.add_to_render_queue(1.0, 1.0, x_offset, y_offset)
|
738
|
+
draw_label_at(width, height, x_offset, y_offset, data_point, gravity)
|
756
739
|
end
|
757
740
|
|
758
741
|
# Shows an error message because you have no data.
|
@@ -829,15 +812,15 @@ module Gruff
|
|
829
812
|
|
830
813
|
private
|
831
814
|
|
832
|
-
def
|
815
|
+
def marker_caps_height
|
833
816
|
hide_bottom_label_area? ? 0 : calculate_caps_height(@marker_font)
|
834
817
|
end
|
835
818
|
|
836
|
-
def
|
819
|
+
def title_caps_height
|
837
820
|
hide_title? ? 0 : calculate_caps_height(@title_font) * @title.lines.to_a.size
|
838
821
|
end
|
839
822
|
|
840
|
-
def
|
823
|
+
def legend_caps_height
|
841
824
|
@hide_legend ? 0 : calculate_caps_height(@legend_font)
|
842
825
|
end
|
843
826
|
|
@@ -849,7 +832,11 @@ module Gruff
|
|
849
832
|
# Make space for half the width of the rightmost column label.
|
850
833
|
# Might be greater than the number of columns if between-style bar markers are used.
|
851
834
|
last_label = @labels.keys.max.to_i
|
852
|
-
last_label >= (column_count - 1) && @center_labels_over_point
|
835
|
+
if last_label >= (column_count - 1) && @center_labels_over_point
|
836
|
+
calculate_width(@marker_font, truncate_label_text(@labels[last_label])) / 2.0
|
837
|
+
else
|
838
|
+
0
|
839
|
+
end
|
853
840
|
end
|
854
841
|
|
855
842
|
def setup_left_margin
|
@@ -859,32 +846,30 @@ module Gruff
|
|
859
846
|
if @has_left_labels
|
860
847
|
@labels.values.reduce('') { |value, memo| value.to_s.length > memo.to_s.length ? value : memo }
|
861
848
|
else
|
862
|
-
y_axis_label(maximum_value
|
849
|
+
y_axis_label(maximum_value, @increment)
|
863
850
|
end
|
864
851
|
end
|
865
852
|
longest_left_label_width = calculate_width(@marker_font, truncate_label_text(text))
|
866
|
-
longest_left_label_width *= 1.25 if @has_left_labels
|
867
853
|
|
868
854
|
# Shift graph if left line numbers are hidden
|
869
|
-
line_number_width =
|
855
|
+
line_number_width = !@has_left_labels && (@hide_line_markers || @hide_line_numbers) ? 0.0 : (longest_left_label_width + LABEL_MARGIN)
|
870
856
|
|
871
|
-
@left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 :
|
857
|
+
@left_margin + line_number_width + (@y_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2))
|
872
858
|
end
|
873
859
|
|
874
860
|
def setup_top_margin
|
875
861
|
# When @hide title, leave a title_margin space for aesthetics.
|
876
862
|
# Same with @hide_legend
|
877
863
|
@top_margin +
|
878
|
-
(hide_title? ? @title_margin :
|
864
|
+
(hide_title? ? @title_margin : title_caps_height + @title_margin) +
|
879
865
|
(@hide_legend || @legend_at_bottom ? @legend_margin : calculate_legend_height + @legend_margin)
|
880
866
|
end
|
881
867
|
|
882
868
|
def setup_bottom_margin
|
883
|
-
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin +
|
869
|
+
graph_bottom_margin = hide_bottom_label_area? ? @bottom_margin : @bottom_margin + marker_caps_height + LABEL_MARGIN
|
884
870
|
graph_bottom_margin += (calculate_legend_height + @legend_margin) if @legend_at_bottom
|
885
871
|
|
886
|
-
x_axis_label_height = @x_axis_label.nil? ? 0.0 :
|
887
|
-
# FIXME: Consider chart types other than bar
|
872
|
+
x_axis_label_height = @x_axis_label.nil? ? 0.0 : marker_caps_height + (LABEL_MARGIN * 2)
|
888
873
|
@raw_rows - graph_bottom_margin - x_axis_label_height - @label_stagger_height
|
889
874
|
end
|
890
875
|
|
@@ -919,7 +904,7 @@ module Gruff
|
|
919
904
|
else
|
920
905
|
value.to_s
|
921
906
|
end
|
922
|
-
elsif (@spread
|
907
|
+
elsif (@spread % (marker_count == 0 ? 1 : marker_count) == 0) || !@y_axis_increment.nil?
|
923
908
|
value.to_i.to_s
|
924
909
|
elsif @spread > 10.0
|
925
910
|
sprintf('%0i', value)
|
@@ -952,44 +937,31 @@ module Gruff
|
|
952
937
|
end
|
953
938
|
|
954
939
|
def calculate_legend_label_widths_for_each_line(legend_labels, legend_square_width)
|
955
|
-
|
956
|
-
|
940
|
+
label_widths = [[]]
|
941
|
+
label_lines = [[]]
|
957
942
|
legend_labels.each do |label|
|
958
943
|
width = calculate_width(@legend_font, label)
|
959
|
-
label_width = width + legend_square_width * 2.7
|
944
|
+
label_width = width + (legend_square_width * 2.7)
|
960
945
|
label_widths.last.push label_width
|
946
|
+
label_lines.last.push label
|
961
947
|
|
962
948
|
if label_widths.last.sum > (@raw_columns * 0.9)
|
963
949
|
label_widths.push [label_widths.last.pop]
|
950
|
+
label_lines.push [label_lines.last.pop]
|
964
951
|
end
|
965
952
|
end
|
966
953
|
|
967
|
-
label_widths
|
954
|
+
label_widths.map(&:sum).zip(label_lines)
|
968
955
|
end
|
969
956
|
|
970
957
|
def calculate_legend_height
|
971
958
|
return 0.0 if @hide_legend
|
972
959
|
|
973
960
|
legend_labels = store.data.map(&:label)
|
974
|
-
|
975
|
-
|
976
|
-
legend_height = 0.0
|
977
|
-
|
978
|
-
legend_labels.each_with_index do |legend_label, _index|
|
979
|
-
next if legend_label.empty?
|
980
|
-
|
981
|
-
label_widths.first.shift
|
982
|
-
if label_widths.first.empty?
|
983
|
-
label_widths.shift
|
984
|
-
line_height = [@legend_caps_height, legend_square_width].max + @legend_margin
|
985
|
-
unless label_widths.empty?
|
986
|
-
# Wrap to next line and shrink available graph dimensions
|
987
|
-
legend_height += line_height
|
988
|
-
end
|
989
|
-
end
|
990
|
-
end
|
961
|
+
legend_label_lines = calculate_legend_label_widths_for_each_line(legend_labels, @legend_box_size)
|
962
|
+
line_height = [legend_caps_height, @legend_box_size].max
|
991
963
|
|
992
|
-
|
964
|
+
(line_height * legend_label_lines.count) + (@legend_margin * (legend_label_lines.count - 1))
|
993
965
|
end
|
994
966
|
|
995
967
|
# Returns the height of the capital letter 'X' for the current font and
|
@@ -998,7 +970,18 @@ module Gruff
|
|
998
970
|
# Not scaled since it deals with dimensions that the regular scaling will
|
999
971
|
# handle.
|
1000
972
|
def calculate_caps_height(font)
|
1001
|
-
|
973
|
+
calculate_height(font, 'X')
|
974
|
+
end
|
975
|
+
|
976
|
+
# Returns the height of a string at this point size.
|
977
|
+
#
|
978
|
+
# Not scaled since it deals with dimensions that the regular scaling will
|
979
|
+
# handle.
|
980
|
+
def calculate_height(font, text)
|
981
|
+
text = text.to_s
|
982
|
+
return 0 if text.empty?
|
983
|
+
|
984
|
+
metrics = text_metrics(font, text)
|
1002
985
|
metrics.height
|
1003
986
|
end
|
1004
987
|
|
@@ -1010,10 +993,14 @@ module Gruff
|
|
1010
993
|
text = text.to_s
|
1011
994
|
return 0 if text.empty?
|
1012
995
|
|
1013
|
-
metrics =
|
996
|
+
metrics = text_metrics(font, text)
|
1014
997
|
metrics.width
|
1015
998
|
end
|
1016
999
|
|
1000
|
+
def text_metrics(font, text)
|
1001
|
+
Gruff::Renderer::Text.new(renderer, text, font: font).metrics
|
1002
|
+
end
|
1003
|
+
|
1017
1004
|
def calculate_increment
|
1018
1005
|
if @y_axis_increment.nil?
|
1019
1006
|
# Try to use a number of horizontal lines that will come out even.
|
@@ -1027,9 +1014,13 @@ module Gruff
|
|
1027
1014
|
end
|
1028
1015
|
end
|
1029
1016
|
|
1030
|
-
# Used for degree
|
1017
|
+
# Used for degree <=> radian conversions
|
1031
1018
|
def deg2rad(angle)
|
1032
|
-
angle *
|
1019
|
+
(angle * Math::PI) / 180.0
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
def rad2deg(angle)
|
1023
|
+
(angle / Math::PI) * 180.0
|
1033
1024
|
end
|
1034
1025
|
end
|
1035
1026
|
|
data/lib/gruff/bezier.rb
CHANGED
@@ -22,7 +22,7 @@ class Gruff::Bezier < Gruff::Base
|
|
22
22
|
private
|
23
23
|
|
24
24
|
def draw_graph
|
25
|
-
x_increment = @graph_width / (column_count - 1)
|
25
|
+
x_increment = @graph_width / (column_count - 1)
|
26
26
|
|
27
27
|
store.norm_data.each do |data_row|
|
28
28
|
poly_points = []
|
@@ -30,7 +30,7 @@ private
|
|
30
30
|
data_row[1].each_with_index do |data_point, index|
|
31
31
|
# Use incremented x and scaled y
|
32
32
|
new_x = @graph_left + (x_increment * index)
|
33
|
-
new_y = @graph_top + (@graph_height - data_point * @graph_height)
|
33
|
+
new_y = @graph_top + (@graph_height - (data_point * @graph_height))
|
34
34
|
|
35
35
|
if index == 0 && RUBY_PLATFORM != 'java'
|
36
36
|
poly_points << new_x
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Here's how to set up a Gruff::BoxPlot.
|
5
|
+
#
|
6
|
+
# g = Gruff::BoxPlot.new
|
7
|
+
# g.data "A", [2, 3, 5, 6, 8, 10, 11, 15, 17, 20, 28, 29, 33, 34, 45, 46, 49, 61]
|
8
|
+
# g.data "B", [3, 4, 34, 35, 38, 39, 45, 60, 61, 69, 80, 130]
|
9
|
+
# g.data "C", [4, 40, 41, 46, 57, 64, 77, 76, 79, 78, 99, 153]
|
10
|
+
# g.write("box_plot.png")
|
11
|
+
#
|
12
|
+
class Gruff::BoxPlot < Gruff::Base
|
13
|
+
# Specifies the filling opacity in area graph. Default is +0.2+.
|
14
|
+
attr_writer :fill_opacity
|
15
|
+
|
16
|
+
# Specifies the stroke width in line. Default is +3.0+.
|
17
|
+
attr_writer :stroke_width
|
18
|
+
|
19
|
+
# Can be used to adjust the spaces between the bars.
|
20
|
+
# Accepts values between 0.00 and 1.00 where 0.00 means no spacing at all
|
21
|
+
# and 1 means that each bars' width is nearly 0 (so each bar is a simple
|
22
|
+
# line with no x dimension).
|
23
|
+
#
|
24
|
+
# Default value is +0.8+.
|
25
|
+
def spacing_factor=(space_percent)
|
26
|
+
raise ArgumentError, 'spacing_factor must be between 0.00 and 1.00' unless (space_percent >= 0) && (space_percent <= 1)
|
27
|
+
|
28
|
+
@spacing_factor = (1 - space_percent)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize_attributes
|
34
|
+
super
|
35
|
+
@fill_opacity = 0.2
|
36
|
+
@stroke_width = 3.0
|
37
|
+
@spacing_factor = 0.8
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw_graph
|
41
|
+
# Setup the BarConversion Object
|
42
|
+
conversion = Gruff::BarConversion.new(
|
43
|
+
top: @graph_top, bottom: @graph_bottom,
|
44
|
+
minimum_value: minimum_value, maximum_value: maximum_value, spread: @spread
|
45
|
+
)
|
46
|
+
|
47
|
+
width = (@graph_width - calculate_spacing) / normalized_boxes.size
|
48
|
+
bar_width = width * @spacing_factor
|
49
|
+
padding = width - bar_width
|
50
|
+
|
51
|
+
normalized_boxes.each_with_index do |box, index|
|
52
|
+
left_x = @graph_left + (width * index) + (padding / 2.0)
|
53
|
+
right_x = left_x + bar_width
|
54
|
+
center_x = (left_x + right_x) / 2.0
|
55
|
+
|
56
|
+
first_y, = conversion.get_top_bottom_scaled(box.first_quartile)
|
57
|
+
third_y, = conversion.get_top_bottom_scaled(box.third_quartile)
|
58
|
+
Gruff::Renderer::Rectangle.new(renderer, color: box.color, width: @stroke_width, opacity: @fill_opacity)
|
59
|
+
.render(left_x, first_y, right_x, third_y)
|
60
|
+
|
61
|
+
median_y, = conversion.get_top_bottom_scaled(box.median)
|
62
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width * 2).render(left_x, median_y, right_x, median_y)
|
63
|
+
|
64
|
+
minmax_left_x = left_x + (bar_width / 4.0)
|
65
|
+
minmax_right_x = right_x - (bar_width / 4.0)
|
66
|
+
min_y, = conversion.get_top_bottom_scaled(box.lower_whisker)
|
67
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, min_y, minmax_right_x, min_y)
|
68
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
69
|
+
.render(center_x, min_y, center_x, first_y)
|
70
|
+
|
71
|
+
max_y, = conversion.get_top_bottom_scaled(box.upper_whisker)
|
72
|
+
Gruff::Renderer::Line.new(renderer, color: box.color, width: @stroke_width).render(minmax_left_x, max_y, minmax_right_x, max_y)
|
73
|
+
Gruff::Renderer::DashLine.new(renderer, color: box.color, width: @stroke_width, dasharray: [@stroke_width, @stroke_width * 2])
|
74
|
+
.render(center_x, max_y, center_x, third_y)
|
75
|
+
|
76
|
+
box.lower_outliers.each do |outlier|
|
77
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
78
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
79
|
+
end
|
80
|
+
|
81
|
+
box.upper_outliers.each do |outlier|
|
82
|
+
outlier_y, = conversion.get_top_bottom_scaled(outlier)
|
83
|
+
Gruff::Renderer::Dot.new(renderer, :circle, color: box.color, opacity: @fill_opacity).render(center_x, outlier_y, @stroke_width * 2)
|
84
|
+
end
|
85
|
+
|
86
|
+
draw_label(center_x, index)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def normalized_boxes
|
91
|
+
@normalized_boxes ||= store.norm_data.map { |data| Gruff::BoxPlot::BoxData.new(data.label, data.points, data.color) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def calculate_spacing
|
95
|
+
@scale * (column_count - 1)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @private
|
99
|
+
class BoxData < Struct.new(:label, :points, :color)
|
100
|
+
def initialize(label, points, color)
|
101
|
+
super(label, points.sort, color)
|
102
|
+
end
|
103
|
+
|
104
|
+
def min
|
105
|
+
points.first || 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def max
|
109
|
+
points.last || 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def min_whisker
|
113
|
+
[min, first_quartile - (1.5 * interquartile_range)].max
|
114
|
+
end
|
115
|
+
|
116
|
+
def max_whisker
|
117
|
+
[max, third_quartile + (1.5 * interquartile_range)].min
|
118
|
+
end
|
119
|
+
|
120
|
+
def upper_whisker
|
121
|
+
max = max_whisker
|
122
|
+
points.select { |point| point <= max }.max
|
123
|
+
end
|
124
|
+
|
125
|
+
def lower_whisker
|
126
|
+
min = min_whisker
|
127
|
+
points.select { |point| point >= min }.min
|
128
|
+
end
|
129
|
+
|
130
|
+
def median
|
131
|
+
if points.size.zero?
|
132
|
+
0
|
133
|
+
elsif points.size.odd?
|
134
|
+
points[points.size / 2]
|
135
|
+
else
|
136
|
+
(points[points.size / 2] + points[(points.size / 2) - 1]) / 2.0
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def first_quartile
|
141
|
+
if points.size.zero?
|
142
|
+
0
|
143
|
+
elsif points.size.odd?
|
144
|
+
points[points.size / 4]
|
145
|
+
else
|
146
|
+
(points[points.size / 4] + points[(points.size / 4) - 1]) / 2.0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def third_quartile
|
151
|
+
if points.size.zero?
|
152
|
+
0
|
153
|
+
elsif points.size.odd?
|
154
|
+
points[(points.size * 3) / 4]
|
155
|
+
else
|
156
|
+
(points[(points.size * 3) / 4] + points[((points.size * 3) / 4) - 1]) / 2.0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def lower_outliers
|
161
|
+
min = lower_whisker
|
162
|
+
points.select { |point| point < min }
|
163
|
+
end
|
164
|
+
|
165
|
+
def upper_outliers
|
166
|
+
max = upper_whisker
|
167
|
+
points.select { |point| point > max }
|
168
|
+
end
|
169
|
+
|
170
|
+
def interquartile_range
|
171
|
+
third_quartile - first_quartile
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|