taskjuggler 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. data/CHANGELOG +44 -0
  2. data/bin/tj3webd +4 -0
  3. data/data/css/tjreport.css +14 -5
  4. data/data/tjp.vim +22 -7
  5. data/examples/Fedora-20/reports.tji +2 -4
  6. data/examples/Scrum/Product Burndown.csv +26 -0
  7. data/examples/Scrum/Sprint 1 Burndown.csv +26 -0
  8. data/examples/Scrum/Sprint 2 Burndown.csv +26 -0
  9. data/examples/Scrum/Sprint 3 Burndown.csv +26 -0
  10. data/examples/Scrum/scrum.tjp +141 -0
  11. data/examples/Tutorial/tutorial.tjp +13 -7
  12. data/lib/taskjuggler/Attributes.rb +2 -3
  13. data/lib/taskjuggler/HTMLDocument.rb +25 -18
  14. data/lib/taskjuggler/Journal.rb +85 -65
  15. data/lib/taskjuggler/KeywordDocumentation.rb +25 -13
  16. data/lib/taskjuggler/LeaveList.rb +1 -1
  17. data/lib/taskjuggler/Limits.rb +3 -5
  18. data/lib/taskjuggler/MessageHandler.rb +173 -19
  19. data/lib/taskjuggler/Painter.rb +75 -0
  20. data/lib/taskjuggler/Painter/BasicShapes.rb +76 -0
  21. data/lib/taskjuggler/Painter/Color.rb +273 -0
  22. data/lib/taskjuggler/Painter/Element.rb +56 -0
  23. data/lib/taskjuggler/Painter/FontData.rb +221 -0
  24. data/lib/taskjuggler/Painter/FontMetrics.rb +125 -0
  25. data/lib/taskjuggler/Painter/FontMetricsData.rb +151 -0
  26. data/lib/taskjuggler/Painter/Group.rb +77 -0
  27. data/lib/taskjuggler/Painter/Points.rb +47 -0
  28. data/lib/taskjuggler/Painter/Primitives.rb +100 -0
  29. data/lib/taskjuggler/Painter/SVGSupport.rb +36 -0
  30. data/lib/taskjuggler/Painter/Text.rb +36 -0
  31. data/lib/taskjuggler/Project.rb +46 -29
  32. data/lib/taskjuggler/ProjectFileParser.rb +24 -22
  33. data/lib/taskjuggler/ProjectFileScanner.rb +2 -2
  34. data/lib/taskjuggler/PropertyTreeNode.rb +26 -34
  35. data/lib/taskjuggler/Query.rb +8 -5
  36. data/lib/taskjuggler/RealFormat.rb +3 -0
  37. data/lib/taskjuggler/Resource.rb +3 -5
  38. data/lib/taskjuggler/ResourceScenario.rb +19 -7
  39. data/lib/taskjuggler/RichText.rb +4 -6
  40. data/lib/taskjuggler/RichText/FunctionExample.rb +1 -1
  41. data/lib/taskjuggler/RichText/FunctionHandler.rb +5 -6
  42. data/lib/taskjuggler/RichText/Parser.rb +4 -6
  43. data/lib/taskjuggler/RichText/RTFNavigator.rb +1 -1
  44. data/lib/taskjuggler/RichText/RTFQuery.rb +2 -3
  45. data/lib/taskjuggler/RichText/RTFReport.rb +1 -1
  46. data/lib/taskjuggler/RichText/RTFReportLink.rb +1 -2
  47. data/lib/taskjuggler/RichText/RTFWithQuerySupport.rb +1 -1
  48. data/lib/taskjuggler/RichText/Scanner.rb +6 -6
  49. data/lib/taskjuggler/RichText/Snip.rb +1 -2
  50. data/lib/taskjuggler/RuntimeConfig.rb +9 -6
  51. data/lib/taskjuggler/ScenarioData.rb +4 -3
  52. data/lib/taskjuggler/Scoreboard.rb +6 -0
  53. data/lib/taskjuggler/SheetHandlerBase.rb +25 -8
  54. data/lib/taskjuggler/SimpleQueryExpander.rb +14 -5
  55. data/lib/taskjuggler/SyntaxReference.rb +1 -2
  56. data/lib/taskjuggler/TableColumnSorter.rb +84 -0
  57. data/lib/taskjuggler/Task.rb +3 -5
  58. data/lib/taskjuggler/TaskJuggler.rb +36 -29
  59. data/lib/taskjuggler/TaskScenario.rb +154 -66
  60. data/lib/taskjuggler/TextParser.rb +24 -17
  61. data/lib/taskjuggler/TextParser/Scanner.rb +16 -11
  62. data/lib/taskjuggler/TextParser/SourceFileInfo.rb +20 -15
  63. data/lib/taskjuggler/TimeSheets.rb +6 -12
  64. data/lib/taskjuggler/Tj3AppBase.rb +35 -16
  65. data/lib/taskjuggler/Tj3Config.rb +1 -1
  66. data/lib/taskjuggler/TjpSyntaxRules.rb +239 -49
  67. data/lib/taskjuggler/XMLElement.rb +9 -2
  68. data/lib/taskjuggler/apps/Tj3.rb +43 -37
  69. data/lib/taskjuggler/apps/Tj3Client.rb +62 -112
  70. data/lib/taskjuggler/apps/Tj3Daemon.rb +66 -29
  71. data/lib/taskjuggler/apps/Tj3Man.rb +5 -5
  72. data/lib/taskjuggler/apps/Tj3SsReceiver.rb +10 -13
  73. data/lib/taskjuggler/apps/Tj3SsSender.rb +13 -16
  74. data/lib/taskjuggler/apps/Tj3TsReceiver.rb +10 -13
  75. data/lib/taskjuggler/apps/Tj3TsSender.rb +12 -15
  76. data/lib/taskjuggler/apps/Tj3TsSummary.rb +12 -15
  77. data/lib/taskjuggler/apps/Tj3WebD.rb +99 -0
  78. data/lib/taskjuggler/daemon/Daemon.rb +50 -10
  79. data/lib/taskjuggler/daemon/DaemonConnector.rb +127 -0
  80. data/lib/taskjuggler/daemon/ProcessIntercom.rb +36 -21
  81. data/lib/taskjuggler/daemon/ProjectBroker.rb +122 -112
  82. data/lib/taskjuggler/daemon/ProjectServer.rb +78 -46
  83. data/lib/taskjuggler/daemon/ReportServer.rb +52 -28
  84. data/lib/taskjuggler/daemon/ReportServlet.rb +92 -21
  85. data/lib/taskjuggler/daemon/WebServer.rb +75 -22
  86. data/lib/taskjuggler/daemon/WelcomePage.rb +1 -0
  87. data/lib/taskjuggler/reports/AccountListRE.rb +3 -3
  88. data/lib/taskjuggler/reports/CSVFile.rb +9 -2
  89. data/lib/taskjuggler/reports/ChartPlotter.rb +453 -0
  90. data/lib/taskjuggler/reports/Navigator.rb +1 -0
  91. data/lib/taskjuggler/reports/NikuReport.rb +4 -4
  92. data/lib/taskjuggler/reports/Report.rb +6 -18
  93. data/lib/taskjuggler/reports/ReportBase.rb +9 -9
  94. data/lib/taskjuggler/reports/ReportContext.rb +2 -2
  95. data/lib/taskjuggler/reports/StatusSheetReport.rb +6 -6
  96. data/lib/taskjuggler/reports/TableReport.rb +24 -15
  97. data/lib/taskjuggler/reports/TimeSheetReport.rb +5 -5
  98. data/lib/taskjuggler/reports/TraceReport.rb +251 -0
  99. data/lib/tj3webd.rb +17 -0
  100. data/manual/Day_To_Day_Juggling +10 -3
  101. data/manual/Installation +38 -19
  102. data/manual/Software +25 -19
  103. data/manual/Tutorial +119 -110
  104. data/manual/html/Day_To_Day_Juggling.html +7 -5
  105. data/manual/html/Getting_Started.html +4 -4
  106. data/manual/html/How_To_Contribute.html +4 -4
  107. data/manual/html/Installation.html +19 -11
  108. data/manual/html/Intro.html +4 -4
  109. data/manual/html/Reporting_Bugs.html +4 -4
  110. data/manual/html/Rich_Text_Attributes.html +4 -4
  111. data/manual/html/Software.html +15 -11
  112. data/manual/html/TaskJuggler_2x_Migration.html +4 -4
  113. data/manual/html/TaskJuggler_Internals.html +4 -4
  114. data/manual/html/The_TaskJuggler_Syntax.html +4 -4
  115. data/manual/html/Tutorial.html +41 -32
  116. data/manual/html/account.html +4 -4
  117. data/manual/html/account.task.html +4 -4
  118. data/manual/html/accountprefix.html +4 -4
  119. data/manual/html/accountreport.html +27 -9
  120. data/manual/html/accountroot.html +5 -5
  121. data/manual/html/active.html +4 -4
  122. data/manual/html/adopt.task.html +4 -4
  123. data/manual/html/aggregate.html +4 -4
  124. data/manual/html/alert.html +4 -4
  125. data/manual/html/alertlevels.html +4 -4
  126. data/manual/html/allocate.html +5 -5
  127. data/manual/html/alphabet.html +4 -4
  128. data/manual/html/alternative.html +4 -4
  129. data/manual/html/author.html +4 -4
  130. data/manual/html/balance.html +5 -5
  131. data/manual/html/booking.resource.html +4 -4
  132. data/manual/html/booking.task.html +4 -4
  133. data/manual/html/caption.html +5 -5
  134. data/manual/html/cellcolor.column.html +43 -8
  135. data/manual/html/celltext.column.html +4 -4
  136. data/manual/html/center.html +5 -5
  137. data/manual/html/charge.html +4 -4
  138. data/manual/html/chargeset.html +4 -4
  139. data/manual/html/columnid.html +27 -15
  140. data/manual/html/columns.html +5 -5
  141. data/manual/html/complete.html +4 -4
  142. data/manual/html/copyright.html +4 -4
  143. data/manual/html/credits.html +4 -4
  144. data/manual/html/css/tjreport.css +14 -5
  145. data/manual/html/currency.html +4 -4
  146. data/manual/html/currencyformat.html +5 -5
  147. data/manual/html/dailymax.html +5 -5
  148. data/manual/html/dailymin.html +5 -5
  149. data/manual/html/dailyworkinghours.html +4 -4
  150. data/manual/html/date.extend.html +4 -4
  151. data/manual/html/date.html +5 -5
  152. data/manual/html/definitions.html +4 -4
  153. data/manual/html/depends.html +4 -4
  154. data/manual/html/details.html +4 -4
  155. data/manual/html/disabled.html +4 -4
  156. data/manual/html/duration.html +4 -4
  157. data/manual/html/efficiency.html +4 -4
  158. data/manual/html/effort.html +4 -4
  159. data/manual/html/email.html +4 -4
  160. data/manual/html/enabled.html +4 -4
  161. data/manual/html/end.column.html +4 -4
  162. data/manual/html/end.html +4 -4
  163. data/manual/html/end.limit.html +4 -4
  164. data/manual/html/end.report.html +5 -5
  165. data/manual/html/end.timesheet.html +4 -4
  166. data/manual/html/endcredit.html +4 -4
  167. data/manual/html/epilog.html +5 -5
  168. data/manual/html/export.html +4 -4
  169. data/manual/html/extend.html +4 -4
  170. data/manual/html/fail.html +43 -8
  171. data/manual/html/fdl.html +4 -4
  172. data/manual/html/flags.account.html +4 -4
  173. data/manual/html/flags.html +4 -4
  174. data/manual/html/flags.journalentry.html +4 -4
  175. data/manual/html/flags.report.html +5 -5
  176. data/manual/html/flags.resource.html +4 -4
  177. data/manual/html/flags.statussheet.html +4 -4
  178. data/manual/html/flags.task.html +4 -4
  179. data/manual/html/flags.timesheet.html +4 -4
  180. data/manual/html/fontcolor.column.html +43 -8
  181. data/manual/html/footer.html +5 -5
  182. data/manual/html/formats.html +5 -5
  183. data/manual/html/functions.html +4 -4
  184. data/manual/html/gapduration.html +4 -4
  185. data/manual/html/gaplength.html +4 -4
  186. data/manual/html/halign.center.html +4 -4
  187. data/manual/html/halign.column.html +43 -8
  188. data/manual/html/halign.left.html +4 -4
  189. data/manual/html/halign.right.html +4 -4
  190. data/manual/html/hasalert.html +4 -4
  191. data/manual/html/header.html +5 -5
  192. data/manual/html/headline.html +7 -7
  193. data/manual/html/height.html +72 -0
  194. data/manual/html/hideaccount.html +46 -11
  195. data/manual/html/hidejournalentry.html +5 -5
  196. data/manual/html/hidereport.html +43 -8
  197. data/manual/html/hideresource.html +44 -9
  198. data/manual/html/hidetask.html +44 -9
  199. data/manual/html/icalreport.html +4 -4
  200. data/manual/html/include.macro.html +4 -4
  201. data/manual/html/include.project.html +4 -4
  202. data/manual/html/include.properties.html +4 -4
  203. data/manual/html/index.html +2 -1
  204. data/manual/html/inherit.extend.html +4 -4
  205. data/manual/html/interval1.html +4 -4
  206. data/manual/html/interval2.html +4 -4
  207. data/manual/html/interval3.html +4 -4
  208. data/manual/html/interval4.html +4 -4
  209. data/manual/html/isactive.html +4 -4
  210. data/manual/html/ischildof.html +4 -4
  211. data/manual/html/isdependencyof.html +4 -4
  212. data/manual/html/isdutyof.html +4 -4
  213. data/manual/html/isfeatureof.html +4 -4
  214. data/manual/html/isleaf.html +4 -4
  215. data/manual/html/ismilestone.html +4 -4
  216. data/manual/html/isongoing.html +4 -4
  217. data/manual/html/isresource.html +4 -4
  218. data/manual/html/isresponsibilityof.html +4 -4
  219. data/manual/html/istask.html +4 -4
  220. data/manual/html/journalattributes.html +11 -7
  221. data/manual/html/journalentry.html +4 -4
  222. data/manual/html/journalmode.html +5 -5
  223. data/manual/html/leaveallowance.html +5 -5
  224. data/manual/html/leaves.html +5 -6
  225. data/manual/html/left.html +5 -5
  226. data/manual/html/length.html +4 -4
  227. data/manual/html/limits.allocate.html +4 -4
  228. data/manual/html/limits.html +4 -4
  229. data/manual/html/limits.resource.html +4 -4
  230. data/manual/html/limits.task.html +4 -4
  231. data/manual/html/listitem.column.html +4 -4
  232. data/manual/html/listtype.column.html +4 -4
  233. data/manual/html/loadunit.html +5 -5
  234. data/manual/html/logicalexpression.html +8 -44
  235. data/manual/html/logicalflagexpression.html +4 -4
  236. data/manual/html/macro.html +4 -4
  237. data/manual/html/managers.html +4 -4
  238. data/manual/html/mandatory.html +4 -4
  239. data/manual/html/maxend.html +4 -4
  240. data/manual/html/maximum.html +5 -5
  241. data/manual/html/maxstart.html +4 -4
  242. data/manual/html/milestone.html +4 -4
  243. data/manual/html/minend.html +4 -4
  244. data/manual/html/minimum.html +5 -5
  245. data/manual/html/minstart.html +4 -4
  246. data/manual/html/monthlymax.html +5 -5
  247. data/manual/html/monthlymin.html +5 -5
  248. data/manual/html/navbar.html +10 -4
  249. data/manual/html/navigator.html +4 -4
  250. data/manual/html/newtask.html +4 -4
  251. data/manual/html/nikureport.html +4 -4
  252. data/manual/html/note.task.html +4 -4
  253. data/manual/html/now.html +4 -4
  254. data/manual/html/numberformat.html +5 -5
  255. data/manual/html/onend.html +4 -4
  256. data/manual/html/onstart.html +4 -4
  257. data/manual/html/opennodes.html +5 -5
  258. data/manual/html/overtime.booking.html +4 -4
  259. data/manual/html/period.column.html +4 -4
  260. data/manual/html/period.limit.html +4 -4
  261. data/manual/html/period.report.html +5 -5
  262. data/manual/html/period.task.html +4 -4
  263. data/manual/html/persistent.html +4 -4
  264. data/manual/html/precedes.html +4 -4
  265. data/manual/html/priority.html +4 -4
  266. data/manual/html/priority.timesheet.html +4 -4
  267. data/manual/html/project.html +4 -4
  268. data/manual/html/projectid.html +4 -4
  269. data/manual/html/projectid.task.html +4 -4
  270. data/manual/html/projectids.html +4 -4
  271. data/manual/html/projection.html +5 -7
  272. data/manual/html/prolog.html +5 -5
  273. data/manual/html/properties.html +11 -5
  274. data/manual/html/purge.html +5 -5
  275. data/manual/html/rate.html +4 -4
  276. data/manual/html/rate.resource.html +4 -4
  277. data/manual/html/reference.extend.html +4 -4
  278. data/manual/html/remaining.html +4 -4
  279. data/manual/html/replace.html +4 -4
  280. data/manual/html/reportprefix.html +4 -4
  281. data/manual/html/resource.html +4 -10
  282. data/manual/html/resourceattributes.html +4 -4
  283. data/manual/html/resourceprefix.html +4 -4
  284. data/manual/html/resourcereport.html +28 -10
  285. data/manual/html/resourceroot.html +5 -5
  286. data/manual/html/resources.limit.html +4 -4
  287. data/manual/html/responsible.html +4 -4
  288. data/manual/html/richtext.extend.html +4 -4
  289. data/manual/html/right.html +5 -5
  290. data/manual/html/rollupaccount.html +44 -9
  291. data/manual/html/rollupresource.html +44 -9
  292. data/manual/html/rolluptask.html +44 -9
  293. data/manual/html/scale.column.html +4 -4
  294. data/manual/html/scenario.html +4 -22
  295. data/manual/html/scenario.ical.html +4 -4
  296. data/manual/html/scenarios.export.html +4 -4
  297. data/manual/html/scenarios.html +5 -5
  298. data/manual/html/scenariospecific.extend.html +4 -4
  299. data/manual/html/scheduled.html +4 -4
  300. data/manual/html/scheduling.html +4 -4
  301. data/manual/html/select.html +4 -4
  302. data/manual/html/selfcontained.html +5 -5
  303. data/manual/html/shift.allocate.html +4 -4
  304. data/manual/html/shift.html +4 -4
  305. data/manual/html/shift.resource.html +5 -5
  306. data/manual/html/shift.task.html +4 -4
  307. data/manual/html/shift.timesheet.html +4 -4
  308. data/manual/html/shifts.allocate.html +4 -4
  309. data/manual/html/shifts.resource.html +4 -4
  310. data/manual/html/shifts.task.html +4 -4
  311. data/manual/html/shorttimeformat.html +4 -4
  312. data/manual/html/sloppy.booking.html +4 -4
  313. data/manual/html/sloppy.projection.html +5 -5
  314. data/manual/html/sortaccounts.html +5 -5
  315. data/manual/html/sortjournalentries.html +5 -5
  316. data/manual/html/sortresources.html +5 -5
  317. data/manual/html/sorttasks.html +5 -5
  318. data/manual/html/start.column.html +4 -4
  319. data/manual/html/start.html +4 -4
  320. data/manual/html/start.limit.html +4 -4
  321. data/manual/html/start.report.html +5 -5
  322. data/manual/html/startcredit.html +4 -4
  323. data/manual/html/status.statussheet.html +4 -4
  324. data/manual/html/status.timesheet.html +4 -4
  325. data/manual/html/statussheet.html +4 -4
  326. data/manual/html/statussheetreport.html +4 -4
  327. data/manual/html/strict.projection.html +5 -5
  328. data/manual/html/summary.html +4 -4
  329. data/manual/html/supplement.html +4 -4
  330. data/manual/html/supplement.resource.html +4 -10
  331. data/manual/html/supplement.task.html +4 -28
  332. data/manual/html/tagfile.html +4 -4
  333. data/manual/html/task.html +4 -28
  334. data/manual/html/task.statussheet.html +4 -4
  335. data/manual/html/task.timesheet.html +4 -4
  336. data/manual/html/taskattributes.html +4 -4
  337. data/manual/html/taskprefix.html +4 -4
  338. data/manual/html/taskreport.html +28 -10
  339. data/manual/html/taskroot.html +5 -5
  340. data/manual/html/text.extend.html +4 -4
  341. data/manual/html/textreport.html +27 -9
  342. data/manual/html/timeformat.html +5 -5
  343. data/manual/html/timeoff.nikureport.html +4 -4
  344. data/manual/html/timesheet.html +4 -4
  345. data/manual/html/timesheetreport.html +23 -5
  346. data/manual/html/timezone.export.html +4 -4
  347. data/manual/html/timezone.html +4 -4
  348. data/manual/html/timezone.report.html +5 -5
  349. data/manual/html/timezone.shift.html +4 -4
  350. data/manual/html/timingresolution.html +4 -4
  351. data/manual/html/title.column.html +4 -4
  352. data/manual/html/title.html +5 -5
  353. data/manual/html/toc.html +207 -179
  354. data/manual/html/tooltip.column.html +45 -10
  355. data/manual/html/tracereport.html +405 -0
  356. data/manual/html/trackingscenario.html +6 -6
  357. data/manual/html/treelevel.html +4 -4
  358. data/manual/html/vacation.html +4 -4
  359. data/manual/html/vacation.resource.html +4 -4
  360. data/manual/html/vacation.shift.html +4 -4
  361. data/manual/html/warn.html +43 -8
  362. data/manual/html/weeklymax.html +5 -5
  363. data/manual/html/weeklymin.html +5 -5
  364. data/manual/html/weekstartsmonday.html +4 -4
  365. data/manual/html/weekstartssunday.html +6 -6
  366. data/manual/html/width.column.html +6 -6
  367. data/manual/html/width.html +72 -0
  368. data/manual/html/work.html +4 -4
  369. data/manual/html/workinghours.project.html +4 -4
  370. data/manual/html/workinghours.resource.html +4 -4
  371. data/manual/html/workinghours.shift.html +4 -4
  372. data/manual/html/yearlyworkingdays.html +4 -4
  373. data/spec/Color_spec.rb +60 -0
  374. data/spec/ProjectBroker_spec.rb +3 -2
  375. data/spec/StatusSheets_spec.rb +5 -4
  376. data/spec/TableColumnSorter_spec.rb +78 -0
  377. data/spec/TimeSheets_spec.rb +6 -2
  378. data/spec/Tj3Daemon_spec.rb +2 -2
  379. data/spec/TraceReport_spec.rb +117 -0
  380. data/taskjuggler.gemspec +1 -1
  381. data/test/MessageChecker.rb +3 -1
  382. data/test/ReferenceGenerator.rb +1 -1
  383. data/test/TestSuite/CSV-Reports/Leave.tjp +1 -1
  384. data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +3 -0
  385. data/test/TestSuite/CSV-Reports/refs/taskcounter.csv +9 -0
  386. data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +19 -16
  387. data/test/TestSuite/CSV-Reports/refs/weekly.csv +1 -0
  388. data/test/TestSuite/Export-Reports/refs/LogicalExpression.tjp +14 -2
  389. data/test/TestSuite/Export-Reports/refs/tutorial.tjp +98 -86
  390. data/test/TestSuite/Scheduler/Correct/Leaves.tjp +25 -0
  391. data/test/TestSuite/Syntax/Correct/Leave.tjp +1 -1
  392. data/test/TestSuite/Syntax/Correct/LogicalExpression.tjp +9 -1
  393. data/test/TestSuite/Syntax/Correct/TraceReport.tjp +10 -0
  394. data/test/TestSuite/Syntax/Correct/tutorial.tjp +10 -4
  395. data/test/test_CSV-Reports.rb +3 -3
  396. data/test/test_Export-Reports.rb +91 -86
  397. data/test/test_Journal.rb +15 -12
  398. data/test/test_Limits.rb +3 -3
  399. data/test/test_Project.rb +1 -2
  400. data/test/test_ProjectFileScanner.rb +1 -1
  401. data/test/test_PropertySet.rb +1 -1
  402. data/test/test_Query.rb +5 -6
  403. data/test/test_ReportGenerator.rb +15 -7
  404. data/test/test_RichText.rb +4 -3
  405. data/test/test_Scheduler.rb +19 -7
  406. data/test/test_ShiftAssignments.rb +2 -2
  407. data/test/test_SimpleQueryExpander.rb +29 -2
  408. data/test/test_Syntax.rb +14 -5
  409. metadata +49 -10
  410. data/lib/taskjuggler/LogFile.rb +0 -73
