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.
- data/CHANGELOG +44 -0
- data/bin/tj3webd +4 -0
- data/data/css/tjreport.css +14 -5
- data/data/tjp.vim +22 -7
- data/examples/Fedora-20/reports.tji +2 -4
- data/examples/Scrum/Product Burndown.csv +26 -0
- data/examples/Scrum/Sprint 1 Burndown.csv +26 -0
- data/examples/Scrum/Sprint 2 Burndown.csv +26 -0
- data/examples/Scrum/Sprint 3 Burndown.csv +26 -0
- data/examples/Scrum/scrum.tjp +141 -0
- data/examples/Tutorial/tutorial.tjp +13 -7
- data/lib/taskjuggler/Attributes.rb +2 -3
- data/lib/taskjuggler/HTMLDocument.rb +25 -18
- data/lib/taskjuggler/Journal.rb +85 -65
- data/lib/taskjuggler/KeywordDocumentation.rb +25 -13
- data/lib/taskjuggler/LeaveList.rb +1 -1
- data/lib/taskjuggler/Limits.rb +3 -5
- data/lib/taskjuggler/MessageHandler.rb +173 -19
- data/lib/taskjuggler/Painter.rb +75 -0
- data/lib/taskjuggler/Painter/BasicShapes.rb +76 -0
- data/lib/taskjuggler/Painter/Color.rb +273 -0
- data/lib/taskjuggler/Painter/Element.rb +56 -0
- data/lib/taskjuggler/Painter/FontData.rb +221 -0
- data/lib/taskjuggler/Painter/FontMetrics.rb +125 -0
- data/lib/taskjuggler/Painter/FontMetricsData.rb +151 -0
- data/lib/taskjuggler/Painter/Group.rb +77 -0
- data/lib/taskjuggler/Painter/Points.rb +47 -0
- data/lib/taskjuggler/Painter/Primitives.rb +100 -0
- data/lib/taskjuggler/Painter/SVGSupport.rb +36 -0
- data/lib/taskjuggler/Painter/Text.rb +36 -0
- data/lib/taskjuggler/Project.rb +46 -29
- data/lib/taskjuggler/ProjectFileParser.rb +24 -22
- data/lib/taskjuggler/ProjectFileScanner.rb +2 -2
- data/lib/taskjuggler/PropertyTreeNode.rb +26 -34
- data/lib/taskjuggler/Query.rb +8 -5
- data/lib/taskjuggler/RealFormat.rb +3 -0
- data/lib/taskjuggler/Resource.rb +3 -5
- data/lib/taskjuggler/ResourceScenario.rb +19 -7
- data/lib/taskjuggler/RichText.rb +4 -6
- data/lib/taskjuggler/RichText/FunctionExample.rb +1 -1
- data/lib/taskjuggler/RichText/FunctionHandler.rb +5 -6
- data/lib/taskjuggler/RichText/Parser.rb +4 -6
- data/lib/taskjuggler/RichText/RTFNavigator.rb +1 -1
- data/lib/taskjuggler/RichText/RTFQuery.rb +2 -3
- data/lib/taskjuggler/RichText/RTFReport.rb +1 -1
- data/lib/taskjuggler/RichText/RTFReportLink.rb +1 -2
- data/lib/taskjuggler/RichText/RTFWithQuerySupport.rb +1 -1
- data/lib/taskjuggler/RichText/Scanner.rb +6 -6
- data/lib/taskjuggler/RichText/Snip.rb +1 -2
- data/lib/taskjuggler/RuntimeConfig.rb +9 -6
- data/lib/taskjuggler/ScenarioData.rb +4 -3
- data/lib/taskjuggler/Scoreboard.rb +6 -0
- data/lib/taskjuggler/SheetHandlerBase.rb +25 -8
- data/lib/taskjuggler/SimpleQueryExpander.rb +14 -5
- data/lib/taskjuggler/SyntaxReference.rb +1 -2
- data/lib/taskjuggler/TableColumnSorter.rb +84 -0
- data/lib/taskjuggler/Task.rb +3 -5
- data/lib/taskjuggler/TaskJuggler.rb +36 -29
- data/lib/taskjuggler/TaskScenario.rb +154 -66
- data/lib/taskjuggler/TextParser.rb +24 -17
- data/lib/taskjuggler/TextParser/Scanner.rb +16 -11
- data/lib/taskjuggler/TextParser/SourceFileInfo.rb +20 -15
- data/lib/taskjuggler/TimeSheets.rb +6 -12
- data/lib/taskjuggler/Tj3AppBase.rb +35 -16
- data/lib/taskjuggler/Tj3Config.rb +1 -1
- data/lib/taskjuggler/TjpSyntaxRules.rb +239 -49
- data/lib/taskjuggler/XMLElement.rb +9 -2
- data/lib/taskjuggler/apps/Tj3.rb +43 -37
- data/lib/taskjuggler/apps/Tj3Client.rb +62 -112
- data/lib/taskjuggler/apps/Tj3Daemon.rb +66 -29
- data/lib/taskjuggler/apps/Tj3Man.rb +5 -5
- data/lib/taskjuggler/apps/Tj3SsReceiver.rb +10 -13
- data/lib/taskjuggler/apps/Tj3SsSender.rb +13 -16
- data/lib/taskjuggler/apps/Tj3TsReceiver.rb +10 -13
- data/lib/taskjuggler/apps/Tj3TsSender.rb +12 -15
- data/lib/taskjuggler/apps/Tj3TsSummary.rb +12 -15
- data/lib/taskjuggler/apps/Tj3WebD.rb +99 -0
- data/lib/taskjuggler/daemon/Daemon.rb +50 -10
- data/lib/taskjuggler/daemon/DaemonConnector.rb +127 -0
- data/lib/taskjuggler/daemon/ProcessIntercom.rb +36 -21
- data/lib/taskjuggler/daemon/ProjectBroker.rb +122 -112
- data/lib/taskjuggler/daemon/ProjectServer.rb +78 -46
- data/lib/taskjuggler/daemon/ReportServer.rb +52 -28
- data/lib/taskjuggler/daemon/ReportServlet.rb +92 -21
- data/lib/taskjuggler/daemon/WebServer.rb +75 -22
- data/lib/taskjuggler/daemon/WelcomePage.rb +1 -0
- data/lib/taskjuggler/reports/AccountListRE.rb +3 -3
- data/lib/taskjuggler/reports/CSVFile.rb +9 -2
- data/lib/taskjuggler/reports/ChartPlotter.rb +453 -0
- data/lib/taskjuggler/reports/Navigator.rb +1 -0
- data/lib/taskjuggler/reports/NikuReport.rb +4 -4
- data/lib/taskjuggler/reports/Report.rb +6 -18
- data/lib/taskjuggler/reports/ReportBase.rb +9 -9
- data/lib/taskjuggler/reports/ReportContext.rb +2 -2
- data/lib/taskjuggler/reports/StatusSheetReport.rb +6 -6
- data/lib/taskjuggler/reports/TableReport.rb +24 -15
- data/lib/taskjuggler/reports/TimeSheetReport.rb +5 -5
- data/lib/taskjuggler/reports/TraceReport.rb +251 -0
- data/lib/tj3webd.rb +17 -0
- data/manual/Day_To_Day_Juggling +10 -3
- data/manual/Installation +38 -19
- data/manual/Software +25 -19
- data/manual/Tutorial +119 -110
- data/manual/html/Day_To_Day_Juggling.html +7 -5
- data/manual/html/Getting_Started.html +4 -4
- data/manual/html/How_To_Contribute.html +4 -4
- data/manual/html/Installation.html +19 -11
- data/manual/html/Intro.html +4 -4
- data/manual/html/Reporting_Bugs.html +4 -4
- data/manual/html/Rich_Text_Attributes.html +4 -4
- data/manual/html/Software.html +15 -11
- data/manual/html/TaskJuggler_2x_Migration.html +4 -4
- data/manual/html/TaskJuggler_Internals.html +4 -4
- data/manual/html/The_TaskJuggler_Syntax.html +4 -4
- data/manual/html/Tutorial.html +41 -32
- data/manual/html/account.html +4 -4
- data/manual/html/account.task.html +4 -4
- data/manual/html/accountprefix.html +4 -4
- data/manual/html/accountreport.html +27 -9
- data/manual/html/accountroot.html +5 -5
- data/manual/html/active.html +4 -4
- data/manual/html/adopt.task.html +4 -4
- data/manual/html/aggregate.html +4 -4
- data/manual/html/alert.html +4 -4
- data/manual/html/alertlevels.html +4 -4
- data/manual/html/allocate.html +5 -5
- data/manual/html/alphabet.html +4 -4
- data/manual/html/alternative.html +4 -4
- data/manual/html/author.html +4 -4
- data/manual/html/balance.html +5 -5
- data/manual/html/booking.resource.html +4 -4
- data/manual/html/booking.task.html +4 -4
- data/manual/html/caption.html +5 -5
- data/manual/html/cellcolor.column.html +43 -8
- data/manual/html/celltext.column.html +4 -4
- data/manual/html/center.html +5 -5
- data/manual/html/charge.html +4 -4
- data/manual/html/chargeset.html +4 -4
- data/manual/html/columnid.html +27 -15
- data/manual/html/columns.html +5 -5
- data/manual/html/complete.html +4 -4
- data/manual/html/copyright.html +4 -4
- data/manual/html/credits.html +4 -4
- data/manual/html/css/tjreport.css +14 -5
- data/manual/html/currency.html +4 -4
- data/manual/html/currencyformat.html +5 -5
- data/manual/html/dailymax.html +5 -5
- data/manual/html/dailymin.html +5 -5
- data/manual/html/dailyworkinghours.html +4 -4
- data/manual/html/date.extend.html +4 -4
- data/manual/html/date.html +5 -5
- data/manual/html/definitions.html +4 -4
- data/manual/html/depends.html +4 -4
- data/manual/html/details.html +4 -4
- data/manual/html/disabled.html +4 -4
- data/manual/html/duration.html +4 -4
- data/manual/html/efficiency.html +4 -4
- data/manual/html/effort.html +4 -4
- data/manual/html/email.html +4 -4
- data/manual/html/enabled.html +4 -4
- data/manual/html/end.column.html +4 -4
- data/manual/html/end.html +4 -4
- data/manual/html/end.limit.html +4 -4
- data/manual/html/end.report.html +5 -5
- data/manual/html/end.timesheet.html +4 -4
- data/manual/html/endcredit.html +4 -4
- data/manual/html/epilog.html +5 -5
- data/manual/html/export.html +4 -4
- data/manual/html/extend.html +4 -4
- data/manual/html/fail.html +43 -8
- data/manual/html/fdl.html +4 -4
- data/manual/html/flags.account.html +4 -4
- data/manual/html/flags.html +4 -4
- data/manual/html/flags.journalentry.html +4 -4
- data/manual/html/flags.report.html +5 -5
- data/manual/html/flags.resource.html +4 -4
- data/manual/html/flags.statussheet.html +4 -4
- data/manual/html/flags.task.html +4 -4
- data/manual/html/flags.timesheet.html +4 -4
- data/manual/html/fontcolor.column.html +43 -8
- data/manual/html/footer.html +5 -5
- data/manual/html/formats.html +5 -5
- data/manual/html/functions.html +4 -4
- data/manual/html/gapduration.html +4 -4
- data/manual/html/gaplength.html +4 -4
- data/manual/html/halign.center.html +4 -4
- data/manual/html/halign.column.html +43 -8
- data/manual/html/halign.left.html +4 -4
- data/manual/html/halign.right.html +4 -4
- data/manual/html/hasalert.html +4 -4
- data/manual/html/header.html +5 -5
- data/manual/html/headline.html +7 -7
- data/manual/html/height.html +72 -0
- data/manual/html/hideaccount.html +46 -11
- data/manual/html/hidejournalentry.html +5 -5
- data/manual/html/hidereport.html +43 -8
- data/manual/html/hideresource.html +44 -9
- data/manual/html/hidetask.html +44 -9
- data/manual/html/icalreport.html +4 -4
- data/manual/html/include.macro.html +4 -4
- data/manual/html/include.project.html +4 -4
- data/manual/html/include.properties.html +4 -4
- data/manual/html/index.html +2 -1
- data/manual/html/inherit.extend.html +4 -4
- data/manual/html/interval1.html +4 -4
- data/manual/html/interval2.html +4 -4
- data/manual/html/interval3.html +4 -4
- data/manual/html/interval4.html +4 -4
- data/manual/html/isactive.html +4 -4
- data/manual/html/ischildof.html +4 -4
- data/manual/html/isdependencyof.html +4 -4
- data/manual/html/isdutyof.html +4 -4
- data/manual/html/isfeatureof.html +4 -4
- data/manual/html/isleaf.html +4 -4
- data/manual/html/ismilestone.html +4 -4
- data/manual/html/isongoing.html +4 -4
- data/manual/html/isresource.html +4 -4
- data/manual/html/isresponsibilityof.html +4 -4
- data/manual/html/istask.html +4 -4
- data/manual/html/journalattributes.html +11 -7
- data/manual/html/journalentry.html +4 -4
- data/manual/html/journalmode.html +5 -5
- data/manual/html/leaveallowance.html +5 -5
- data/manual/html/leaves.html +5 -6
- data/manual/html/left.html +5 -5
- data/manual/html/length.html +4 -4
- data/manual/html/limits.allocate.html +4 -4
- data/manual/html/limits.html +4 -4
- data/manual/html/limits.resource.html +4 -4
- data/manual/html/limits.task.html +4 -4
- data/manual/html/listitem.column.html +4 -4
- data/manual/html/listtype.column.html +4 -4
- data/manual/html/loadunit.html +5 -5
- data/manual/html/logicalexpression.html +8 -44
- data/manual/html/logicalflagexpression.html +4 -4
- data/manual/html/macro.html +4 -4
- data/manual/html/managers.html +4 -4
- data/manual/html/mandatory.html +4 -4
- data/manual/html/maxend.html +4 -4
- data/manual/html/maximum.html +5 -5
- data/manual/html/maxstart.html +4 -4
- data/manual/html/milestone.html +4 -4
- data/manual/html/minend.html +4 -4
- data/manual/html/minimum.html +5 -5
- data/manual/html/minstart.html +4 -4
- data/manual/html/monthlymax.html +5 -5
- data/manual/html/monthlymin.html +5 -5
- data/manual/html/navbar.html +10 -4
- data/manual/html/navigator.html +4 -4
- data/manual/html/newtask.html +4 -4
- data/manual/html/nikureport.html +4 -4
- data/manual/html/note.task.html +4 -4
- data/manual/html/now.html +4 -4
- data/manual/html/numberformat.html +5 -5
- data/manual/html/onend.html +4 -4
- data/manual/html/onstart.html +4 -4
- data/manual/html/opennodes.html +5 -5
- data/manual/html/overtime.booking.html +4 -4
- data/manual/html/period.column.html +4 -4
- data/manual/html/period.limit.html +4 -4
- data/manual/html/period.report.html +5 -5
- data/manual/html/period.task.html +4 -4
- data/manual/html/persistent.html +4 -4
- data/manual/html/precedes.html +4 -4
- data/manual/html/priority.html +4 -4
- data/manual/html/priority.timesheet.html +4 -4
- data/manual/html/project.html +4 -4
- data/manual/html/projectid.html +4 -4
- data/manual/html/projectid.task.html +4 -4
- data/manual/html/projectids.html +4 -4
- data/manual/html/projection.html +5 -7
- data/manual/html/prolog.html +5 -5
- data/manual/html/properties.html +11 -5
- data/manual/html/purge.html +5 -5
- data/manual/html/rate.html +4 -4
- data/manual/html/rate.resource.html +4 -4
- data/manual/html/reference.extend.html +4 -4
- data/manual/html/remaining.html +4 -4
- data/manual/html/replace.html +4 -4
- data/manual/html/reportprefix.html +4 -4
- data/manual/html/resource.html +4 -10
- data/manual/html/resourceattributes.html +4 -4
- data/manual/html/resourceprefix.html +4 -4
- data/manual/html/resourcereport.html +28 -10
- data/manual/html/resourceroot.html +5 -5
- data/manual/html/resources.limit.html +4 -4
- data/manual/html/responsible.html +4 -4
- data/manual/html/richtext.extend.html +4 -4
- data/manual/html/right.html +5 -5
- data/manual/html/rollupaccount.html +44 -9
- data/manual/html/rollupresource.html +44 -9
- data/manual/html/rolluptask.html +44 -9
- data/manual/html/scale.column.html +4 -4
- data/manual/html/scenario.html +4 -22
- data/manual/html/scenario.ical.html +4 -4
- data/manual/html/scenarios.export.html +4 -4
- data/manual/html/scenarios.html +5 -5
- data/manual/html/scenariospecific.extend.html +4 -4
- data/manual/html/scheduled.html +4 -4
- data/manual/html/scheduling.html +4 -4
- data/manual/html/select.html +4 -4
- data/manual/html/selfcontained.html +5 -5
- data/manual/html/shift.allocate.html +4 -4
- data/manual/html/shift.html +4 -4
- data/manual/html/shift.resource.html +5 -5
- data/manual/html/shift.task.html +4 -4
- data/manual/html/shift.timesheet.html +4 -4
- data/manual/html/shifts.allocate.html +4 -4
- data/manual/html/shifts.resource.html +4 -4
- data/manual/html/shifts.task.html +4 -4
- data/manual/html/shorttimeformat.html +4 -4
- data/manual/html/sloppy.booking.html +4 -4
- data/manual/html/sloppy.projection.html +5 -5
- data/manual/html/sortaccounts.html +5 -5
- data/manual/html/sortjournalentries.html +5 -5
- data/manual/html/sortresources.html +5 -5
- data/manual/html/sorttasks.html +5 -5
- data/manual/html/start.column.html +4 -4
- data/manual/html/start.html +4 -4
- data/manual/html/start.limit.html +4 -4
- data/manual/html/start.report.html +5 -5
- data/manual/html/startcredit.html +4 -4
- data/manual/html/status.statussheet.html +4 -4
- data/manual/html/status.timesheet.html +4 -4
- data/manual/html/statussheet.html +4 -4
- data/manual/html/statussheetreport.html +4 -4
- data/manual/html/strict.projection.html +5 -5
- data/manual/html/summary.html +4 -4
- data/manual/html/supplement.html +4 -4
- data/manual/html/supplement.resource.html +4 -10
- data/manual/html/supplement.task.html +4 -28
- data/manual/html/tagfile.html +4 -4
- data/manual/html/task.html +4 -28
- data/manual/html/task.statussheet.html +4 -4
- data/manual/html/task.timesheet.html +4 -4
- data/manual/html/taskattributes.html +4 -4
- data/manual/html/taskprefix.html +4 -4
- data/manual/html/taskreport.html +28 -10
- data/manual/html/taskroot.html +5 -5
- data/manual/html/text.extend.html +4 -4
- data/manual/html/textreport.html +27 -9
- data/manual/html/timeformat.html +5 -5
- data/manual/html/timeoff.nikureport.html +4 -4
- data/manual/html/timesheet.html +4 -4
- data/manual/html/timesheetreport.html +23 -5
- data/manual/html/timezone.export.html +4 -4
- data/manual/html/timezone.html +4 -4
- data/manual/html/timezone.report.html +5 -5
- data/manual/html/timezone.shift.html +4 -4
- data/manual/html/timingresolution.html +4 -4
- data/manual/html/title.column.html +4 -4
- data/manual/html/title.html +5 -5
- data/manual/html/toc.html +207 -179
- data/manual/html/tooltip.column.html +45 -10
- data/manual/html/tracereport.html +405 -0
- data/manual/html/trackingscenario.html +6 -6
- data/manual/html/treelevel.html +4 -4
- data/manual/html/vacation.html +4 -4
- data/manual/html/vacation.resource.html +4 -4
- data/manual/html/vacation.shift.html +4 -4
- data/manual/html/warn.html +43 -8
- data/manual/html/weeklymax.html +5 -5
- data/manual/html/weeklymin.html +5 -5
- data/manual/html/weekstartsmonday.html +4 -4
- data/manual/html/weekstartssunday.html +6 -6
- data/manual/html/width.column.html +6 -6
- data/manual/html/width.html +72 -0
- data/manual/html/work.html +4 -4
- data/manual/html/workinghours.project.html +4 -4
- data/manual/html/workinghours.resource.html +4 -4
- data/manual/html/workinghours.shift.html +4 -4
- data/manual/html/yearlyworkingdays.html +4 -4
- data/spec/Color_spec.rb +60 -0
- data/spec/ProjectBroker_spec.rb +3 -2
- data/spec/StatusSheets_spec.rb +5 -4
- data/spec/TableColumnSorter_spec.rb +78 -0
- data/spec/TimeSheets_spec.rb +6 -2
- data/spec/Tj3Daemon_spec.rb +2 -2
- data/spec/TraceReport_spec.rb +117 -0
- data/taskjuggler.gemspec +1 -1
- data/test/MessageChecker.rb +3 -1
- data/test/ReferenceGenerator.rb +1 -1
- data/test/TestSuite/CSV-Reports/Leave.tjp +1 -1
- data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +3 -0
- data/test/TestSuite/CSV-Reports/refs/taskcounter.csv +9 -0
- data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +19 -16
- data/test/TestSuite/CSV-Reports/refs/weekly.csv +1 -0
- data/test/TestSuite/Export-Reports/refs/LogicalExpression.tjp +14 -2
- data/test/TestSuite/Export-Reports/refs/tutorial.tjp +98 -86
- data/test/TestSuite/Scheduler/Correct/Leaves.tjp +25 -0
- data/test/TestSuite/Syntax/Correct/Leave.tjp +1 -1
- data/test/TestSuite/Syntax/Correct/LogicalExpression.tjp +9 -1
- data/test/TestSuite/Syntax/Correct/TraceReport.tjp +10 -0
- data/test/TestSuite/Syntax/Correct/tutorial.tjp +10 -4
- data/test/test_CSV-Reports.rb +3 -3
- data/test/test_Export-Reports.rb +91 -86
- data/test/test_Journal.rb +15 -12
- data/test/test_Limits.rb +3 -3
- data/test/test_Project.rb +1 -2
- data/test/test_ProjectFileScanner.rb +1 -1
- data/test/test_PropertySet.rb +1 -1
- data/test/test_Query.rb +5 -6
- data/test/test_ReportGenerator.rb +15 -7
- data/test/test_RichText.rb +4 -3
- data/test/test_Scheduler.rb +19 -7
- data/test/test_ShiftAssignments.rb +2 -2
- data/test/test_SimpleQueryExpander.rb +29 -2
- data/test/test_Syntax.rb +14 -5
- metadata +49 -10
- 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/
|
|
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
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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.
|
|
102
|
+
@server.mount("/#{dir}", WEBrick::HTTPServlet::FileHandler, fullDir)
|
|
60
103
|
rescue
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
@@ -33,7 +33,7 @@ class TaskJuggler
|
|
|
33
33
|
def generateIntermediateFormat
|
|
34
34
|
super
|
|
35
35
|
|
|
36
|
-
# Prepare the
|
|
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('
|
|
53
|
-
(revenueAccount = @report.get('
|
|
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
|
|
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
|
-
|
|
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
|
+
|