pulse_meter_visualizer 0.4.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/.gitignore +19 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +8 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +40 -0
  10. data/Rakefile +72 -0
  11. data/examples/basic.ru +145 -0
  12. data/examples/basic_sensor_data.rb +96 -0
  13. data/lib/pulse_meter/visualize/app.rb +78 -0
  14. data/lib/pulse_meter/visualize/base.rb +15 -0
  15. data/lib/pulse_meter/visualize/coffee/application.coffee +40 -0
  16. data/lib/pulse_meter/visualize/coffee/collections/page_info_list.coffee +17 -0
  17. data/lib/pulse_meter/visualize/coffee/collections/sensor_info_list.coffee +4 -0
  18. data/lib/pulse_meter/visualize/coffee/collections/widget_list.coffee +14 -0
  19. data/lib/pulse_meter/visualize/coffee/extensions.coffee +26 -0
  20. data/lib/pulse_meter/visualize/coffee/models/dinamic_widget.coffee +34 -0
  21. data/lib/pulse_meter/visualize/coffee/models/page_info.coffee +2 -0
  22. data/lib/pulse_meter/visualize/coffee/models/sensor_info.coffee +2 -0
  23. data/lib/pulse_meter/visualize/coffee/models/widget.coffee +54 -0
  24. data/lib/pulse_meter/visualize/coffee/presenters/area.coffee +2 -0
  25. data/lib/pulse_meter/visualize/coffee/presenters/gauge.coffee +11 -0
  26. data/lib/pulse_meter/visualize/coffee/presenters/line.coffee +2 -0
  27. data/lib/pulse_meter/visualize/coffee/presenters/pie.coffee +20 -0
  28. data/lib/pulse_meter/visualize/coffee/presenters/series.coffee +44 -0
  29. data/lib/pulse_meter/visualize/coffee/presenters/table.coffee +10 -0
  30. data/lib/pulse_meter/visualize/coffee/presenters/timeline.coffee +13 -0
  31. data/lib/pulse_meter/visualize/coffee/presenters/widget.coffee +65 -0
  32. data/lib/pulse_meter/visualize/coffee/router.coffee +21 -0
  33. data/lib/pulse_meter/visualize/coffee/views/dynamic_chart.coffee +91 -0
  34. data/lib/pulse_meter/visualize/coffee/views/dynamic_widget.coffee +58 -0
  35. data/lib/pulse_meter/visualize/coffee/views/page_title.coffee +17 -0
  36. data/lib/pulse_meter/visualize/coffee/views/page_titles.coffee +15 -0
  37. data/lib/pulse_meter/visualize/coffee/views/sensor_info_list.coffee +19 -0
  38. data/lib/pulse_meter/visualize/coffee/views/widget.coffee +99 -0
  39. data/lib/pulse_meter/visualize/coffee/views/widget_chart.coffee +13 -0
  40. data/lib/pulse_meter/visualize/coffee/views/widget_list.coffee +15 -0
  41. data/lib/pulse_meter/visualize/dsl/base.rb +131 -0
  42. data/lib/pulse_meter/visualize/dsl/errors.rb +40 -0
  43. data/lib/pulse_meter/visualize/dsl/layout.rb +27 -0
  44. data/lib/pulse_meter/visualize/dsl/page.rb +33 -0
  45. data/lib/pulse_meter/visualize/dsl/sensor.rb +20 -0
  46. data/lib/pulse_meter/visualize/dsl/widget.rb +37 -0
  47. data/lib/pulse_meter/visualize/dsl/widgets/area.rb +20 -0
  48. data/lib/pulse_meter/visualize/dsl/widgets/gauge.rb +12 -0
  49. data/lib/pulse_meter/visualize/dsl/widgets/line.rb +21 -0
  50. data/lib/pulse_meter/visualize/dsl/widgets/pie.rb +16 -0
  51. data/lib/pulse_meter/visualize/dsl/widgets/table.rb +19 -0
  52. data/lib/pulse_meter/visualize/layout.rb +79 -0
  53. data/lib/pulse_meter/visualize/page.rb +25 -0
  54. data/lib/pulse_meter/visualize/public/css/application.css +56 -0
  55. data/lib/pulse_meter/visualize/public/css/bootstrap.css +4883 -0
  56. data/lib/pulse_meter/visualize/public/css/bootstrap.min.css +729 -0
  57. data/lib/pulse_meter/visualize/public/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  58. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  59. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  60. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  61. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  62. data/lib/pulse_meter/visualize/public/css/images/ui-bg_glass_75_ffffff_1x400.png +0 -0
  63. data/lib/pulse_meter/visualize/public/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  64. data/lib/pulse_meter/visualize/public/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +0 -0
  65. data/lib/pulse_meter/visualize/public/css/images/ui-icons_222222_256x240.png +0 -0
  66. data/lib/pulse_meter/visualize/public/css/images/ui-icons_2e83ff_256x240.png +0 -0
  67. data/lib/pulse_meter/visualize/public/css/images/ui-icons_454545_256x240.png +0 -0
  68. data/lib/pulse_meter/visualize/public/css/images/ui-icons_888888_256x240.png +0 -0
  69. data/lib/pulse_meter/visualize/public/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  70. data/lib/pulse_meter/visualize/public/css/images/ui-icons_f6cf3b_256x240.png +0 -0
  71. data/lib/pulse_meter/visualize/public/css/jquery-ui-1.8.16.bootstrap.css +1320 -0
  72. data/lib/pulse_meter/visualize/public/favicon.ico +208 -0
  73. data/lib/pulse_meter/visualize/public/img/glyphicons-halflings-white.png +0 -0
  74. data/lib/pulse_meter/visualize/public/img/glyphicons-halflings.png +0 -0
  75. data/lib/pulse_meter/visualize/public/js/application.js +973 -0
  76. data/lib/pulse_meter/visualize/public/js/backbone-min.js +38 -0
  77. data/lib/pulse_meter/visualize/public/js/bootstrap.js +1835 -0
  78. data/lib/pulse_meter/visualize/public/js/jquery-1.7.2.min.js +4 -0
  79. data/lib/pulse_meter/visualize/public/js/jquery-ui-1.8.16.bootstrap.min.js +791 -0
  80. data/lib/pulse_meter/visualize/public/js/jquery-ui-1.8.23.custom.min.js +21 -0
  81. data/lib/pulse_meter/visualize/public/js/jquery-ui-timepicker-addon.js +1687 -0
  82. data/lib/pulse_meter/visualize/public/js/json2.js +487 -0
  83. data/lib/pulse_meter/visualize/public/js/underscore-min.js +32 -0
  84. data/lib/pulse_meter/visualize/sensor.rb +63 -0
  85. data/lib/pulse_meter/visualize/series_extractor.rb +107 -0
  86. data/lib/pulse_meter/visualize/views/main.haml +30 -0
  87. data/lib/pulse_meter/visualize/views/sensors.haml +76 -0
  88. data/lib/pulse_meter/visualize/views/widgets/area.haml +53 -0
  89. data/lib/pulse_meter/visualize/views/widgets/extend_options.haml +11 -0
  90. data/lib/pulse_meter/visualize/views/widgets/gauge.haml +13 -0
  91. data/lib/pulse_meter/visualize/views/widgets/line.haml +54 -0
  92. data/lib/pulse_meter/visualize/views/widgets/pie.haml +13 -0
  93. data/lib/pulse_meter/visualize/views/widgets/table.haml +45 -0
  94. data/lib/pulse_meter/visualize/widget.rb +38 -0
  95. data/lib/pulse_meter/visualize/widgets/gauge.rb +47 -0
  96. data/lib/pulse_meter/visualize/widgets/pie.rb +36 -0
  97. data/lib/pulse_meter/visualize/widgets/timeline.rb +114 -0
  98. data/lib/pulse_meter/visualizer.rb +38 -0
  99. data/lib/pulse_meter_visualizer.rb +2 -0
  100. data/pulse_meter_visualizer.gemspec +41 -0
  101. data/spec/pulse_meter/visualize/app_spec.rb +27 -0
  102. data/spec/pulse_meter/visualize/dsl/layout_spec.rb +64 -0
  103. data/spec/pulse_meter/visualize/dsl/page_spec.rb +62 -0
  104. data/spec/pulse_meter/visualize/dsl/sensor_spec.rb +30 -0
  105. data/spec/pulse_meter/visualize/dsl/widget_spec.rb +6 -0
  106. data/spec/pulse_meter/visualize/dsl/widgets/area_spec.rb +44 -0
  107. data/spec/pulse_meter/visualize/dsl/widgets/gauge_spec.rb +22 -0
  108. data/spec/pulse_meter/visualize/dsl/widgets/line_spec.rb +44 -0
  109. data/spec/pulse_meter/visualize/dsl/widgets/pie_spec.rb +35 -0
  110. data/spec/pulse_meter/visualize/dsl/widgets/table_spec.rb +36 -0
  111. data/spec/pulse_meter/visualize/layout_spec.rb +54 -0
  112. data/spec/pulse_meter/visualize/page_spec.rb +153 -0
  113. data/spec/pulse_meter/visualize/sensor_spec.rb +120 -0
  114. data/spec/pulse_meter/visualize/series_extractor_spec.rb +80 -0
  115. data/spec/pulse_meter/visualize/widgets/area_spec.rb +6 -0
  116. data/spec/pulse_meter/visualize/widgets/gauge_spec.rb +63 -0
  117. data/spec/pulse_meter/visualize/widgets/line_spec.rb +6 -0
  118. data/spec/pulse_meter/visualize/widgets/pie_spec.rb +73 -0
  119. data/spec/pulse_meter/visualize/widgets/table_spec.rb +6 -0
  120. data/spec/pulse_meter/visualizer_spec.rb +42 -0
  121. data/spec/shared_examples/dsl_widget.rb +106 -0
  122. data/spec/shared_examples/widget.rb +97 -0
  123. data/spec/spec_helper.rb +36 -0
  124. metadata +518 -0
