svg-graph 2.0.1 → 2.0.2.beta

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0a6c4dcff26a2412d42bb11f392ca6292afde8d
4
- data.tar.gz: b91765dbbf54900ffeba3f665af1b17429e72d05
3
+ metadata.gz: d856850b800bff5e1688e0ad450ccbcc21bdd21d
4
+ data.tar.gz: 095e8398439866cc6cd52439d60d036ef960ac8a
5
5
  SHA512:
6
- metadata.gz: d63ea323203f76843f1ba2b07b1db9522b7619a871467064b7f06e87e9e4d55f94404a263d513c1d295e38f2377cec4beaba53549c30f4d8142a91cc6ef26550
7
- data.tar.gz: e682a1ee1c37dfdad7f95a52942334ba3f0eee7a001779cf0fd696745f3a7cdb5a54f265312351365992a7602d984e1b003297e1987d4cae170b8c0c096f8032
6
+ metadata.gz: 69d1f607ba2edec2452d14e39fdb18079bc59a76c52898e101d7adff4c383d8ae40c589e45a001eb3b50912bd2d46af6ac89e4d1b9a9a68f87968a8cf2382877
7
+ data.tar.gz: 2cb188581fe18553a4c1a2527168f4d4988cf2b47dd6577dadf248b4d93fe09d26608f164bdca8fddb9d97ee9feece347b1287ee8fef12e325245a11d63b05d6
data/History.txt CHANGED
@@ -1,7 +1,18 @@
1
1
  TODO
2
- * fix axis titles positioning for timeseries and schedule graphs
2
+ * fix axis titles positioning for staggered axis (prevent overlap)
3
+ * fix scale_integer
4
+ * fix sorting and connected lines for Plot
3
5
 
4
- === 2.0.0 / 2016-08-09
6
+
7
+ === 2.0.2 work in progress
8
+ * fix axis-title positioning
9
+ * fix/add support for popups of values line, plot, bar graph
10
+ * Line,Plot,Pie graph support nil values (or pairs of nil for plot) for non-present datapoints
11
+ * changed text anchor of first datapoint label to avoid overlap with axis
12
+ * fix pie graph key overlapping the graph
13
+ *
14
+
15
+ === 2.0.1 / 2016-08-09
5
16
  * dropped support for ruby 1.8.7 [lumean]
6
17
  * Merge ErrBar graph from https://github.com/mondhs/svg-graph
7
18
  * Merge number formatting to have more readable floats from https://github.com/mondhs/svg-graph
data/README.markdown CHANGED
@@ -74,10 +74,14 @@ File.open('bar.svg', 'w') {|f| f.write(g.burn_svg_only)}
74
74
 
75
75
  ### BarHorizontal
76
76
 
77
+ ![example bar_horizontal graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/bar_horizontal.svg)
78
+
77
79
  ### ErrBar
78
80
 
79
81
  ### Line
80
82
 
83
+ ![example line graph](https://cdn.rawgit.com/lumean/svg-graph2/master/examples/line.svg)
84
+
81
85
  ### Pie
82
86
 
83
87
  ### Plot
data/lib/SVG/Graph/Bar.rb CHANGED
@@ -61,7 +61,7 @@ module SVG
61
61
  # See Graph::initialize and BarBase::set_defaults
62
62
  def set_defaults
63
63
  super
64
- self.top_align = self.top_font = 1
64
+ # self.top_align = self.top_font = 1
65
65
  end
66
66
 
67
67
  protected
@@ -78,16 +78,17 @@ module SVG
78
78
  top_pad = range == 0 ? 10 : range / 20.0
79
79
  scale_range = (maxvalue + top_pad) - minvalue
80
80
 
81
- scale_division = scale_divisions || (scale_range / 10.0)
81
+ @y_scale_division = scale_divisions || (scale_range / 10.0)
82
82
 
83
83
  if scale_integers
84
- scale_division = scale_division < 1 ? 1 : scale_division.round
84
+ @y_scale_division = @y_scale_division < 1 ? 1 : @y_scale_division.round
85
85
  end
86
86
 
87
87
  rv = []
88
- maxvalue = maxvalue%scale_division == 0 ?
89
- maxvalue : maxvalue + scale_division
90
- minvalue.step( maxvalue, scale_division ) {|v| rv << v}
88
+ if maxvalue%@y_scale_division != 0
89
+ maxvalue = maxvalue + @y_scale_division
90
+ end
91
+ minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
91
92
  return rv
92
93
  end
93
94
 
@@ -99,8 +100,9 @@ module SVG
99
100
  minvalue = min_value
100
101
  fieldwidth = field_width
101
102
 
102
- unit_size = (@graph_height.to_f - font_size*2*top_font) /
103
- (get_y_labels.max - get_y_labels.min)
103
+ # unit_size = (@graph_height.to_f - font_size*2*top_font) /
104
+ # (get_y_labels.max - get_y_labels.min)
105
+ unit_size = field_height
104
106
  bargap = bar_gap ? (fieldwidth < 10 ? fieldwidth / 2 : 10) : 0
105
107
 
106
108
  bar_width = fieldwidth - bargap
@@ -120,7 +122,7 @@ module SVG
120
122
  # +ve -ve value - 0
121
123
  # -ve -ve value.abs - 0
122
124
 
123
- value = dataset[:data][i]
125
+ value = dataset[:data][i]/@y_scale_division
124
126
 
125
127
  left = (fieldwidth * field_count)
126
128
 
@@ -137,7 +139,8 @@ module SVG
137
139
  "class" => "fill#{dataset_count+1}"
138
140
  })
