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