@@ -11,12 +11,10 @@
11
11
  # published by the Free Software Foundation.
12
12
  #
13
13
 
14
- require 'drb'
15
14
  require 'webrick'
16
- require 'stringio'
17
15
 
18
16
  require 'taskjuggler/AppConfig'
19
- require 'taskjuggler/RichText'
17
+ require 'taskjuggler/daemon/Daemon'
20
18
  require 'taskjuggler/daemon/WelcomePage'
21
19
  require 'taskjuggler/daemon/ReportServlet'
22
20
 
@@ -24,24 +22,73 @@ class TaskJuggler
24
22
 
25
23
  # The WebServer class provides a self-contained HTTP server that can serve
26
24
  # HTML versions of Report objects that are generated on the fly.
27
- class WebServer
25
+ class WebServer < Daemon
28
26
 
29
- attr_reader :broker
27
+ include DaemonConnectorMixin
28
+
29
+ attr_accessor :authKey, :port, :uriFile, :webServerPort
30
30
 
31
31
  # Create a web server object that runs in a separate thread.
32
- def initialize(broker, port)
33
- @log = LogFile.instance
34
- @broker = broker
32
+ def initialize
33
+ super
34
+ # For security reasons, this will probably not change. All DRb
35
+ # operations are limited to localhost only. The client and the sever
36
+ # must have access to the identical file system.
37
+ @host = '127.0.0.1'
38
+ # The default TCP/IP port. ASCII code decimals for 'T' and 'J'.
39
+ @port = 8474
40
+ # The file with the server URI in case port is 0.
41
+ @uriFile = File.join(Dir.getwd, '.tj3d.uri')
42
+ # We don't have a default key. The user must provice a key in the config
43
+ # file. Otherwise the daemon will not start.
44
+ @authKey = nil
45
+
46
+ # Reference to WEBrick object.
47
+ @webServer = nil
48
+
49
+ # Port used by the web server
50
+ @webServerPort = 8080
35
51
 
