taskjuggler 3.1.0 → 3.2.0

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