139
141
 
140
- make_datapoint_text(left + bar_width/2.0, top - 6, value.to_s)
142
+ make_datapoint_text(left + bar_width/2.0, top - font_size/2, dataset[:data][i].to_s)
143
+ add_popup(left + bar_width/2.0, top , dataset[:data][i].to_s)
141
144
  dataset_count += 1
142
145
  end
143
146
  field_count += 1
@@ -67,7 +67,7 @@ module SVG
67
67
  :show_x_guidelines => true,
68
68
  :show_y_guidelines => false
69
69
  )
70
- self.right_align = self.right_font = 1
70
+ # self.right_align = self.right_font = 1
71
71
  end
72
72
 
73
73
  protected
@@ -79,16 +79,17 @@ module SVG
79
79
  top_pad = range == 0 ? 10 : range / 20.0
80
80
  scale_range = (maxvalue + top_pad) - minvalue
81
81
 
82
- scale_division = scale_divisions || (scale_range / 10.0)
82
+ @x_scale_division = scale_divisions || (scale_range / 10.0)
83
83
 
84
84
  if scale_integers
85
- scale_division = scale_division < 1 ? 1 : scale_division.round
85
+ @x_scale_division = @x_scale_division < 1 ? 1 : @x_scale_division.round
86
86
  end
87
87
 
88
88
  rv = []
89
- maxvalue = maxvalue%scale_division == 0 ?
90
- maxvalue : maxvalue + scale_division
91
- minvalue.step( maxvalue, scale_division ) {|v| rv << v}
89
+ if maxvalue%@x_scale_division != 0
90
+ maxvalue = maxvalue + @x_scale_division
91
+ end
92
+ minvalue.step( maxvalue, @x_scale_division ) {|v| rv << v}
92
93
  return rv
93
94
  end
94
95
 
@@ -103,9 +104,10 @@ module SVG
103
104
  def draw_data
104
105
  minvalue = min_value
105
106
  fieldheight = field_height
106
-
107
- unit_size = (@graph_width.to_f - font_size*2*right_font ) /
108
- (get_x_labels.max - get_x_labels.min )
107
+ # number of steps in px between x-labels
108
+ # unit_size = (@graph_width.to_f - font_size*2*right_font ) /
109
+ # (get_x_labels.max - get_x_labels.min )
110
+ unit_size = field_width
109
111
  bargap = bar_gap ? (fieldheight < 10 ? fieldheight / 2 : 10) : 0
110
112
 
111
113
  bar_height = fieldheight - bargap
@@ -116,7 +118,7 @@ module SVG
116
118
  @config[:fields].each_index { |i|
117
119
  dataset_count = 0
118
120
  for dataset in @data
119
- value = dataset[:data][i]
121
+ value = dataset[:data][i]/@x_scale_division
120
122
 
121
123
  top = @graph_height - (fieldheight * field_count)
122
124
  top += (bar_height * dataset_count) if stack == :side
@@ -137,8 +139,9 @@ module SVG
137
139
  })
138
140
 
139
141
  make_datapoint_text(
140
- left+length+5, top+y_mod, value, "text-anchor: start; "
142
+ left+length+5, top+y_mod, dataset[:data][i].to_s, "text-anchor: start; "
141
143
  )
144
+ add_popup(left+length, top+y_mod , dataset[:data][i].to_s)
142
145
  dataset_count += 1
143
146
  end
144
147
  field_count += 1
@@ -69,7 +69,7 @@ module SVG
69
69
  # See Graph::initialize and BarBase::set_defaults
70
70
  def set_defaults
71
71
  super
72
- self.top_align = self.top_font = 1
72
+ # self.top_align = self.top_font = 1
73
73
  end
74
74
 
75
75
  protected
@@ -102,7 +102,8 @@ module SVG
102
102
  def initialize( config )
103
103
  @config = config
104
104
  @data = []
105
- self.top_align = self.top_font = self.right_align = self.right_font = 0
105
+ #self.top_align = self.top_font = 0
106
+ #self.right_align = self.right_font = 0
106
107
 