36
- config = { :Port => port }
37
- @server = WEBrick::HTTPServer.new(config)
38
- @server.mount('/', WelcomePage, nil)
39
- @server.mount('/taskjuggler', ReportServlet, @broker)
52
+ Kernel.trap('TERM') do
53
+ debug('webserver_term_signal', 'TERM signal received. Exiting...')
54
+ # When the OS sends us a TERM signal, we try to exit gracefully.
55
+ stop
56
+ end
57
+ end
58
+
59
+ def start
60
+ # In daemon mode, we fork twice and only the 2nd child continues here.
61
+ super()
62
+
63
+ debug('', "Starting web server")
64
+ config = { :Port => @webServerPort }
65
+ begin
66
+ @server = WEBrick::HTTPServer.new(config)
67
+ info('webserver_port',
68
+ "Web server is listening on port #{@webServerPort}")
69
+ rescue
70
+ fatal('webrick_start_failed', "Cannot start WEBrick: #{$!}")
71
+ end
72
+
73
+ begin
74
+ @server.mount('/', WelcomePage, nil)
75
+ rescue
76
+ fatal('welcome_page_mount_failed',
77
+ "Cannot mount WEBrick welcome page: #{$!}")
78
+ end
79
+
80
+ begin
81
+ @server.mount('/taskjuggler', ReportServlet,
82
+ [ @authKey, @host, @port, @uri ])
83
+ rescue
84
+ fatal('broker_page_mount_failed',
85
+ "Cannot mount WEBrick broker page: #{$!}")
86
+ end
40
87
 
