You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

13 years ago
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
​
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. require 'SVG/Graph/Graph'
  2. module SVG
  3. module Graph
  4. # === For creating SVG plots of scalar data
  5. #
  6. # = Synopsis
  7. #
  8. # require 'SVG/Graph/Plot'
  9. #
  10. # # Data sets are x,y pairs
  11. # # Note that multiple data sets can differ in length, and that the
  12. # # data in the datasets needn't be in order; they will be ordered
  13. # # by the plot along the X-axis.
  14. # projection = [
  15. # 6, 11, 0, 5, 18, 7, 1, 11, 13, 9, 1, 2, 19, 0, 3, 13,
  16. # 7, 9
  17. # ]
  18. # actual = [
  19. # 0, 18, 8, 15, 9, 4, 18, 14, 10, 2, 11, 6, 14, 12,
  20. # 15, 6, 4, 17, 2, 12
  21. # ]
  22. #
  23. # graph = SVG::Graph::Plot.new({
  24. # :height => 500,
  25. # :width => 300,
  26. # :key => true,
  27. # :scale_x_integers => true,
  28. # :scale_y_integerrs => true,
  29. # })
  30. #
  31. # graph.add_data({
  32. # :data => projection
  33. # :title => 'Projected',
  34. # })
  35. #
  36. # graph.add_data({
  37. # :data => actual,
  38. # :title => 'Actual',
  39. # })
  40. #
  41. # print graph.burn()
  42. #
  43. # = Description
  44. #
  45. # Produces a graph of scalar data.
  46. #
  47. # This object aims to allow you to easily create high quality
  48. # SVG[http://www.w3c.org/tr/svg] scalar plots. You can either use the
  49. # default style sheet or supply your own. Either way there are many options
  50. # which can be configured to give you control over how the graph is
  51. # generated - with or without a key, data elements at each point, title,
  52. # subtitle etc.
  53. #
  54. # = Examples
  55. #
  56. # http://www.germane-software/repositories/public/SVG/test/plot.rb
  57. #
  58. # = Notes
  59. #
  60. # The default stylesheet handles upto 10 data sets, if you
  61. # use more you must create your own stylesheet and add the
  62. # additional settings for the extra data sets. You will know
  63. # if you go over 10 data sets as they will have no style and
  64. # be in black.
  65. #
  66. # Unlike the other types of charts, data sets must contain x,y pairs:
  67. #
  68. # [ 1, 2 ] # A data set with 1 point: (1,2)
  69. # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
  70. #
  71. # = See also
  72. #
  73. # * SVG::Graph::Graph
  74. # * SVG::Graph::BarHorizontal
  75. # * SVG::Graph::Bar
  76. # * SVG::Graph::Line
  77. # * SVG::Graph::Pie
  78. # * SVG::Graph::TimeSeries
  79. #
  80. # == Author
  81. #
  82. # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
  83. #
  84. # Copyright 2004 Sean E. Russell
  85. # This software is available under the Ruby license[LICENSE.txt]
  86. #
  87. class Plot < Graph
  88. # In addition to the defaults set by Graph::initialize, sets
  89. # [show_data_values] true
  90. # [show_data_points] true
  91. # [area_fill] false
  92. # [stacked] false
  93. def set_defaults
  94. init_with(
  95. :show_data_values => true,
  96. :show_data_points => true,
  97. :area_fill => false,
  98. :stacked => false
  99. )
  100. self.top_align = self.right_align = self.top_font = self.right_font = 1
  101. end
  102. # Determines the scaling for the X axis divisions.
  103. #
  104. # graph.scale_x_divisions = 2
  105. #
  106. # would cause the graph to attempt to generate labels stepped by 2; EG:
  107. # 0,2,4,6,8...
  108. attr_accessor :scale_x_divisions
  109. # Determines the scaling for the Y axis divisions.
  110. #
  111. # graph.scale_y_divisions = 0.5
  112. #
  113. # would cause the graph to attempt to generate labels stepped by 0.5; EG:
  114. # 0, 0.5, 1, 1.5, 2, ...
  115. attr_accessor :scale_y_divisions
  116. # Make the X axis labels integers
  117. attr_accessor :scale_x_integers
  118. # Make the Y axis labels integers
  119. attr_accessor :scale_y_integers
  120. # Fill the area under the line
  121. attr_accessor :area_fill
  122. # Show a small circle on the graph where the line
  123. # goes from one point to the next.
  124. attr_accessor :show_data_points
  125. # Set the minimum value of the X axis
  126. attr_accessor :min_x_value
  127. # Set the minimum value of the Y axis
  128. attr_accessor :min_y_value
  129. # Adds data to the plot. The data must be in X,Y pairs; EG
  130. # [ 1, 2 ] # A data set with 1 point: (1,2)
  131. # [ 1,2, 5,6] # A data set with 2 points: (1,2) and (5,6)
  132. def add_data data
  133. @data = [] unless @data
  134. raise "No data provided by #{conf.inspect}" unless data[:data] and
  135. data[:data].kind_of? Array
  136. raise "Data supplied must be x,y pairs! "+
  137. "The data provided contained an odd set of "+
  138. "data points" unless data[:data].length % 2 == 0
  139. return if data[:data].length == 0
  140. x = []
  141. y = []
  142. data[:data].each_index {|i|
  143. (i%2 == 0 ? x : y) << data[:data][i]
  144. }
  145. sort( x, y )
  146. data[:data] = [x,y]
  147. @data << data
  148. end
  149. protected
  150. def keys
  151. @data.collect{ |x| x[:title] }
  152. end
  153. def calculate_left_margin
  154. super
  155. label_left = get_x_labels[0].to_s.length / 2 * font_size * 0.6
  156. @border_left = label_left if label_left > @border_left
  157. end
  158. def calculate_right_margin
  159. super
  160. label_right = get_x_labels[-1].to_s.length / 2 * font_size * 0.6
  161. @border_right = label_right if label_right > @border_right
  162. end
  163. X = 0
  164. Y = 1
  165. def x_range
  166. max_value = @data.collect{|x| x[:data][X][-1] }.max
  167. min_value = @data.collect{|x| x[:data][X][0] }.min
  168. min_value = min_value<min_x_value ? min_value : min_x_value if min_x_value
  169. range = max_value - min_value
  170. right_pad = range == 0 ? 10 : range / 20.0
  171. scale_range = (max_value + right_pad) - min_value
  172. scale_division = scale_x_divisions || (scale_range / 10.0)
  173. if scale_x_integers
  174. scale_division = scale_division < 1 ? 1 : scale_division.round
  175. end
  176. [min_value, max_value, scale_division]
  177. end
  178. def get_x_values
  179. min_value, max_value, scale_division = x_range
  180. rv = []
  181. min_value.step( max_value, scale_division ) {|v| rv << v}
  182. return rv
  183. end
  184. alias :get_x_labels :get_x_values
  185. def field_width
  186. values = get_x_values
  187. max = @data.collect{|x| x[:data][X][-1]}.max
  188. dx = (max - values[-1]).to_f / (values[-1] - values[-2])
  189. (@graph_width.to_f - font_size*2*right_font) /
  190. (values.length + dx - right_align)
  191. end
  192. def y_range
  193. max_value = @data.collect{|x| x[:data][Y].max }.max
  194. min_value = @data.collect{|x| x[:data][Y].min }.min
  195. min_value = min_value<min_y_value ? min_value : min_y_value if min_y_value
  196. range = max_value - min_value
  197. top_pad = range == 0 ? 10 : range / 20.0
  198. scale_range = (max_value + top_pad) - min_value
  199. scale_division = scale_y_divisions || (scale_range / 10.0)
  200. if scale_y_integers
  201. scale_division = scale_division < 1 ? 1 : scale_division.round
  202. end
  203. return [min_value, max_value, scale_division]
  204. end
  205. def get_y_values
  206. min_value, max_value, scale_division = y_range
  207. rv = []
  208. min_value.step( max_value, scale_division ) {|v| rv << v}
  209. return rv
  210. end
  211. alias :get_y_labels :get_y_values
  212. def field_height
  213. values = get_y_values
  214. max = @data.collect{|x| x[:data][Y].max }.max
  215. if values.length == 1
  216. dx = values[-1]
  217. else
  218. dx = (max - values[-1]).to_f / (values[-1] - values[-2])
  219. end
  220. (@graph_height.to_f - font_size*2*top_font) /
  221. (values.length + dx - top_align)
  222. end
  223. def draw_data
  224. line = 1
  225. x_min, x_max, x_div = x_range
  226. y_min, y_max, y_div = y_range
  227. x_step = (@graph_width.to_f - font_size*2) / (x_max-x_min)
  228. y_step = (@graph_height.to_f - font_size*2) / (y_max-y_min)
  229. for data in @data
  230. x_points = data[:data][X]
  231. y_points = data[:data][Y]
  232. lpath = "L"
  233. x_start = 0
  234. y_start = 0
  235. x_points.each_index { |idx|
  236. x = (x_points[idx] - x_min) * x_step
  237. y = @graph_height - (y_points[idx] - y_min) * y_step
  238. x_start, y_start = x,y if idx == 0
  239. lpath << "#{x} #{y} "
  240. }
  241. if area_fill
  242. @graph.add_element( "path", {
  243. "d" => "M#{x_start} #@graph_height #{lpath} V#@graph_height Z",
  244. "class" => "fill#{line}"
  245. })
  246. end
  247. @graph.add_element( "path", {
  248. "d" => "M#{x_start} #{y_start} #{lpath}",
  249. "class" => "line#{line}"
  250. })
  251. if show_data_points || show_data_values
  252. x_points.each_index { |idx|
  253. x = (x_points[idx] - x_min) * x_step
  254. y = @graph_height - (y_points[idx] - y_min) * y_step
  255. if show_data_points
  256. @graph.add_element( "circle", {
  257. "cx" => x.to_s,
  258. "cy" => y.to_s,
  259. "r" => "2.5",
  260. "class" => "dataPoint#{line}"
  261. })
  262. add_popup(x, y, format( x_points[idx], y_points[idx] )) if add_popups
  263. end
  264. make_datapoint_text( x, y-6, y_points[idx] ) if show_data_values
  265. }
  266. end
  267. line += 1
  268. end
  269. end
  270. def format x, y
  271. "(#{(x * 100).to_i / 100}, #{(y * 100).to_i / 100})"
  272. end
  273. def get_css
  274. return <<EOL
  275. /* default line styles */
  276. .line1{
  277. fill: none;
  278. stroke: #ff0000;
  279. stroke-width: 1px;
  280. }
  281. .line2{
  282. fill: none;
  283. stroke: #0000ff;
  284. stroke-width: 1px;
  285. }
  286. .line3{
  287. fill: none;
  288. stroke: #00ff00;
  289. stroke-width: 1px;
  290. }
  291. .line4{
  292. fill: none;
  293. stroke: #ffcc00;
  294. stroke-width: 1px;
  295. }
  296. .line5{
  297. fill: none;
  298. stroke: #00ccff;
  299. stroke-width: 1px;
  300. }
  301. .line6{
  302. fill: none;
  303. stroke: #ff00ff;
  304. stroke-width: 1px;
  305. }
  306. .line7{
  307. fill: none;
  308. stroke: #00ffff;
  309. stroke-width: 1px;
  310. }
  311. .line8{
  312. fill: none;
  313. stroke: #ffff00;
  314. stroke-width: 1px;
  315. }
  316. .line9{
  317. fill: none;
  318. stroke: #ccc6666;
  319. stroke-width: 1px;
  320. }
  321. .line10{
  322. fill: none;
  323. stroke: #663399;
  324. stroke-width: 1px;
  325. }
  326. .line11{
  327. fill: none;
  328. stroke: #339900;
  329. stroke-width: 1px;
  330. }
  331. .line12{
  332. fill: none;
  333. stroke: #9966FF;
  334. stroke-width: 1px;
  335. }
  336. /* default fill styles */
  337. .fill1{
  338. fill: #cc0000;
  339. fill-opacity: 0.2;
  340. stroke: none;
  341. }
  342. .fill2{
  343. fill: #0000cc;
  344. fill-opacity: 0.2;
  345. stroke: none;
  346. }
  347. .fill3{
  348. fill: #00cc00;
  349. fill-opacity: 0.2;
  350. stroke: none;
  351. }
  352. .fill4{
  353. fill: #ffcc00;
  354. fill-opacity: 0.2;
  355. stroke: none;
  356. }
  357. .fill5{
  358. fill: #00ccff;
  359. fill-opacity: 0.2;
  360. stroke: none;
  361. }
  362. .fill6{
  363. fill: #ff00ff;
  364. fill-opacity: 0.2;
  365. stroke: none;
  366. }
  367. .fill7{
  368. fill: #00ffff;
  369. fill-opacity: 0.2;
  370. stroke: none;
  371. }
  372. .fill8{
  373. fill: #ffff00;
  374. fill-opacity: 0.2;
  375. stroke: none;
  376. }
  377. .fill9{
  378. fill: #cc6666;
  379. fill-opacity: 0.2;
  380. stroke: none;
  381. }
  382. .fill10{
  383. fill: #663399;
  384. fill-opacity: 0.2;
  385. stroke: none;
  386. }
  387. .fill11{
  388. fill: #339900;
  389. fill-opacity: 0.2;
  390. stroke: none;
  391. }
  392. .fill12{
  393. fill: #9966FF;
  394. fill-opacity: 0.2;
  395. stroke: none;
  396. }
  397. /* default line styles */
  398. .key1,.dataPoint1{
  399. fill: #ff0000;
  400. stroke: none;
  401. stroke-width: 1px;
  402. }
  403. .key2,.dataPoint2{
  404. fill: #0000ff;
  405. stroke: none;
  406. stroke-width: 1px;
  407. }
  408. .key3,.dataPoint3{
  409. fill: #00ff00;
  410. stroke: none;
  411. stroke-width: 1px;
  412. }
  413. .key4,.dataPoint4{
  414. fill: #ffcc00;
  415. stroke: none;
  416. stroke-width: 1px;
  417. }
  418. .key5,.dataPoint5{
  419. fill: #00ccff;
  420. stroke: none;
  421. stroke-width: 1px;
  422. }
  423. .key6,.dataPoint6{
  424. fill: #ff00ff;
  425. stroke: none;
  426. stroke-width: 1px;
  427. }
  428. .key7,.dataPoint7{
  429. fill: #00ffff;
  430. stroke: none;
  431. stroke-width: 1px;
  432. }
  433. .key8,.dataPoint8{
  434. fill: #ffff00;
  435. stroke: none;
  436. stroke-width: 1px;
  437. }
  438. .key9,.dataPoint9{
  439. fill: #cc6666;
  440. stroke: none;
  441. stroke-width: 1px;
  442. }
  443. .key10,.dataPoint10{
  444. fill: #663399;
  445. stroke: none;
  446. stroke-width: 1px;
  447. }
  448. .key11,.dataPoint11{
  449. fill: #339900;
  450. stroke: none;
  451. stroke-width: 1px;
  452. }
  453. .key12,.dataPoint12{
  454. fill: #9966FF;
  455. stroke: none;
  456. stroke-width: 1px;
  457. }
  458. EOL
  459. end
  460. end
  461. end
  462. end

软件技术创新是当今国家整体创新能力的重要基础和发展引擎。近年来,大众参与的软件创新与创业活动已经成为网络时代软件开发和应用的新形态,正在快速改变着全球软件创新模式和软件产业格局。 ...