107
108
  init_with({
108
109
  :width => 500,
@@ -192,12 +193,15 @@ module SVG
192
193
  #
193
194
  # This method will croak unless at least one data set has
194
195
  # been added to the graph object.
195
- #
196
+ #
196
197
  # print graph.burn
198
+ #
197
199
  def burn
198
200
  raise "No data available" unless @data.size > 0
199
201
 
200
- calculations if methods.include? 'calculations'
202
+ # undocumented and not used in any sublass
203
+ # to be removed
204
+ #calculations if methods.include? 'calculations'
201
205
 
202
206
  start_svg
203
207
  calculate_graph_dimensions
@@ -205,7 +209,7 @@ module SVG
205
209
  draw_graph
206
210
  draw_titles
207
211
  draw_legend
208
- draw_data
212
+ draw_data # this method needs to be implemented by child classes
209
213
  @graph.add_element( @foreground )
210
214
  style
211
215
 
@@ -278,7 +282,7 @@ module SVG
278
282
  # Default it false, to turn on set to true.
279
283
  attr_accessor :rotate_x_labels
280
284
  # This turns the Y axis labels by 90 degrees.
281
- # Default it false, to turn on set to true.
285
+ # Default it true, to turn on set to false.
282
286
  attr_accessor :rotate_y_labels
283
287
  # How many "steps" to use between displayed X axis labels,
284
288
  # a step of one means display every label, a step of two results
@@ -382,7 +386,9 @@ module SVG
382
386
 
383
387
 
384
388
  protected
385
-
389
+
390
+ # implementation of quicksort
391
+ # used for Schedule and Plot
386
392
  def sort( *arrys )
387
393
  sort_multiple( arrys )
388
394
  end
@@ -396,35 +402,45 @@ module SVG
396
402
  @popup_radius ||= 10
397
403
  end
398
404
 
399
- attr_accessor :top_align, :top_font, :right_align, :right_font
405
+ # unknown why needed
406
+ # attr_accessor :top_align, :top_font, :right_align, :right_font
400
407
 
408
+ # size of the square box in the legend which indicates the colors
401
409
  KEY_BOX_SIZE = 12
402
410
 
403
411
  # Override this (and call super) to change the margin to the left
404
412
  # of the plot area. Results in @border_left being set.
413
+ #
414
+ # By default it is 7 + max label height(font size or string length, depending on rotate) + title height
405
415
  def calculate_left_margin
406
416
  @border_left = 7
407
417
  # Check size of Y labels
408
- max_y_label_height_px = y_label_font_size
409
- if !rotate_y_labels
410
- max_y_label_height_px = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
418
+ @border_left += max_y_label_width_px
419
+ if (show_y_title && (y_title_location ==:middle))
420
+ @border_left += y_title_font_size + 5
411
421
  end
412
-
413
- @border_left += max_y_label_height_px if show_y_labels
414
- @border_left += max_y_label_height_px + 10 if stagger_y_labels
415
- @border_left += y_title_font_size + 5 if (show_y_title && (y_title_location ==:middle))
416
422
  end
417
423
 
418
-
419
424
  # Calculates the width of the widest Y label. This will be the
420
- # character height if the Y labels are rotated
425
+ # character height if the Y labels are rotated. Returns 0 if labels
426
+ # are not shown
421
427
  def max_y_label_width_px
422
- return font_size if rotate_y_labels
428
+ return 0 if !show_y_labels
429
+ if !rotate_y_labels
430
+ max_width = get_longest_label(get_y_labels).to_s.length * y_label_font_size * 0.6
431
+ else
432
+ max_width = y_label_font_size + 3
433
+ end
434
+ max_width += 10 if stagger_y_labels
435
+ return max_width
423
436
  end
424
437
 
425
438
 
426
439
  # Override this (and call super) to change the margin to the right
427
440
  # of the plot area. Results in @border_right being set.
441
+ #
442
+ # By default it is 7 + width of the key if it is placed on the right
443
+ # or the maximum of this value or the tilte length (if title is placed at :end)
428
444
  def calculate_right_margin
429
445
  @border_right = 7
430
446
  if key and key_position == :right
@@ -441,6 +457,8 @@ module SVG
441
457
 
442
458
  # Override this (and call super) to change the margin to the top
443
459
  # of the plot area. Results in @border_top being set.
460
+ #
461
+ # This is 5 + the Title size + 5 + subTitle size
444
462
  def calculate_top_margin
445
463
  @border_top = 5
446
464
  @border_top += [title_font_size, y_title_font_size].max if (show_graph_title || (y_title_location ==:end))
@@ -448,32 +466,42 @@ module SVG
448
466
  @border_top += subtitle_font_size if show_graph_subtitle
449
467
  end
450
468
 
469
+ def add_datapoint_text_and_popup( x, y, label )
470
+ add_popup( x, y, label )
471
+ make_datapoint_text( x, y, label )
472
+ end
451
473
 
452
- # Adds pop-up point information to a graph.
453
- def add_popup( x, y, label )
454
- txt_width = label.length * font_size * 0.6 + 10
455
- tx = (x+txt_width > width ? x-5 : x+5)
456
- t = @foreground.add_element( "text", {
457
- "x" => tx.to_s,
458
- "y" => (y - font_size).to_s,
459
- "visibility" => "hidden",
460
- })
461
- t.attributes["style"] = "fill: #000; "+
462
- (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
463
- t.text = label.to_s
464
- t.attributes["id"] = t.object_id.to_s
465
-
466
- @foreground.add_element( "circle", {
467
- "cx" => x.to_s,
468
- "cy" => y.to_s,
469
- "r" => "#{@popup_radius}",
470
- "style" => "opacity: 0",
471
- "onmouseover" =>
472
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
473
- "onmouseout" =>
474
- "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
475
- })
476
-
474
+ # Adds pop-up point information to a graph only if the config option is set.
475
+ def add_popup( x, y, label, style="" )
476
+ if add_popups
477
+ if( numeric?(label) )
478
+ label = @number_format % label
479
+ end
480
+ txt_width = label.length * font_size * 0.6 + 10
481
+ tx = (x+txt_width > width ? x-5 : x+5)
482
+ t = @foreground.add_element( "text", {
483
+ "x" => tx.to_s,
484
+ "y" => (y - font_size).to_s,
485
+ "class" => "dataPointLabel",
486
+ "visibility" => "hidden",
487
+ })
488
+ t.attributes["style"] = "stroke-width: 2; fill: #000; #{style}"+
489
+ (x+txt_width > width ? "text-anchor: end;" : "text-anchor: start;")
490
+ t.text = label.to_s
491
+ t.attributes["id"] = t.object_id.to_s
492
+
493
+ # add a circle to catch the mouseover
494
+ @foreground.add_element( "circle", {
495
+ "cx" => x.to_s,
496
+ "cy" => y.to_s,
497
+ "r" => "#{popup_radius}",
498
+ "style" => "opacity: 0",
499
+ "onmouseover" =>
500
+ "document.getElementById(#{t.object_id}).setAttribute('visibility', 'visible' )",
501
+ "onmouseout" =>
502
+ "document.getElementById(#{t.object_id}).setAttribute('visibility', 'hidden' )",
503
+ })
504
+ end # add_popups
477
505
  end
478
506
 
479
507
  # returns the longest label from an array of labels as string
@@ -491,22 +519,32 @@ module SVG
491
519
 
492
520
  # Override this (and call super) to change the margin to the bottom
493
521
  # of the plot area. Results in @border_bottom being set.
522
+ #
523
+ # 7 + max label height(font size or string length, depending on rotate) + title height
494
524
  def calculate_bottom_margin
495
525
  @border_bottom = 7
496
526
  if key and key_position == :bottom
497
527
  @border_bottom += @data.size * (font_size + 5)
498
528
  @border_bottom += 10
499
529
  end
500
- if show_x_labels
501
- max_x_label_height_px = x_label_font_size
502
- if rotate_x_labels
503
- max_x_label_height_px = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6
504
- end
505
-
506
- @border_bottom += max_x_label_height_px
507
- @border_bottom += max_x_label_height_px + 10 if stagger_x_labels
530
+ @border_bottom += max_x_label_height_px
531
+ if (show_x_title && (x_title_location ==:middle))
532
+ @border_bottom += x_title_font_size + 5
533
+ end
534
+ end
535
+
536
+ # returns the maximum height of the labels respect the rotation or 0 if
537
+ # the labels are not shown
538
+ def max_x_label_height_px
539
+ return 0 if !show_x_labels
540
+
541
+ if rotate_x_labels
542
+ max_height = get_longest_label(get_x_labels).to_s.length * x_label_font_size * 0.6
543
+ else
544
+ max_height = x_label_font_size + 3
508
545
  end
509
- @border_bottom += x_title_font_size + 5 if (show_x_title && (x_title_location ==:middle))
546
+ max_height += 10 if stagger_x_labels
547
+ return max_height
510
548
  end
511
549
 
512
550
 
@@ -553,18 +591,25 @@ module SVG
553
591
  true if Float(object) rescue false
554
592
  end
555
593
 
594
+ # adds the datapoint text to the graph only if the config option is set
556
595
  def make_datapoint_text( x, y, value, style="" )
557
596
  if show_data_values
558
597
  textStr = value
559
598
  if( numeric?(value) )
560
599
  textStr = @number_format % value
561
600
  end
601
+ # change anchor is label overlaps axis
602
+ if x < textStr.length/2 * font_size * 0.6
603
+ style << "text-anchor: start;"
604
+ end
605
+ # white background for better readability
562
606
  @foreground.add_element( "text", {
563
607
  "x" => x.to_s,
564
608
  "y" => y.to_s,
565
609
  "class" => "dataPointLabel",
566
610
  "style" => "#{style} stroke: #fff; stroke-width: 2;"
567
611
  }).text = textStr
612
+ # actual label
568
613
  text = @foreground.add_element( "text", {
569
614
  "x" => x.to_s,
570
615
  "y" => y.to_s,
@@ -574,7 +619,7 @@ module SVG
574
619
  text.attributes["style"] = style if style.length > 0
575
620
  end
576
621
  end
577
-
622
+
578
623
 
579
624
  # Draws the X axis labels
580
625
  def draw_x_labels
@@ -591,6 +636,10 @@ module SVG
591
636
  end
592
637
 
593
638
  if step == 0 then
639
+ label = label.to_s
640
+ if( numeric?(label) )
641
+ label = @number_format % label
642
+ end
594
643
  text = @graph.add_element( "text" )
595
644
  text.attributes["class"] = "xAxisLabels"
596
645
  text.text = label.to_s
@@ -632,20 +681,39 @@ module SVG
632
681
  0
633
682
  end
634
683
 
635
-
684
+ # override this method in child class
685
+ # must return the array of labels for the x-axis
686
+ def get_x_labels
687
+ end
688
+
689
+ # override this method in child class
690
+ # must return the array of labels for the y-axis
691
+ # this method defines @y_scale_division
692
+ def get_y_labels
693
+ end
694
+
695
+ # space in px between x-labels
636
696
  def field_width
637
- (@graph_width.to_f - font_size*2*right_font) /
638
- (get_x_labels.length - right_align)
697
+ #(@graph_width.to_f - font_size*2*right_font) /
698
+ # (get_x_labels.length - right_align)
699
+ @graph_width.to_f / get_x_labels.length
639
700
  end
640
701
 
641
-
702
+ # space in px between the y-labels
642
703
  def field_height
643
- (@graph_height.to_f - font_size*2*top_font) /
644
- (get_y_labels.length - top_align)
704
+ #(@graph_height.to_f - font_size*2*top_font) /
705
+ # (get_y_labels.length - top_align)
706
+ @graph_height.to_f / get_y_labels.length
645
707
  end
646
708
 
647
709
 
648
- # Draws the Y axis labels
710
+ # Draws the Y axis labels, the Y-Axis (@graph_height) is divided equally into #get_y_labels.lenght sections
711
+ # So the y coordinate for an arbitrary value is calculated as follows:
712
+ # y = @graph_height equals the min_value
713
+ # #normalize value of a single scale_division:
714
+ # count = value /(@y_scale_division)
715
+ # y = @graph_height - count * field_height
716
+ #
649
717
  def draw_y_labels
650
718
  stagger = y_label_font_size + 5
651
719
  if show_y_labels
@@ -735,16 +803,12 @@ module SVG
735
803
  end
736
804
 
737
805
  if show_x_title
738
- y = @graph_height + @border_top + x_title_font_size
739
806
  if (x_title_location == :end)
740
- y = y - x_title_font_size/2.0
741
- x = width - x_title.length * x_title_font_size * 0.6/2.0
807
+ y = @graph_height + @border_top + x_title_font_size/2.0
808
+ x = @border_left + @graph_width + x_title.length * x_title_font_size * 0.6/2.0
742
809
  else
743
- x = width / 2
744
- if show_x_labels
745
- y += x_label_font_size + 5 if stagger_x_labels
746
- y += x_label_font_size + 5
747
- end
810
+ y = @graph_height + @border_top + x_title_font_size + max_x_label_height_px
811
+ x = @border_left + @graph_width / 2
748
812
  end
749
813
 
750
814
  @root.add_element("text", {
@@ -755,12 +819,12 @@ module SVG
755
819
  end
756
820
 
757
821
  if show_y_title
758
- x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
759
822
  if (y_title_location == :end)
760
823
  x = y_title.length * y_title_font_size * 0.6/2.0 # positioning is not optimal but ok for now
761
824
  y = @border_top - y_title_font_size/2.0
762
825
  else
763
- y = height / 2
826
+ x = y_title_font_size + (y_title_text_direction==:bt ? 3 : -3)
827
+ y = @border_top + @graph_height / 2
764
828
  end
765
829
  text = @root.add_element("text", {
766
830
  "x" => x.to_s,
@@ -816,12 +880,12 @@ module SVG
816
880
  x_offset = @border_left + 20
817
881
  y_offset = @border_top + @graph_height + 5
818
882
  if show_x_labels
819
- max_x_label_height_px = (not rotate_x_labels) ?
820
- x_label_font_size :
821
- get_x_labels.max{|a,b|
822
- a.to_s.length<=>b.to_s.length
823
- }.to_s.length * x_label_font_size * 0.6
824
- x_label_font_size
883
+ # max_x_label_height_px = (not rotate_x_labels) ?
884
+ # x_label_font_size :
885
+ # get_x_labels.max{|a,b|
886
+ # a.to_s.length<=>b.to_s.length
887
+ # }.to_s.length * x_label_font_size * 0.6
888
+ # x_label_font_size
825
889
  y_offset += max_x_label_height_px
826
890
  y_offset += max_x_label_height_px + 5 if stagger_x_labels
827
891
  end
@@ -898,10 +962,17 @@ module SVG
898
962
 
899
963
 
900
964
  # Override and place code to add defs here
965
+ # @param defs [REXML::Element]
901
966
  def add_defs defs
902
967
  end
903
968
 
904
-
969
+ # Creates the XML document and adds the root svg element with
970
+ # the width, height and viewBox attributes already set.
971
+ # The element is stored as @root.
972
+ #
973
+ # In addition a rectangle background of the same size as the
974
+ # svg is added.
975
+ #
905
976
  def start_svg
906
977
  # Base document
907
978
  @doc = Document.new
@@ -946,7 +1017,7 @@ module SVG
946
1017
  })
947
1018
  end
948
1019
 
949
-
1020
+ #
950
1021
  def calculate_graph_dimensions
951
1022
  calculate_left_margin
952
1023
  calculate_right_margin
@@ -45,7 +45,10 @@ module SVG
45
45
  # http://www.germane-software/repositories/public/SVG/test/single.rb
46
46
  #
47
47
  # = Notes
48
- #
48
+ # Only number of fileds datapoints will be drawn, additional data values
49
+ # are ignored. Nil values in data are skipped and
50
+ # interpolated as straight line to the next datapoint.
51
+ #
49
52
  # The default stylesheet handles upto 10 data sets, if you
50
53
  # use more you must create your own stylesheet and add the
51
54
  # additional settings for the extra data sets. You will know
@@ -78,65 +81,64 @@ module SVG
78
81
  # Fill in the area under the plot if true
79
82
  attr_accessor :area_fill
80
83
 
81
- # The constructor takes a hash reference, fields (the names for each
82
- # field on the X axis) MUST be set, all other values are defaulted to
83
- # those shown above - with the exception of style_sheet which defaults
84
- # to using the internal style sheet.
85
- def initialize config
86
- raise "fields was not supplied or is empty" unless config[:fields] &&
87
- config[:fields].kind_of?(Array) &&
88
- config[:fields].length > 0
89
- super
90
- end
84
+ # The constructor takes a hash reference, :fields (the names for each
85
+ # field on the X axis) MUST be set, all other values are defaulted to
86
+ # those shown above - with the exception of style_sheet which defaults
87
+ # to using the internal style sheet.
88
+ def initialize config
89
+ raise "fields was not supplied or is empty" unless config[:fields] &&
90
+ config[:fields].kind_of?(Array) &&
91
+ config[:fields].length > 0
92
+ super
93
+ end
91
94
 
92
95
  # In addition to the defaults set in Graph::initialize, sets
93
96
  # [show_data_points] true
94
97
  # [show_data_values] true
95
98
  # [stacked] false
96
99
  # [area_fill] false
97
- def set_defaults
100
+ def set_defaults
98
101
  init_with(
99
- :show_data_points => true,
100
- :show_data_values => true,
101
- :stacked => false,
102
- :area_fill => false
102
+ :show_data_points => true,
103
+ :show_data_values => true,
104
+ :stacked => false,
105
+ :area_fill => false
103
106
  )
104
-
105
-
106
- self.top_align = self.top_font = self.right_align = self.right_font = 1
107
- end
107
+ # self.top_align = self.top_font = self.right_align = self.right_font = 1
108
+ end
108
109
 
109
110
  protected
110
111
 
111
- def max_value
112
- max = 0
113
-
114
- if (stacked == true) then
115
- sums = Array.new(@config[:fields].length).fill(0)
116
-
117
- @data.each do |data|
118
- sums.each_index do |i|
119
- sums[i] += data[:data][i].to_f
120
- end
121
- end
122
-
123
- max = sums.max
124
- else
125
- max = @data.collect{|x| x[:data].max}.max
112
+ def max_value
113
+ max = 0
114
+ if stacked
115
+ sums = Array.new(@config[:fields].length).fill(0)
116
+
117
+ @data.each do |data|
118
+ sums.each_index do |i|
119
+ sums[i] += data[:data][i].to_f
126
120
  end
127
-
128
- return max
121
+ end
122
+ max = sums.max
123
+ else
124
+ # compact removes nil values when computing the max
125
+ max = @data.collect{ |x|
126
+ x[:data].compact.max
127
+ }.max
129
128
  end
129
+
130
+ return max
131
+ end
130
132
 
131
133
  def min_value
132
134
  min = 0
133
-
134
- if (min_scale_value.nil? == false) then
135
+ # compact removes nil values
136
+ if (!min_scale_value.nil?) then
135
137
  min = min_scale_value
136
138
  elsif (stacked == true) then
137
- min = @data[-1][:data].min
139
+ min = @data[-1][:data].compact.min
138
140
  else
139
- min = @data.collect{|x| x[:data].min}.min
141
+ min = @data.collect{|x| x[:data].compact.min}.min
140
142
  end
141
143
 
142
144
  return min
@@ -155,58 +157,75 @@ module SVG
155
157
  def get_y_labels
156
158
  maxvalue = max_value
157
159
  minvalue = min_value
160
+ #
158
161
  range = maxvalue - minvalue
159
- top_pad = range == 0 ? 10 : range / 20.0
162
+ if range == 0
163
+ top_pad = 10
164
+ else
165
+ top_pad = range / 20.0
166
+ end
160
167
  scale_range = (maxvalue + top_pad) - minvalue
161
168
 
162
- scale_division = scale_divisions || (scale_range / 10.0)
169
+ @y_scale_division = scale_divisions || (scale_range / 10.0)
163
170
 
164
171
  if scale_integers
165
- scale_division = scale_division < 1 ? 1 : scale_division.round
172
+ # only use integers if there will be at least 3 labels and division is > 0.5
173
+ if maxvalue/@y_scale_division >= 3 && @y_scale_division > 0.5
174
+ @y_scale_division = @y_scale_division.round
175
+ end
166
176
  end
167
177
 
168
178
  rv = []
169
- maxvalue = maxvalue%scale_division == 0 ?
170
- maxvalue : maxvalue + scale_division
171
- minvalue.step( maxvalue, scale_division ) {|v| rv << v}
179
+ # make sure we have at least one label higher than the max_value
180
+ if maxvalue%@y_scale_division != 0
181
+ maxvalue = maxvalue + @y_scale_division
182
+ end
183
+ minvalue.step( maxvalue, @y_scale_division ) {|v| rv << v}
172
184
  return rv
173
185
  end
174
186
 
175
187
  def calc_coords(field, value, width = field_width, height = field_height)
176
188
  coords = {:x => 0, :y => 0}
177
189
  coords[:x] = width * field
178
- coords[:y] = @graph_height - value * height
190
+ coords[:y] = @graph_height - value/@y_scale_division * height
179
191
 
180
192
  return coords
181
193
  end
182
194
 
183
195
  def draw_data
184
196
  minvalue = min_value
185
- fieldheight = (@graph_height.to_f - font_size*2*top_font) /
186
- (get_y_labels.max - get_y_labels.min)
197
+ #fieldheight = (@graph_height.to_f - font_size*2*top_font) /
198
+ # (get_y_labels.max - get_y_labels.min)
199
+ fieldheight = field_height
187
200
  fieldwidth = field_width
188
201
  line = @data.length
189
202
 
190
203
  prev_sum = Array.new(@config[:fields].length).fill(0)
191
- cum_sum = Array.new(@config[:fields].length).fill(-minvalue)
204
+ cum_sum = Array.new(@config[:fields].length).fill(nil)
192
205
 
193
206
  for data in @data.reverse
194
207
  lpath = ""
195
208
  apath = ""
196
-
197
- if not stacked then cum_sum.fill(-minvalue) end
198
209
 
199
- data[:data].each_index do |i|
200
- cum_sum[i] += data[:data][i]
201
-
210
+ # reset cum_sum if we are not in a stacked graph
211
+ if not stacked then cum_sum.fill(nil) end
212
+
213
+ # only consider as many datapoints as we have fields
214
+ @config[:fields].each_index do |i|
215
+ next if data[:data][i].nil?
216
+ if cum_sum[i].nil? #first time init
217
+ cum_sum[i] = data[:data][i] - minvalue
218
+ else # in case of stacked
219
+ cum_sum[i] += data[:data][i]
220
+ end
202
221
  c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
203
-
204
222
  lpath << "#{c[:x]} #{c[:y]} "
205
223
  end
206
224
 
207
225
  if area_fill
208
226
  if stacked then
209
227
  (prev_sum.length - 1).downto 0 do |i|
228
+ next if prev_sum[i].nil?
210
229
  c = calc_coords(i, prev_sum[i], fieldwidth, fieldheight)
211
230
 
212
231
  apath << "#{c[:x]} #{c[:y]} "
@@ -229,21 +248,27 @@ module SVG
229
248
  "class" => "line#{line}"
230
249
  })
231
250
 
232
- if show_data_points || show_data_values
251
+ if show_data_points || show_data_values || add_popups
233
252
  cum_sum.each_index do |i|
253
+ # skip datapoint if nil
254
+ next if cum_sum[i].nil?
255
+ c = calc_coords(i, cum_sum[i], fieldwidth, fieldheight)
234
256
  if show_data_points
235
257
  @graph.add_element( "circle", {
236
- "cx" => (fieldwidth * i).to_s,
237
- "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
258
+ # "cx" => (fieldwidth * i).to_s,
259
+ # "cy" => (@graph_height - cum_sum[i] * fieldheight).to_s,
260
+ "cx" => c[:x].to_s,
261
+ "cy" => c[:y].to_s,
238
262
  "r" => "2.5",
239
263
  "class" => "dataPoint#{line}"
240
264
  })
241
265
  end
242
- make_datapoint_text(
243
- fieldwidth * i,
244
- @graph_height - cum_sum[i] * fieldheight - 6,
245
- cum_sum[i] + minvalue
246
- )
266
+ #x = fieldwidth * i
267
+ #y = @graph_height - cum_sum[i] * fieldheight
268
+ #make_datapoint_text( x, y - font_size/2, cum_sum[i] + minvalue)
269
+ #add_popup(x, y, cum_sum[i] + minvalue)
270
+ make_datapoint_text( c[:x], c[:y] - font_size/2, cum_sum[i] + minvalue)
271
+ add_popup(c[:x], c[:y], cum_sum[i] + minvalue)
247
272
  end
248
273
  end
249
274
 
data/lib/SVG/Graph/Pie.rb CHANGED
@@ -108,10 +108,17 @@ module SVG
108
108
  # is the same as:
109
109
  #
110
110
  # graph.add_data( { :data => [3,5,8,13] } )
111
+ #
112
+ # nil values in the array will be replaced by 0
113
+ #
114
+ # graph.add_data( { :data => [3,nil,nil,2] } ) is equivalent to graph.add_data( { :data => [3,0,0,2] } )
115
+ #
111
116
  def add_data arg
112
117
  arg[:data].each_index {|idx|
113
118
  @data[idx] = 0 unless @data[idx]
114
- @data[idx] += arg[:data][idx]
119
+ if !arg[:data][idx].nil?
120
+ @data[idx] += arg[:data][idx]
121
+ end
115
122
  }
116
123
  end
117
124
 
@@ -160,10 +167,12 @@ module SVG
160
167
  def draw_graph
161
168
  end
162
169
 
170
+ # We don't have axis labels
163
171
  def get_y_labels
164
172
  [""]
165
173
  end
166
174
 
175
+ # We don't have axis labels
167
176
  def get_x_labels
168
177
  [""]
169
178
  end
@@ -195,7 +204,8 @@ module SVG
195
204
  diameter -= 10 if show_shadow
196
205
  radius = diameter / 2.0
197
206
 
198
- xoff = (width - diameter) / 2
207
+ #xoff = (width - diameter) / 2
208
+ xoff = (@graph_width - diameter) / 2
199
209
  yoff = (height - @border_bottom - diameter)
200
210
  yoff -= 10 if show_shadow
201
211
  @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
@@ -102,7 +102,7 @@ module SVG
102
102
  :show_lines => true,
103
103
  :round_popups => true
104
104
  )
105
- self.top_align = self.right_align = self.top_font = self.right_font = 1
105
+ # self.top_align = self.right_align = self.top_font = self.right_font = 1
106
106
  end
107
107
 
108
108
  # Determines the scaling for the X axis divisions.
@@ -143,32 +143,41 @@ module SVG
143
143
 
144
144
 
145
145
  # Adds data to the plot. The data must be in X,Y pairs; EG
146
- # [ 1, 2 ] # A data set with 1 point: (1,2)
147
- # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
148
- def add_data(data)
149
-
150
- @data = [] unless @data
151
-
152
- raise "No data provided by #{conf.inspect}" unless data[:data] and
153
- data[:data].kind_of? Array
146
+ # data_set1 = [ 1, 2 ] # A data set with 1 point: (1,2)
147
+ # data_set2 = [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
148
+ #
149
+ # graph.add_data({
150
+ # :data => data_set1,
151
+ # :title => 'single point'
152
+ # })
153
+ # graph.add_data({
154
+ # :data => data_set2,
155
+ # :title => 'two points'
156
+ # })
157
+ def add_data(conf)
158
+ @data ||= []
159
+ # remove nil values
160
+ conf[:data] = conf[:data].compact
161
+ raise "No data provided by #{conf.inspect}" unless conf[:data] and
162
+ conf[:data].kind_of? Array
154
163
  raise "Data supplied must be x,y pairs! "+
155
164
  "The data provided contained an odd set of "+
156
- "data points" unless data[:data].length % 2 == 0
157
- return if data[:data].length == 0
165
+ "data points" unless conf[:data].length % 2 == 0
166
+ return if conf[:data].length == 0
158
167
 
159
- data[:description] ||= Array.new(data[:data].size/2)
160
- if data[:description].size != data[:data].size/2
161
- raise "Description for popups does not have same size as provided data: #{data[:description].size} vs #{data[:data].size/2}"
168
+ conf[:description] ||= Array.new(conf[:data].size/2)
169
+ if conf[:description].size != conf[:data].size/2
170
+ raise "Description for popups does not have same size as provided data: #{conf[:description].size} vs #{conf[:data].size/2}"
162
171
  end
163
172
 
164
173
  x = []
165
174
  y = []
166
- data[:data].each_index {|i|
167
- (i%2 == 0 ? x : y) << data[:data][i]
175
+ conf[:data].each_index {|i|
176
+ (i%2 == 0 ? x : y) << conf[:data][i]
168
177
  }
169
- sort( x, y, data[:description] )
170
- data[:data] = [x,y]
171
- @data << data
178
+ sort( x, y, conf[:description] )
179
+ conf[:data] = [x,y]
180
+ @data << conf
172
181
  end
173
182
 
174
183
  protected
@@ -231,11 +240,13 @@ module SVG
231
240
  alias :get_x_labels :get_x_values
232
241
 
233
242
  def field_width
243
+ # exclude values which are outside max_x_range
234
244
  values = get_x_values
235
245
  max = max_x_range
236
246
  dx = (max - values[-1]).to_f / (values[-1] - values[-2])
237
- (@graph_width.to_f - font_size*2*right_font) /
238
- (values.length + dx - right_align)
247
+ #(@graph_width.to_f - font_size*2*right_font) /
248
+ # (values.length + dx - right_align)
249
+ @graph_width.to_f / values.length
239
250
  end
240
251
 
241
252
 
@@ -283,6 +294,7 @@ module SVG
283
294
  alias :get_y_labels :get_y_values
284
295
 
285
296
  def field_height
297
+ # exclude values which are outside max_x_range
286
298
  values = get_y_values
287
299
  max = max_y_range
288
300
  if values.length == 1
@@ -290,8 +302,9 @@ module SVG
290
302
  else
291
303
  dx = (max - values[-1]).to_f / (values[-1] - values[-2])
292
304
  end
293
- (@graph_height.to_f - font_size*2*top_font) /
294
- (values.length + dx - top_align)
305
+ #(@graph_height.to_f - font_size*2*top_font) /
306
+ # (values.length + dx - top_align)
307
+ @graph_height.to_f / values.length
295
308
  end
296
309
 
297
310
  def draw_data
@@ -330,7 +343,7 @@ module SVG
330
343
  })
331
344
  end
332
345
 
333
- if show_data_points || show_data_values
346
+ if show_data_points || show_data_values || add_popups
334
347
  x_points.each_index { |idx|
335
348
  x = (x_points[idx] - x_min) * x_step
336
349
  y = @graph_height - (y_points[idx] - y_min) * y_step
@@ -338,9 +351,9 @@ module SVG
338
351
  DataPoint.new(x, y, line).shape(data[:description][idx]).each{|s|
339
352
  @graph.add_element( *s )
340
353
  }
341
- add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx])) if add_popups
342
354
  end
343
- make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
355
+ make_datapoint_text( x, y-6, y_points[idx] )
356
+ add_popup(x, y, format( x_points[idx], y_points[idx], data[:description][idx]))
344
357
  }
345
358
  end
346
359
  line += 1
data/lib/svggraph.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module SVG
2
2
  module Graph
3
- VERSION = '2.0.1'
3
+ VERSION = '2.0.2.beta'
4
4
  end
5
5
  end
6
6
  require_relative 'SVG/Graph/DataPoint'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svg-graph
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Russell
@@ -54,7 +54,7 @@ files:
54
54
  - test/test_svg_graph.rb
55
55
  homepage: http://www.germane-software.com/software/SVG/SVG::Graph/
56
56
  licenses:
57
- - GPL
57
+ - GPL-2.0
58
58
  metadata: {}
59
59
  post_install_message:
60
60
  rdoc_options: []
@@ -67,12 +67,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
67
  version: '0'
68
68
  required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  requirements:
70
- - - ">="
70
+ - - ">"
71
71
  - !ruby/object:Gem::Version
72
- version: '0'
72
+ version: 1.3.1
73
73
  requirements: []
74
74
  rubyforge_project:
75
- rubygems_version: 2.4.8
75
+ rubygems_version: 2.6.7
76
76
  signing_key:
77
77
  specification_version: 4
78
78
  summary: SVG:::Graph is a pure Ruby library for generating charts, which are a type