@@ -0,0 +1,58 @@
1
+ DynamicWidgetView = Backbone.View.extend {
2
+ tagName: 'div'
3
+
4
+ initialize: (options) ->
5
+ @pageInfos = options['pageInfos']
6
+ @sensorInfo = new SensorInfoList
7
+
8
+ @sensorListView = new SensorInfoListView(@sensorInfo)
9
+ @chartView = new DynamicChartView {pageInfos: @pageInfos}
10
+
11
+ @$el.html(@template()())
12
+
13
+ @$el.find('#sensor-list-area').append(@sensorListView.el)
14
+
15
+ @chartView.render()
16
+ @$el.find('#dynamic-plotarea').append(@chartView.el)
17
+
18
+ events: {
19
+ "click #sensor-controls #refresh": 'refresh'
20
+ "click #sensor-controls #draw": 'drawChart'
21
+ }
22
+
23
+ template: ->
24
+ _.template($("#dynamic-widget").html())
25
+
26
+ errorTemplate: -> _.template($("#dynamic-widget-error").html())
27
+
28
+ error: (error)->
29
+ @$el.find('#errors').append(@errorTemplate()(error: error))
30
+
31
+ refresh: ->
32
+ @sensorInfo.fetch()
33
+
34
+ intervalsEqual: (sensors) ->
35
+ interval = sensors[0].get('interval')
36
+ badIntervals = _.filter(sensors, (s) ->
37
+ s.get('interval') != interval
38
+ )
39
+ badIntervals.length == 0
40
+
41
+ drawChart: ->
42
+ selectedSensors = @sensorListView.selectedSensors()
43
+ return unless selectedSensors.length > 0
44
+
45
+ unless @intervalsEqual(selectedSensors)
46
+ @error('Selected sensors have different intervals')
47
+ return
48
+
49
+ type = @$el.find('#chart-type').val()
50
+ @chartView.draw(selectedSensors, type)
51
+
52
+ render: (container) ->
53
+ container.empty()
54
+ container.append(@$el)
55
+ @sensorInfo.fetch()
56
+ @chartView.update()
57
+
58
+ }
@@ -0,0 +1,17 @@
1
+ PageTitleView = Backbone.View.extend {
2
+ tagName: 'li'
3
+
4
+ template: _.template('<a href="#/pages/<%= id %>"><%= title %></a>')
5
+
6
+ initialize: ->
7
+ @model.bind 'change', @render, this
8
+ @model.bind 'destroy', @remove, this
9
+
10
+ render: ->
11
+ @$el.html @template(@model.toJSON())
12
+ if @model.get('selected')
13
+ @$el.addClass('active')
14
+ else
15
+ @$el.removeClass('active')
16
+ }
17
+
@@ -0,0 +1,15 @@
1
+ PageTitlesView = Backbone.View.extend {
2
+ initialize: (@pageInfos) ->
3
+ @pageInfos.bind 'reset', @render, this
4
+
5
+ addOne: (pageInfo) ->
6
+ view = new PageTitleView {
7
+ model: pageInfo
8
+ }
9
+ view.render()
10
+ $('#page-titles').append(view.el)
11
+
12
+ render: ->
13
+ $('#page-titles').empty()
14
+ @pageInfos.each(@addOne)
15
+ }
@@ -0,0 +1,19 @@
1
+ SensorInfoListView = Backbone.View.extend {
2
+ tagName: 'div'
3
+
4
+ template: ->
5
+ _.template($("#sensor-list").html())
6
+
7
+ initialize: (sensorInfo) ->
8
+ @sensorInfo = sensorInfo
9
+ @sensorInfo.bind 'reset', @render, this
10
+
11
+ render: ->
12
+ @$el.html @template()({sensors: @sensorInfo.toJSON()})
13
+
14
+ selectedSensors: ->
15
+ checked = _.filter @$el.find('.sensor-box'), (el) -> $(el).is(':checked')
16
+ ids = {}
17
+ _.each checked, (box) -> ids[box.id] = true
18
+ selected = @sensorInfo.filter (sensor) -> ids[sensor.id]
19
+ }
@@ -0,0 +1,99 @@
1
+ WidgetView = Backbone.View.extend {
2
+ tagName: 'div'
3
+
4
+ template: (args) ->
5
+ _.template($(".widget-template[data-widget-type=\"#{@model.get('type')}\"]").html())(args)
6
+
7
+ initialize: (options) ->
8
+ @pageInfos = options['pageInfos']
9
+ @model.bind('destroy', @remove, this)
10
+ @model.bind('redraw', @updateChart, this)
11
+
12
+ events: {
13
+ "click #refresh": 'refresh'
14
+ "click #need-refresh": 'setRefresh'
15
+ "click #extend-timespan": 'extendTimespan'
16
+ "click #reset-timespan": 'resetTimespan'
17
+ "change #start-time input": 'maybeEnableStopTime'
18
+ "click #set-interval": 'setTimelineInterval'
19
+ }
20
+
21
+ refresh: ->
22
+ @model.forceUpdate()
23
+
24
+ setRefresh: ->
25
+ needRefresh = @$el.find('#need-refresh').is(":checked")
26
+ @model.setRefresh(needRefresh)
27
+ true
28
+
29
+ extendTimespan: ->
30
+ select = @$el.find("#extend-timespan-val")
31
+ val = select.first().val()
32
+ @model.increaseTimespan(parseInt(val))
33
+
34
+ setTimelineInterval: ->
35
+ start = @unixtimeFromDatepicker("#start-time input")
36
+ end = @unixtimeFromDatepicker("#end-time input")
37
+ @model.setStartTime(start)
38
+ @model.setEndTime(end)
39
+
40
+ maybeEnableStopTime: ->
41
+ date = @dateFromDatepicker("#start-time input")
42
+ disabled = if date then false else true
43
+ @$el.find("#end-time input").prop("disabled", disabled)
44
+
45
+ resetTimespan: ->
46
+ @model.resetTimespan()
47
+
48
+ renderChart: ->
49
+ @chartView.render()
50
+
51
+ updateChart: ->
52
+ @chartView.updateData(@cutoffMin(), @cutoffMax())
53
+
54
+ setIds: ->
55
+ @$el.find('#configure-button').prop('href', "#configure-#{@model.id}")
56
+ @$el.find('#configure').attr('id', "configure-#{@model.id}")
57
+ @$el.find('#start-time input').attr('id', "start-time-#{@model.id}")
58
+ @$el.find('#end-time input').attr('id', "end-time-#{@model.id}")
59
+
60
+ render: ->
61
+ @$el.html @template(@model.toJSON())
62
+ @setIds()
63
+ @chartView = new WidgetChartView {
64
+ pageInfos: @pageInfos
65
+ model: @model
66
+ }
67
+ @$el.find("#plotarea").append(@chartView.el)
68
+ @$el.addClass("span#{@model.get('width')}")
69
+ @initDatePickers()
70
+
71
+ initDatePickers: ->
72
+ @$el.find(".datepicker").each (i) ->
73
+ $(this).datetimepicker
74
+ showOtherMonths: true
75
+ selectOtherMonths: true
76
+ @$el.find("#end-time input").prop("disabled", true)
77
+
78
+ cutoffMin: ->
79
+ val = parseFloat(@controlValue('#cutoff-min'))
80
+ if _.isNaN(val) then null else val
81
+
82
+ cutoffMax: ->
83
+ val = parseFloat(@controlValue('#cutoff-max'))
84
+ if _.isNaN(val) then null else val
85
+
86
+ controlValue: (id) ->
87
+ val = @$el.find(id).first().val()
88
+
89
+ dateFromDatepicker: (id) ->
90
+ @$el.find(id).datetimepicker("getDate")
91
+
92
+ unixtimeFromDatepicker: (id) ->
93
+ date = @dateFromDatepicker(id)
94
+ if date
95
+ date.getTime() / 1000
96
+ else
97
+ null
98
+
99
+ }
@@ -0,0 +1,13 @@
1
+ WidgetChartView = Backbone.View.extend {
2
+ tagName: 'div'
3
+
4
+ initialize: (options) ->
5
+ @pageInfos = options['pageInfos']
6
+ @model.bind 'destroy', @remove, this
7
+
8
+ updateData: (min, max) ->
9
+ @presenter.draw(min, max)
10
+
11
+ render: ->
12
+ @presenter = WidgetPresenter.create(@pageInfos, @model, @el)
13
+ }
@@ -0,0 +1,15 @@
1
+ WidgetListView = Backbone.View.extend {
2
+ initialize: (options) ->
3
+ @widgetList = options['widgetList']
4
+ @pageInfos = options['pageInfos']
5
+ @widgetList.bind 'reset', @render, this
6
+
7
+ render: ->
8
+ container = $('#widgets')
9
+ container.empty()
10
+ @widgetList.each (w) =>
11
+ view = new WidgetView { pageInfos: @pageInfos, model: w }
12
+ view.render()
13
+ container.append(view.el)
14
+ view.renderChart()
15
+ }
@@ -0,0 +1,131 @@
1
+ module PulseMeter
2
+ module Visualize
3
+ module DSL
4
+ class DArray < Array; end
5
+ class BadDataClass < PulseMeter::Visualize::DSL::Error; end
6
+ class Base
7
+ include PulseMeter::Mixins::Utils
8
+
9
+ def initialize
10
+ @opts = {}
11
+ end
12
+
13
+ def process_args(args)
14
+ args.each_pair do |k, v|
15
+ send(k, v)
16
+ end
17
+ end
18
+
19
+ class << self
20
+
21
+ def deprecated_setter(name)
22
+ define_method(name) do |*args|
23
+ STDERR.puts "DEPRECATION: #{name} DSL helper does not take any effect anymore."
24
+ end
25
+ end
26
+
27
+ def setter(name, &block)
28
+ define_method(name) do |val|
29
+ block.call(val) if block
30
+ @opts[name] = val
31
+ end
32
+ end
33
+
34
+ def bool_setter(name)
35
+ define_method(name) do |val|
36
+ @opts[name] = !!val
37
+ end
38
+ end
39
+
40
+ def string_setter(name, &block)
41
+ define_method(name) do |val|
42
+ val = val.to_s
43
+ block.call(val) if block
44
+ @opts[name] = val
45
+ end
46
+ end
47
+
48
+ def int_setter(name, &block)
49
+ define_method(name) do |val|
50
+ val = val.to_i
51
+ block.call(val) if block
52
+ @opts[name] = val
53
+ end
54
+ end
55
+
56
+ def hash_extender(name, &block)
57
+ define_method(name) do |val|
58
+ @opts[name] ||= {}
59
+ @opts[name].merge!(val)
60
+ end
61
+ end
62
+
63
+ def array_extender(name, &block)
64
+ define_method(name) do |val|
65
+ @opts[name] ||=[]
66
+ block.call(val) if block
67
+ @opts[name] << val
68
+ end
69
+ end
70
+
71
+ def dsl_setter(name, klass)
72
+ define_method(name) do |*args, &block|
73
+ @opts[name] = create_dsl_obj(args, klass, block)
74
+ end
75
+ end
76
+
77
+ def dsl_array_extender(collection_name, name, klass)
78
+ define_method(name) do |*args, &block|
79
+ @opts[collection_name] ||= DArray.new
80
+ @opts[collection_name] << create_dsl_obj(args, klass, block)
81
+ end
82
+ end
83
+
84
+ def data_class
85
+ @data_class || PulseMeter::Visualize::Base
86
+ end
87
+
88
+ def data_class=(klass)
89
+ raise BadDataClass unless klass.is_a?(Class) && klass <= PulseMeter::Visualize::Base
90
+ @data_class = klass
91
+ end
92
+
93
+ end
94
+
95
+ def create_dsl_obj(args, klass, block)
96
+ params, options = extract_params(args)
97
+ dsl_obj = klass.new(*params)
98
+ dsl_obj.process_args(options)
99
+ block.call(dsl_obj) if block
100
+ dsl_obj
101
+ end
102
+
103
+ def extract_params(args)
104
+ opts = if args.last.is_a?(Hash)
105
+ args.pop
106
+ else
107
+ {}
108
+ end
109
+ [args, opts]
110
+ end
111
+
112
+ def to_data
113
+ klass = self.class.data_class
114
+ args = @opts.each_with_object({}) do |(k, v), acc|
115
+ acc[k] = case v
116
+ when PulseMeter::Visualize::DSL::Base
117
+ v.to_data
118
+ when DArray
119
+ v.map(&:to_data)
120
+ else
121
+ v
122
+ end
123
+ end
124
+ klass.new(args)
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+ end
131
+
@@ -0,0 +1,40 @@
1
+ module PulseMeter
2
+ module Visualize
3
+ module DSL
4
+ class Error < StandardError; end
5
+
6
+ class NoWidgets < Error; end
7
+
8
+ class BadSensorName < Error
9
+ def initialize(name)
10
+ super("Bad sensor name: `#{name}'")
11
+ end
12
+ end
13
+
14
+ class BadWidgetName < Error
15
+ def initialize(name)
16
+ super("Bad widget name: `#{name}'")
17
+ end
18
+ end
19
+
20
+ class BadWidgetWidth < Error
21
+ def initialize(width)
22
+ super("Bad widget width: `#{width}'")
23
+ end
24
+ end
25
+
26
+ class BadWidgetRedrawInterval < Error
27
+ def initialize(redraw_interval)
28
+ super("Bad widget redraw_interval: `#{redraw_interval}'")
29
+ end
30
+ end
31
+
32
+ class BadWidgetTimeSpan < Error
33
+ def initialize(timespan)
34
+ super("Bad widget timespan: `#{timespan}'")
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,27 @@
1
+ module PulseMeter
2
+ module Visualize
3
+ module DSL
4
+ class Layout < Base
5
+ DEFAULT_TITLE = "Pulse Meter"
6
+
7
+ self.data_class = PulseMeter::Visualize::Layout
8
+
9
+ def initialize
10
+ super()
11
+ self.title(DEFAULT_TITLE)
12
+ self.use_utc(false)
13
+ end
14
+
15
+ string_setter :title
16
+ bool_setter :use_utc
17
+ hash_extender :gchart_options
18
+
19
+ deprecated_setter :outlier_color
20
+ deprecated_setter :highchart_options
21
+
22
+ dsl_array_extender :pages, :page, PulseMeter::Visualize::DSL::Page
23
+ end
24
+ end
25
+ end
26
+ end
27
+