svg-graph 2.0.1 → 2.0.2.beta

Sign up to get free protection for your applications and to get access to all the features.
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