41
88
  # Serve some directories via the FileHandler servlet.
42
89
  %w( css icons scripts ).each do |dir|
43
90
  unless (fullDir = AppConfig.dataDirs("data/#{dir}")[0])
44
- @log.fatal(<<"EOT"
91
+ error('dir_not_found', <<"EOT"
45
92
  Cannot find the #{dir} directory. This is usually the result of an
46
93
  improper TaskJuggler installation. If you know the directory, you can use the
47
94
  TASKJUGGLER_DATA_PATH environment variable to specify the location. The
@@ -50,19 +97,25 @@ directories must be separated by colons.
50
97
  EOT
51
98
  )
52
99
  end
53
- @server.mount("/#{dir}", WEBrick::HTTPServlet::FileHandler, fullDir)
54
- end
55
100
 
56
- # Start the web server in a new thread so we don't block this thread.
57
- @thread = Thread.new do
58
101
  begin
59
- @server.start
102
+ @server.mount("/#{dir}", WEBrick::HTTPServlet::FileHandler, fullDir)
60
103
  rescue
61
- $stderr.print $!.to_s
62
- $stderr.print $!.backtrace.join("\n")
63
- @log.fatal("Web server error: #{$!}")
104
+ fatal('dir_mount_failed',
105
+ "Cannot mount directory #{dir} in WEBrick: #{$!}")
64
106
  end
65
107
  end
108
+
109
+ # Install signal handler to exit gracefully on CTRL-C.
110
+ intHandler = Kernel.trap('INT') do
111
+ stop
112
+ end
113
+
114
+ begin
115
+ @server.start
116
+ rescue
117
+ fatal('web_server_error', "Web server error: #{$!}")
118
+ end
66
119
  end
67
120
 
68
121
  # Stop the web server.
@@ -70,8 +123,8 @@ EOT
70
123
  if @server
71
124
  @server.shutdown
72
125
  @server = nil
73
- @thread.join
74
126
  end
127
+ super
75
128
  end
76
129
 
77
130
  end
@@ -14,6 +14,7 @@
14
14
  require 'webrick'
15
15
 
16
16
  require 'taskjuggler/Tj3Config'
17
+ require 'taskjuggler/HTMLDocument'
17
18
 
18
19
  class TaskJuggler
19
20
 
@@ -33,7 +33,7 @@ class TaskJuggler
33
33
  def generateIntermediateFormat
34
34
  super
35
35
 
36
- # Prepare the task list.
36
+ # Prepare the account list.
37
37
  accountList = PropertyList.new(@project.accounts)
38
38
  accountList.setSorting(@report.get('sortAccounts'))
39
39
  accountList.query = @report.project.reportContexts.last.query
@@ -49,8 +49,8 @@ class TaskJuggler
49
49
  generateHeaderCell(columnDescr)
50
50
  end
51
51
 
52
- if (costAccount = @report.get('costAccount')) &&
53
- (revenueAccount = @report.get('revenueAccount'))
52
+ if (costAccount = @report.get('costaccount')) &&
53
+ (revenueAccount = @report.get('revenueaccount'))
54
54
  # We are in balance mode. First show the cost and then the revenue
55
55
  # accounts and then the total balance.
56
56
  costAccountList = PropertyList.new(@project.accounts)
@@ -90,6 +90,7 @@ class TaskJuggler
90
90
  # Make sure the input is terminated with a record end.
91
91
  str += "\n" unless str[-1] == ?\n
92
92
 
93
+ line = 1
93
94
  str.each_utf8_char do |c|
94
95
  #puts "c: #{c} State: #{state}"
95
96
  case state
@@ -123,6 +124,7 @@ class TaskJuggler
123
124
  else
124
125
  # We've found a normal character of the quoted field
125
126
  field << c
127
+ line += 1 if c == "\n"
126
128
  end
127
129
  when :quoteInQuotedField
128
130
  # We are processing a quoted quote or the end of a quoted field
@@ -134,7 +136,7 @@ class TaskJuggler
134
136
  state = :fieldEnd
135
137
  redo
136
138
  else
137
- raise "Unexpected character #{c} in CSV"
139
+ raise "Line #{line}: Unexpected character #{c} in cell: #{field}"
138
140
  end
139
141
  when :inUnquotedField
140
142
  # We are processing an unquoted field
@@ -165,13 +167,18 @@ class TaskJuggler
165
167
  @data << fields
166
168
  # Look for a new record.
167
169
  state = :startOfRecord
170
+ line += 1
168
171
  else
169
172
  raise "Unknown state #{state}"
170
173
  end
171
174
  end
172
175
 
173
176
  unless state == :startOfRecord
174
- raise "CSV state machine error in state #{state}"
177
+ if state == :inQuotedField
178
+ raise "Line #{line}: Unterminated quoted cell: #{field}"
179
+ else
180
+ raise "Line #{line}: CSV error in state #{state}: #{field}"
181
+ end
175
182
  end
176
183
 
177
184
  @data
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env ruby -w
2
+ # encoding: UTF-8
3
+ #
4
+ # = ChartPlotter.rb -- The TaskJuggler III Project Management Software
5
+ #
6
+ # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012
7
+ # by Chris Schlaeger <chris@linux.com>
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify
10
+ # it under the terms of version 2 of the GNU General Public License as
11
+ # published by the Free Software Foundation.
12
+ #
13
+
14
+ require 'taskjuggler/Painter'
15
+
16
+ class TaskJuggler
17
+
18
+ class ChartPlotterError < RuntimeError
19
+ end
20
+
21
+ class ChartPlotter
22
+
23
+ def initialize(width, height, data)
24
+ # +------------------------------------------------
25
+ # | ^
26
+ # | topMargin | legendGap
27
+ # | v <->
28
+ # | | -x- foo
29
+ # |<-leftMargin->| -x- bar
30
+ # | | <-legend
31
+ # | | Width---->
32
+ # | +------------
33
+ # | ^ <-rightMargin->
34
+ # | bottomMargin|
35
+ # | v
36
+ # +------------------------------------------------
37
+ # <-----------------canvasWidth-------------------->
38
+ # The width of the canvas area
39
+ @width = width
40
+ # The height of the canvas area
41
+ @height = height
42
+ # The raw data to plot as loaded from the CSV file.
43
+ @data = data
44
+
45
+ # The margins between the graph plotting area and the canvas borders.
46
+ @topMargin = 30
47
+ @bottomMargin = 30
48
+ @leftMargin = 70
49
+ @rightMargin = (@width * 0.382).to_i
50
+
51
+ @legendGap = 20
52
+ @markerWidth = 20
53
+ @markerX = @width - @rightMargin + @legendGap
54
+ @markerGap = 5
55
+ @labelX = @markerX + @markerWidth + @markerGap
56
+ @labelHeight = 24
57
+
58
+ # The location of the 0/0 point of the graph plotter.
59
+ @x0 = @leftMargin
60
+ @y0 = @height - @bottomMargin
61
+
62
+ @labels = []
63
+ @yData = []
64
+ @xData = nil
65
+ @dataType = nil
66
+ @xMinDate = nil
67
+ @xMaxDate = nil
68
+ @yMinDate = nil
69
+ @yMaxDate = nil
70
+ @yMinVal = nil
71
+ @yMaxVal = nil
72
+ end
73
+
74
+ # Create the chart as Painter object.
75
+ def generate
76
+ analyzeData
77
+ calcChartGeometry
78
+ @painter = Painter.new(@width, @height) do |pa|
79
+ drawGrid(pa)
80
+ 0.upto(@yData.length - 1) do |ci|
81
+ # Compute a unique and distinguishable color for each data set. We
82
+ # primarily use the hue value of the HSV color space for this. It
83
+ # has 6 main colors each 60 degrees apart from each other. After the
84
+ # first 360 round, we shift the angle by 60 / round so we get a
85
+ # different color set than in the previous round. Additionally, the
86
+ # saturation is decreased with each data set.
87
+ color = Painter::Color.new(
88
+ (60 * (ci % 6) + (60 / (1 + ci / 6))) % 360,
89
+ 255 - (ci / 8), 230, :hsv)
90
+
91
+ drawDataGraph(pa, ci, color)
92
+ drawLegendEntry(pa, ci, color)
93
+ end
94
+ end
95
+ end
96
+
97
+ def to_svg
98
+ @painter.to_svg
99
+ end
100
+
101
+ private
102
+
103
+ def analyzeData
104
+ # Convert the @data from a line list into a column list. Each element of
105
+ # the list is an Array for the other dimension.
106
+ columns = []
107
+ ri = 0
108
+ @data.each do |row|
109
+ ci = 0
110
+ row.each do |col|
111
+ columns << [] if ri == 0
112
+
113
+ if ci >= columns.length
114
+ error("Row #{ri} contains more elements than the header row")
115
+ end
116
+
117
+ columns[ci][ri] = col
118
+ ci += 1
119
+ end
120
+ ri += 1
121
+ end
122
+
123
+ header = true
124
+ line = 1
125
+ columns[0].each do |date|
126
+ if header
127
+ unless date == "Date"
128
+ error("First column must have a 'Date' header instead of '#{date}'")
129
+ end
130
+ header = false
131
+ else
132
+ unless date.is_a?(TjTime)
133
+ error("First colum (#{date}) of line #{line} must be all dates")
134
+ end
135
+ @xMinDate = date if @xMinDate.nil? || date < @xMinDate
136
+ @xMaxDate = date if @xMaxDate.nil? || date > @xMaxDate
137
+ end
138
+ line += 1
139
+ end
140
+
141
+ unless @xMinDate && @xMaxDate
142
+ error("First column does not contain valid dates.")
143
+ end
144
+
145
+ # Add the xData values.
146
+ @xData = columns[0][1..-1]
147
+
148
+ # Now eleminate columns that contain invalid data.
149
+ 1.upto(columns.length - 1) do |colIdx|
150
+ col = columns[colIdx]
151
+ badCol = false
152
+ col[1..-1].each do |cell|
153
+ if cell
154
+ if cell.is_a?(TjTime)
155
+ if @dataType && @dataType != :date
156
+ error("Column #{colIdx} contains non-date (#{cell}). " +
157
+ "The columns will be ignored.")
158
+ badCol = true
159
+ break
160
+ else
161
+ @dataType = :date
162
+ end
163
+
164
+ @yMinDate = cell if @yMinDate.nil? || cell < @yMinDate
165
+ @yMaxDate = cell if @yMaxDate.nil? || cell > @yMaxDate
166
+ elsif cell.is_a?(Fixnum) || cell.is_a?(Float)
167
+ if @dataType && @dataType != :number
168
+ error("Column #{colIdx} contains non-number (#{cell}). " +
169
+ "The columns will be ignored.")
170
+ badCol = true
171
+ break
172
+ else
173
+ @dataType = :number
174
+ end
175
+
176
+ @yMinVal = cell if @yMinVal.nil? || cell < @yMinVal
177
+ @yMaxVal = cell if @yMaxVal.nil? || cell > @yMaxVal
178
+ else
179
+ error("Column #{colIdx} contains invalid data (#{cell}). " +
180
+ "The columns will be ignored.")
181
+ badCol = true
182
+ break
183
+ end
184
+ else
185
+ # Ignore missing values
186
+ next unless cell
187
+ end
188
+ end
189
+ # Store the header of the column. It will be used as label.
190
+ @labels << col[0]
191
+ # Add the column values as another entry into the yData list.
192
+ @yData << col[1..-1] unless badCol
193
+ end
194
+
195
+ if @dataType.nil? || @yData.empty?
196
+ error("Columns don't contain any valid dates.")
197
+ end
198
+ end
199
+
200
+ def calcChartGeometry
201
+ # The size of the X-axis in pixels
202
+ xAxisPixels = @width - (@rightMargin + @leftMargin)
203
+ fm = Painter::FontMetrics.new
204
+ # Width of the date label in pixels
205
+ @dateLabelWidth = fm.width('LiberationSans', 10.0, '2000-01-01')
206
+ # Height of the date label in pixels
207
+ @labelHeight = fm.height('LiberationSans', 10.0)
208
+ # Distance between 2 labels in pixels
209
+ labelPadding = 10
210
+ # The number of labels that fit underneath the X-axis
211
+ @noXLabels = (xAxisPixels / (@dateLabelWidth + labelPadding)).floor
212
+
213
+ # The number of labels that fit along the Y-axis
214
+ yAxisPixels = @height - (@topMargin + @bottomMargin)
215
+ @noYLabels = (yAxisPixels / (@labelHeight + labelPadding)).floor
216
+ @noYLabels = 10 if @noYLabels > 10
217
+
218
+ # Set min X date to midnight time.
219
+ @xMinDate = @xMinDate.midnight
220
+ # Ensure that we have at least a @noXLabels days long interval.
221
+ minInterval = 60 * 60 * 24 * @noXLabels
222
+ @xMaxDate = @xMinDate + minInterval if @xMaxDate - @xMinDate < minInterval
223
+
224
+ case @dataType
225
+ when :date
226
+ # Set min Y date to midnight time.
227
+ @yMinDate = @yMinDate.midnight
228
+ # Ensure that we have at least a @noYLabels days long interval.
229
+ minInterval = 60 * 60 * 24 * @noYLabels
230
+ if @yMaxDate - @yMinDate < minInterval
231
+ @yMaxDate = @yMinDate + minInterval
232
+ end
233
+ when :number
234
+ # If all Y values are the same, we ensure that the Y-axis starts at 0
235
+ # to provide a sense of scale.
236
+ @yMinVal = 0 if @yMinVal == @yMaxVal
237
+
238
+ # Ensure that Y-axis has at least a range of @noYLabels
239
+ if @yMaxVal - @yMinVal < @noYLabels
240
+ @yMaxVal = @yMinVal + @noYLabels
241
+ end
242
+ else
243
+ raise "Unsupported dataType: #{@dataType}"
244
+ end
245
+ end
246
+
247
+ def xLabels(p)
248
+ # The time difference between two labels.
249
+ labelInterval = (@xMaxDate - @xMinDate) / @noXLabels
250
+ # We want the first label to show left-aligned with the Y-axis. Calc the
251
+ # date for the first label.
252
+ date = @xMinDate + labelInterval / 2
253
+
254
+ p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0,
255
+ :stroke => p.color(:black), :stroke_width => 1,
256
+ :fill => p.color(:black)) do |gp|
257
+ @noXLabels.times do |i|
258
+ x = xDate2c(date)
259
+ gp.text(x - @dateLabelWidth / 2, y2c(-5 - @labelHeight),
260
+ date.to_s('%Y-%m-%d'), :stroke_width => 0)
261
+ #gp.rect(x - @dateLabelWidth / 2, y2c(-5 - @labelHeight),
262
+ # @dateLabelWidth, @labelHeight, :fill => gp.color(:white))
263
+ gp.line(x, y2c(0), x, y2c(-4))
264
+ date += labelInterval
265
+ end
266
+ end
267
+ end
268
+
269
+ def yLabels(p)
270
+ case @dataType
271
+ when :date
272
+ return unless @yMinDate && @yMaxDate
273
+
274
+ yInterval = @yMaxDate - @yMinDate
275
+
276
+ # The time difference between two labels.
277
+ labelInterval = yInterval / @noYLabels
278
+ date = @yMinDate + labelInterval / 2
279
+ p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0,
280
+ :stroke => p.color(:black), :stroke_width => 1,
281
+ :fill => p.color(:black)) do |gp|
282
+ @noYLabels.times do |i|
283
+ y = yDate2c(date)
284
+ gp.text(0, y + @labelHeight / 2 - 2,
285
+ date.to_s('%Y-%m-%d'), :stroke_width => 0)
286
+ gp.line(x2c(-4), y, @width - @rightMargin, y)
287
+ date += labelInterval
288
+ end
289
+ end
290
+ when :number
291
+ return unless @yMinVal && @yMaxVal
292
+
293
+ yInterval = (@yMaxVal - @yMinVal).to_f
294
+
295
+ fm = Painter::FontMetrics.new
296
+
297
+ # The value difference between two labels.
298
+ labelInterval = yInterval / @noYLabels
299
+
300
+ # We'd like to have the labels to only show number starting with
301
+ # single most significant digit that read 1, 2 or 5. If necessary, we
302
+ # increase the labelInterval to the next matching number and reduce
303
+ # the number of y labels accordingly.
304
+ factor = 10 ** Math.log10(labelInterval).floor
305
+ msd = (labelInterval / factor).ceil
306
+ if msd == 3 || msd == 4
307
+ msd = 5
308
+ elsif msd > 5
309
+ msd = 10
310
+ end
311
+ labelInterval = msd * factor
312
+ @noYLabels = ((@yMaxVal - @yMinVal) / labelInterval).floor
313
+
314
+ val = @yMinVal + labelInterval
315
+ p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0,
316
+ :stroke => p.color(:black), :stroke_width => 1,
317
+ :fill => p.color(:black)) do |gp|
318
+ @noYLabels.times do |i|
319
+ y = yNum2c(val)
320
+ labelText = val.to_s
321
+ labelWidth = fm.width('LiberationSans', 10.0, labelText)
322
+ gp.text(@leftMargin - 7 - labelWidth, y + @labelHeight / 2 - 3,
323
+ labelText, :stroke_width => 0)
324
+ gp.line(x2c(-4), y, @width - @rightMargin, y)
325
+ val += labelInterval
326
+ end
327
+ end
328
+ else
329
+ raise "Unsupported dataType #{@dataType}"
330
+ end
331
+
332
+
333
+ end
334
+
335
+ # Convert a chart X coordinate to a canvas X coordinate.
336
+ def x2c(x)
337
+ @x0 + x
338
+ end
339
+
340
+ # Convert a chart Y coordinate to a canvas Y coordinate.
341
+ def y2c(y)
342
+ @y0 - y
343
+ end
344
+
345
+ # Convert a date to a chart X coordinate.
346
+ def xDate2c(date)
347
+ x2c(((date - @xMinDate) * (@width - (@leftMargin + @rightMargin))) /
348
+ (@xMaxDate - @xMinDate))
349
+ end
350
+
351
+ # Convert a Y data date to a chart Y coordinate.
352
+ def yDate2c(date)
353
+ y2c(((date - @yMinDate) * (@height - (@topMargin + @bottomMargin))) /
354
+ (@yMaxDate - @yMinDate))
355
+ end
356
+
357
+ # Convert a Y data value to a chart Y coordinate.
358
+ def yNum2c(number)
359
+ y2c(((number - @yMinVal) * (@height - (@topMargin + @bottomMargin))) /
360
+ (@yMaxVal - @yMinVal))
361
+ end
362
+
363
+ def drawGrid(painter)
364
+ painter.group(:stroke => painter.color(:black), :font_size => 11) do |p|
365
+ p.line(x2c(0), y2c(0),
366
+ x2c(@width - (@leftMargin + @rightMargin)), y2c(0))
367
+ p.line(x2c(0), y2c(0),
368
+ x2c(0), y2c(@height - (@topMargin + @bottomMargin)))
369
+ yLabels(p)
370
+ xLabels(p)
371
+ end
372
+ end
373
+
374
+ def drawDataGraph(painter, ci, color)
375
+ values = @yData[ci]
376
+ painter.group(:stroke_width => 3, :stroke => color, :fill => color) do |p|
377
+ lastX = lastY = nil
378
+ # Plot markers for each x/y data pair of the set and connect the
379
+ # dots with lines. If a y value is nil, the line will be
380
+ # interrupted.
381
+ values.length.times do |i|
382
+ if values[i]
383
+ xc = xDate2c(@xData[i])
384
+ if values[i].is_a?(TjTime)
385
+ yc = yDate2c(values[i])
386
+ else
387
+ yc = yNum2c(values[i])
388
+ end
389
+ p.line(lastX, lastY, xc, yc) if lastY
390
+ setMarker(p, ci, xc, yc)
391
+ lastX = xc
392
+ lastY = yc
393
+ end
394
+ end
395
+ end
396
+ end
397
+
398
+ def drawLegendEntry(painter, ci, color)
399
+ painter.group(:stroke_width => 3, :stroke => color, :fill => color,
400
+ :font_size => 11) do |p|
401
+ # Add the marker to the legend
402
+ labelY = @topMargin + @labelHeight / 2 + ci * (@labelHeight + 4)
403
+ markerY = labelY + (@labelHeight + 4) / 2
404
+ setMarker(p, ci, @markerX + @markerWidth / 2, markerY)
405
+ p.line(@markerX, markerY, @markerX + @markerWidth, markerY)
406
+ p.text(@labelX, labelY + @labelHeight, @labels[ci],
407
+ :stroke => p.color(:black), :stroke_width => 0,
408
+ :fill => p.color(:black))
409
+ end
410
+ end
411
+
412
+ def setMarker(p, type, x, y)
413
+ r = 4
414
+ case (type / 5) % 5
415
+ when 0
416
+ # Diamond
417
+ points = [ [ x - r, y ],
418
+ [ x, y + r ],
419
+ [ x + r, y ],
420
+ [ x, y - r ],
421
+ [ x - r, y ] ]
422
+ p.polyline(points)
423
+ when 1
424
+ # Square
425
+ rr = (r / Math.sqrt(2.0)).to_i
426
+ p.rect(x - rr, y - rr, 2 * rr, 2 * rr)
427
+ when 2
428
+ # Triangle Down
429
+ points = [ [ x - r, y - r ],
430
+ [ x, y + r ],
431
+ [ x + r, y - r ],
432
+ [ x - r, y - r ] ]
433
+ p.polyline(points)
434
+ when 3
435
+ # Triangle Up
436
+ points = [ [ x - r, y + r ],
437
+ [ x, y - r ],
438
+ [ x + r, y + r ],
439
+ [ x - r, y + r ] ]
440
+ p.polyline(points)
441
+ else
442
+ p.circle(x, y, r)
443
+ end
444
+ end
445
+
446
+ def error(msg)
447
+ raise ChartPlotterError, msg
448
+ end
449
+
450
+ end
451
+
452
+ end
453
+