ela 3.2.0 → 3.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 931596436e4c496baf811835178c70761fe4e7b2
4
- data.tar.gz: b548f627ac5a723a0f1ae3ee43afda8e525b5825
2
+ SHA256:
3
+ metadata.gz: 0a2f4ca90c26f32467f5e7d3821d25a853273455ef4c9b7a254064bf570af599
4
+ data.tar.gz: 45df5d9aa6cbd546d0c373b22d1eee98fcbc41faf5ab058b6ea55e0e79e99ce6
5
5
  SHA512:
6
- metadata.gz: 827290ddd153f81e11049c5096feba7ac9fbeeeff00d034cd047a337cbd73c8a6c1c2ef00ec96b6590655e196e056cea2706c4a88d4fcc09747534c779d83997
7
- data.tar.gz: ce6769b30ce2a1acf84265feaec2c703714eac5140f9698179466ab2cfce4179f4b43534a6c28ed83f39044e162a0e31e09059cb11b7b855450aaa2b7cba5c1a
6
+ metadata.gz: fbb3da9beeeb1451c1dca8f9ac9209c3c65ea4ed37688233b21e5c232b2eaa8d524aa7d1abf42b493781ad42d167e54f76c3aad38be78b538ebc4570738e565d
7
+ data.tar.gz: 4f640623e209d40d9e1f42f22698dccfab2b8b212a0ecda8589da3e13fe2d43851d4982841e26174f09ba1e572c6a150317750503cada4e25fe43c197f0ab7dc
@@ -10,11 +10,12 @@ class ELA.Models.BaseApp extends ELA.Models.BaseAppModel
10
10
  @handlePath()
11
11
 
12
12
  handlePath: =>
13
- path = @get('path')?[0]
14
- if path is 'help'
15
- @set showHelp: true
16
- else
17
- @set showHelp: false
13
+ path = @get('path')
14
+
15
+ @set(showHelp: _.last(path) is 'help')
16
+
17
+ first = _.first(path)
18
+ @set(layout: first) if first? and first isnt 'help'
18
19
 
19
20
  helpTextName: =>
20
21
  @path.replace('/', '_')
@@ -0,0 +1,11 @@
1
+ ELA.Models ?= {}
2
+ class ELA.Models.CanvasParams extends Backbone.Model
3
+ serialize: -> {}
4
+ deserialize: ->
5
+
6
+ constructor: (attributes, options) ->
7
+ @app = attributes.app
8
+ delete attributes.app
9
+ # Special behavior: initial attributes overwrite defaults
10
+ @defaults = _.defaults(_.clone(@defaults), attributes)
11
+ super({}, options)
@@ -0,0 +1,167 @@
1
+ ELA.Models ?= {}
2
+ class ELA.Models.GraphParams extends ELA.Models.CanvasParams
3
+ defaults:
4
+ xScale: 1.1 # display 10% more than the configured xRange
5
+ yScale: 1.1 # display 10% more than the configured yRange
6
+ xOrigin: 30
7
+ yOrigin: 0
8
+ xOriginAnchor: 'left'
9
+ yOriginAnchor: 'bottom'
10
+ xOriginRatio: null
11
+ yOriginRatio: null
12
+ yOffset: 0
13
+ debug: {}
14
+ xLabelPosition: 'top'
15
+ yLabelPosition: 'right'
16
+ xPanLocked: false
17
+ yPanLocked: false
18
+ xPinchLocked: false
19
+ yPinchLocked: false
20
+ scaleLink: false
21
+ xAxis: { y: 0 }
22
+ yAxis: { x: 0 }
23
+
24
+ constructor: ->
25
+ super
26
+
27
+ if @defaults.xOriginRatio? and @defaults.xOriginAnchor is 'right'
28
+ @defaults.xOriginRatio = 1 - @defaults.xOriginRatio
29
+ @defaults.xOriginAnchor = 'left'
30
+
31
+ if @defaults.yOriginRatio? and @defaults.yOriginAnchor is 'bottom'
32
+ @defaults.yOriginRatio = 1 - @defaults.yOriginRatio
33
+ @defaults.yOriginAnchor = 'top'
34
+
35
+ initialize: ->
36
+ # Place origin when width and height are being set for the first
37
+ # time
38
+ @once('change:width change:height', @reset)
39
+ @on('change:width change:height', @resize)
40
+
41
+ # @calculateRanges changes params.xyRange
42
+ @on('change:xScale', @calculateRangeX)
43
+ @on('change:yScale', @calculateRangeY)
44
+
45
+ @calculateRanges()
46
+ @bindCalculatorEvents()
47
+ @listenTo(@app.curves, 'change:selected', @bindCalculatorEvents)
48
+ @listenTo @app, 'change:calculators', ->
49
+ @calculateRanges()
50
+ @bindCalculatorEvents()
51
+
52
+ bindCalculatorEvents: ->
53
+ # Remove current callbacks, add new ones for each curve
54
+ @stopListening(@app.previous('calculators'))
55
+
56
+ for calc in @app.get('calculators')
57
+ filteredCurves = @filteredCurves()
58
+ for curve in filteredCurves
59
+ @listenTo calc, "change:#{curve.get('function')}", @requestRepaint
60
+
61
+ @listenTo(calc, 'change:maxX change:xRange', @calculateRangeX)
62
+ @listenTo(calc, 'change:maxY change:yRange', @calculateRangeY)
63
+ # TODO: Add dynamic dependencies to calculator, so that we can
64
+ # actually only listen to maxY changes and recalculate ranges.
65
+ oldestCurve = filteredCurves[0]
66
+ if oldestCurve?
67
+ @listenTo(calc,
68
+ "change:#{oldestCurve.get('function')}"
69
+ @calculateRanges
70
+ )
71
+
72
+ reset: ->
73
+ if (xOriginRatio = @get('xOriginRatio'))?
74
+ @set(xOrigin: @get('width') * xOriginRatio)
75
+ else if @get('xOriginAnchor') is 'left'
76
+ @set(xOrigin: @defaults.xOrigin)
77
+ else
78
+ @set(xOrigin: @get('width') - @defaults.xOrigin)
79
+
80
+ if (yOriginRatio = @get('yOriginRatio'))?
81
+ @set(yOrigin: @get('height') * yOriginRatio)
82
+ else if @defaults.yOriginAnchor is 'bottom'
83
+ @set(yOrigin: @get('height') - @defaults.yOrigin)
84
+ else
85
+ @set(yOrigin: @defaults.yOrigin)
86
+
87
+ if @get('scaleLink')
88
+ # axis with less pixels per unit length should define scale
89
+ if @get('width') / @maxRangeX(null, 1) < @params.get('height') / @maxRangeY(null, 1)
90
+ @set(yScale: @linkedYScale(xScale))
91
+ else
92
+ @set(xScale: @linkedXScale(yScale))
93
+
94
+ resize: ->
95
+ previous = @previousAttributes()
96
+ if previous.width? and previous.height?
97
+ previousXOrigin = if previous.xOrigin?
98
+ previous.xOrigin
99
+ else
100
+ @get('xOrigin')
101
+ previousYOrigin = if previous.yOrigin?
102
+ previous.yOrigin
103
+ else
104
+ @get('yOrigin')
105
+ @set
106
+ xOrigin: previousXOrigin * @get('width') / previous.width
107
+ yOrigin: previousYOrigin * @get('height') / previous.height
108
+
109
+ switch @get('scaleLink')
110
+ when 'x'
111
+ @set xScale: @linkedXScale()
112
+ when 'y'
113
+ @set yScale: @linkedYScale()
114
+ else
115
+ @reset()
116
+
117
+ calculateRanges: ->
118
+ @set
119
+ xRange: @maxRangeX()
120
+ yRange: @maxRangeY()
121
+
122
+ maxRangeX: (func, xScale = @get('xScale')) ->
123
+ func = @get('axisLabelingForCurve')?.get('function') unless func?
124
+ ranges = (calc.xRange(func) for calc in @app.get('calculators'))
125
+ Math.max.apply(Math, _.compact(ranges)) * xScale
126
+
127
+ maxRangeY: (func, yScale = @get('yScale')) ->
128
+ func = @get('axisLabelingForCurve')?.get('function') unless func?
129
+ ranges = (calc.yRange(func) for calc in @app.get('calculators'))
130
+ Math.max.apply(Math, _.compact(ranges)) * yScale
131
+
132
+ linkedXScale: (yScale) ->
133
+ # calculate xScale to fulfill the following equation:
134
+ # (width / maxRangeX) / xScale = height / maxRangeY
135
+ (@get('width') / @maxRangeX(null, 1)) /
136
+ (@get('height') / @maxRangeY(null, yScale))
137
+
138
+ linkedYScale: (xScale) ->
139
+ # calculate yScale to fulfill the following equation:
140
+ # (height / maxRangeY) / yScale = width / maxRangeX
141
+ (@get('height') / @maxRangeY(null, 1)) /
142
+ (@get('width') / @maxRangeX(null, xScale))
143
+
144
+ filteredCurves: ->
145
+ curves = @app.curves.history
146
+ if graphCurves = @get('curves')
147
+ _.filter curves, (curve) ->
148
+ graphCurves.indexOf(curve.get('function')) >= 0
149
+ else
150
+ curves
151
+
152
+ sortedCurves: ->
153
+ _.sortBy @filteredCurves(), (curve) ->
154
+ curve.get('zIndex')
155
+
156
+ serialize: ->
157
+ xScale: @get('xScale')
158
+ yScale: @get('yScale')
159
+ xOriginRatio: @get('xOrigin') / @get('width')
160
+ yOriginRatio: @get('yOrigin') / @get('height')
161
+
162
+ deserialize: (attributes) ->
163
+ @set
164
+ xScale: attributes.xScale
165
+ yScale: attributes.yScale
166
+ xOrigin: @get('width') * attributes.xOriginRatio
167
+ yOrigin: @get('height') * attributes.yOriginRatio
@@ -1,6 +1,7 @@
1
1
  ELA.Views ?= {}
2
2
  class ELA.Views.BaseApp extends Backbone.Poised.View
3
3
  template: JST['general/app']
4
+ headlineTemplate: JST['general/app_headline']
4
5
 
5
6
  id: -> @model.name.toDash()
6
7
  tagName: 'section'
@@ -22,11 +23,12 @@ class ELA.Views.BaseApp extends Backbone.Poised.View
22
23
  'tap header .share-form button': 'copyShareLink'
23
24
  'submit header .share-form form': (e) -> e.preventDefault()
24
25
  'tap header *[data-toggle-aside]': 'setCurrentAside'
25
- 'tap header .poised.subapps.select': 'viewSubappOptions'
26
- 'tap header .poised.subapps.select .option': 'openSubapp'
26
+ 'tap header .poised.subviews.select': 'viewSubviewOptions'
27
+ 'tap header .poised.subviews.select .option.subapp': 'openSubapp'
28
+ 'tap header .poised.subviews.select .option.layout': 'openLayout'
27
29
  'tap header .context.icon': 'toggleContextMenu'
28
30
  'tap article.graph:has(~ aside.active)': 'hideAsides'
29
- 'tap section:has(.subapps.select.view)': 'hideSubappOptions'
31
+ 'tap section:has(.subviews.select.view)': 'hideSubappOptions'
30
32
  'tap section:has(.menu.view)': 'hideMenus'
31
33
 
32
34
  hammerjs: true
@@ -43,13 +45,28 @@ class ELA.Views.BaseApp extends Backbone.Poised.View
43
45
  for aside in @asides
44
46
  aside.link ?= 'icon'
45
47
 
48
+ $(window).on('resize', @renderHeadlineHtml)
49
+ $(window).on('resize', @checkLayout)
50
+ @checkLayout()
51
+
46
52
  @model.displayParams ?= {}
47
53
 
48
- viewSubappOptions: =>
49
- @$('.subapps.select').toggleClass('view')
54
+ remove: ->
55
+ super
56
+ $(window).off('resize', @renderHeadlineHtml)
57
+ $(window).off('resize', @checkLayout)
58
+
59
+ checkLayout: =>
60
+ availableLayouts = @availableLayouts()
61
+ if availableLayouts.indexOf(@model.get('layout')) < 0
62
+ ELA.router.navigate "app/#{@model.path}/#{availableLayouts[0]}",
63
+ trigger: true
50
64
 
51
- hideSubappOptions: =>
52
- @$('.subapps.select').removeClass('view')
65
+ viewSubviewOptions: =>
66
+ @$('.subviews.select').toggleClass('view')
67
+
68
+ hideSubviewOptions: =>
69
+ @$('.subviews.select').removeClass('view')
53
70
 
54
71
  toggleContextMenu: =>
55
72
  @$('.menu:has(.icon.context)').toggleClass('view')
@@ -65,6 +82,11 @@ class ELA.Views.BaseApp extends Backbone.Poised.View
65
82
  $target = $(e.target)
66
83
  ELA.router.navigate("app/#{$target.data('path')}", trigger: true)
67
84
 
85
+ openLayout: (e) =>
86
+ $target = $(e.target)
87
+ ELA.router.navigate "app/#{@model.path}/#{$target.data('path')}",
88
+ trigger: true
89
+
68
90
  showHelp: =>
69
91
  ELA.router.navigate("app/#{@model.path}/help", trigger: true)
70
92
 
@@ -153,20 +175,41 @@ class ELA.Views.BaseApp extends Backbone.Poised.View
153
175
  @subviews.help?.setActive(showHelp)
154
176
  @$app.toggleClass('active', not showHelp)
155
177
 
156
- render: =>
157
- @$el.html @template
178
+ availableLayouts: ->
179
+ names = []
180
+ for name, layout of @layouts
181
+ if layout.minWidth
182
+ continue if layout.minWidth > $(window).width()
183
+ if layout.minHeight
184
+ continue if layout.minHeight > $(window).height()
185
+ names.push(name)
186
+ names
187
+
188
+ headlineHtml: ->
189
+ @headlineTemplate
158
190
  name: @model.name
159
191
  path: @model.path
192
+ relatedApps: @relatedApps()
193
+ availableLayouts: @availableLayouts()
194
+
195
+ renderHeadlineHtml: =>
196
+ @$('header h2').html(@headlineHtml())
197
+
198
+ render: =>
199
+ @$el.html @template
160
200
  hasHelpText: @model.hasHelpText
161
201
  iconAsideNames: @iconAsideNames()
162
202
  contextAsides: @contextAsides()
163
- relatedApps: @relatedApps()
203
+ views: view.name for view in @views
164
204
  currentPath: @model.path
205
+ headlineHtml: @headlineHtml()
165
206
  @$shareLink = @$('li.share-link')
166
207
  @$shareForm = @$('li.share-form')
167
208
  @$shareUrlInput = @$shareForm.find('input')
168
209
  @$shareCopyButton = @$shareForm.find('button')
169
210
 
211
+ _.delay(@renderHeadlineHtml)
212
+
170
213
  @$app = @$('section.app')
171
214
  for aside in @asides
172
215
  AsideView = aside.view.toFunction()
@@ -188,7 +231,7 @@ class ELA.Views.BaseApp extends Backbone.Poised.View
188
231
  model: @model
189
232
  parentView: this
190
233
  views: @views
191
- layout: @layout
234
+ layouts: @layouts
192
235
  localePrefix: @localePrefix
193
236
  @$('.viewport').replaceWith(view.render().el)
194
237
 
@@ -1,10 +1,8 @@
1
1
  ELA.Views ?= {}
2
2
  class ELA.Views.Canvas extends Backbone.Poised.View
3
- tagName: 'canvas'
3
+ @Params: ELA.Models.CanvasParams
4
4
 
5
- class @Params extends Backbone.Model
6
- serialize: -> {}
7
- deserialize: ->
5
+ tagName: 'canvas'
8
6
 
9
7
  # True if an animation frame request is currently pending
10
8
  animationFrameRequested: false
@@ -15,15 +13,9 @@ class ELA.Views.Canvas extends Backbone.Poised.View
15
13
  @defaultFont = "#{fontWeight} 12px Roboto"
16
14
 
17
15
  initialize: (options = {}) ->
18
- # Make sure we got the parameters model for holding view specific information
19
- @defaults = _.defaults(options.defaults, @defaults) if options.defaults?
20
- if options.params?
21
- @params = options.params
22
- @params.set(@defaults)
23
- else
24
- @model.displayParams ?= {}
25
- @params = new @constructor.Params(@defaults)
26
- @model.displayParams[options.name] = @params
16
+ # Make sure we got the parameters model for holding view specific
17
+ # information
18
+ @params = options.params
27
19
 
28
20
  @setCanvasResolution()
29
21
 
@@ -44,9 +36,10 @@ class ELA.Views.Canvas extends Backbone.Poised.View
44
36
  # Do not take scale into account here, otherwise
45
37
  # non-active subapps will get initialized with wrong size
46
38
  # because they have a css scale of 0.75
47
- @params.set
48
- width: $parent[0].clientWidth
49
- height: $parent[0].clientHeight
39
+ if $parent.length > 0
40
+ @params.set
41
+ width: $parent[0].clientWidth
42
+ height: $parent[0].clientHeight
50
43
 
51
44
  setCanvasResolution: ->
52
45
  width = @params.get('width')
@@ -1,18 +1,6 @@
1
1
  ELA.Views ?= {}
2
- class ELA.Views.BaseGraph extends ELA.Views.Canvas
3
- class @Params extends Backbone.Model
4
- serialize: ->
5
- xScale: @get('xScale')
6
- yScale: @get('yScale')
7
- xOriginRatio: @get('xOrigin') / @get('width')
8
- yOriginRatio: @get('yOrigin') / @get('height')
9
-
10
- deserialize: (attributes) ->
11
- @set
12
- xScale: attributes.xScale
13
- yScale: attributes.yScale
14
- xOrigin: @get('width') * attributes.xOriginRatio
15
- yOrigin: @get('height') * attributes.yOriginRatio
2
+ class ELA.Views.Graph extends ELA.Views.Canvas
3
+ @Params: ELA.Models.GraphParams
16
4
 
17
5
  # A variable that contains temporary information about the pinch
18
6
  # or pan action that is currently performed.
@@ -24,75 +12,33 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
24
12
  # We need to cache current pixelRatio to reset the canvas scale.
25
13
  pixelRatio: null
26
14
 
27
- # Defaults for the view params model.
28
- defaults:
29
- xScale: 1.1 # display 10% more than the configured xRange
30
- yScale: 1.1 # display 10% more than the configured yRange
31
- xOrigin: 30
32
- yOrigin: 0
33
- xOriginAnchor: 'left'
34
- yOriginAnchor: 'bottom'
35
- xOriginRatio: null
36
- yOriginRatio: null
37
- yOffset: 0
38
- debug: {}
39
- xLabelPosition: 'top'
40
- yLabelPosition: 'right'
41
- xPanLocked: false
42
- yPanLocked: false
43
- xPinchLocked: false
44
- yPinchLocked: false
45
- scaleLink: false
46
- xAxis: { y: 0 }
47
- yAxis: { x: 0 }
48
-
49
15
  initialize: ->
50
16
  super
51
17
 
52
- if @defaults.xOriginRatio? and @defaults.xOriginAnchor isnt 'left'
53
- @defaults.xOriginRatio = 1 - @defaults.xOriginRatio
54
- @defaults.xOriginAnchor = 'left'
55
-
56
- if @defaults.yOriginRatio? and @defaults.yOriginAnchor is 'bottom'
57
- @defaults.yOriginRatio = 1 - @defaults.yOriginRatio
58
- @defaults.yOriginAnchor = 'top'
59
-
60
- @resetCanvas()
61
-
62
- # @calculateRanges changes params.xyRange
63
- @params.on 'change:xScale', @calculateRangeX
64
- @params.on 'change:yScale', @calculateRangeY
65
- # When xyRange change we need to repaint
66
- @params.on 'change:xOrigin change:yOrigin change:yRange change:xRange change:width change:height', @requestRepaint
67
-
68
- @calculateRanges()
69
-
70
- calculateRangeX: =>
71
- @params.set xRange: @maxRangeX()
72
-
73
- calculateRangeY: =>
74
- @params.set yRange: @maxRangeY()
75
-
76
- calculateRanges: =>
77
- @calculateRangeX()
78
- @calculateRangeY()
18
+ @listenTo(@params,
19
+ 'change:xOrigin change:yOrigin ' +
20
+ 'change:yRange change:xRange ' +
21
+ 'change:width change:height ' +
22
+ 'change:axisLabelingForCurve'
23
+ @requestRepaint
24
+ )
79
25
 
80
- maxRangeX: (func, xScale = @params.get('xScale')) =>
81
- xScale * 5
26
+ @listenTo(@model.curves, 'change:selected', @requestRepaint)
27
+ @listenTo(@model, 'change:calculators', @requestRepaint)
82
28
 
83
- maxRangeY: (func, yScale = @params.get('yScale')) =>
84
- yScale * 5
29
+ for guide in @params.get('guides')
30
+ @listenTo(@model, "change:#{guide.attribute}", @requestRepaint)
85
31
 
86
32
  events:
87
- 'pan': 'pan',
88
- 'panstart': 'panstart'
89
- 'panend': 'panend'
90
- 'pinchstart': 'pinchstart'
91
- 'pinch': 'pinch'
92
- 'pinchend': 'pinchend'
93
- 'doubletap': 'resetCanvas'
94
- 'mousewheel': 'mousewheel'
95
- 'DOMMouseScroll': 'mousewheel'
33
+ pan: 'pan'
34
+ panstart: 'panstart'
35
+ panend: 'panend'
36
+ pinchstart: 'pinchstart'
37
+ pinch: 'pinch'
38
+ pinchend: 'pinchend'
39
+ doubletap: 'resetCanvas'
40
+ mousewheel: 'mousewheel'
41
+ DOMMouseScroll: 'mousewheel'
96
42
 
97
43
  hammerjs:
98
44
  recognizers: [
@@ -119,46 +65,7 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
119
65
  # Set xScale/yScale and xOrigin/yOrigin to their default values.
120
66
  resetCanvas: =>
121
67
  clearTimeout(@panendTimeout)
122
-
123
- if @defaults.xOriginRatio?
124
- xOrigin = @params.get('width') * @defaults.xOriginRatio
125
- else if @defaults.xOriginAnchor is 'left'
126
- xOrigin = @defaults.xOrigin
127
- else
128
- xOrigin = @params.get('width') - @defaults.xOrigin
129
-
130
- if @defaults.yOriginRatio?
131
- yOrigin = @params.get('height') * @defaults.yOriginRatio
132
- else if @defaults.yOriginAnchor is 'bottom'
133
- yOrigin = @params.get('height') - @defaults.yOrigin
134
- else
135
- yOrigin = @defaults.yOrigin
136
-
137
- xScale = @defaults.xScale
138
- yScale = @defaults.yScale
139
-
140
- if @params.get('scaleLink')
141
- # axis with less pixels per unit length should define scale
142
- if @params.get('width') / @maxRangeX(null, 1) < @params.get('height') / @maxRangeY(null, 1)
143
- yScale = @linkedYScale(xScale)
144
- else
145
- xScale = @linkedXScale(yScale)
146
-
147
- @params.set
148
- xScale: xScale
149
- yScale: yScale
150
- xOrigin: xOrigin
151
- yOrigin: yOrigin
152
-
153
- linkedXScale: (yScale) =>
154
- # calculate xScale to fulfill the following equation:
155
- # (width / maxRangeX) / xScale = height / maxRangeY
156
- (@params.get('width') / @maxRangeX(null, 1)) / (@params.get('height') / @maxRangeY(null, yScale))
157
-
158
- linkedYScale: (xScale) =>
159
- # calculate yScale to fulfill the following equation:
160
- # (height / maxRangeY) / yScale = width / maxRangeX
161
- (@params.get('height') / @maxRangeY(null, 1)) / (@params.get('width') / @maxRangeX(null, xScale))
68
+ @params.reset()
162
69
 
163
70
  debugCircle: (context, color, x, y, radius = 20) =>
164
71
  context.beginPath()
@@ -282,46 +189,39 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
282
189
  precision += 1 while stepSize and stepSize*Math.pow(10, precision) < 1
283
190
  value.toFixed(precision)
284
191
 
285
- setCanvasResolution: (params) ->
286
- super
287
-
288
- # Recalculate xOrigin/yOrigin if we're in a width/height change
289
- # callback. Otherwise, we're initializing and we should skip that.
290
- if params?
291
- previous = @params.previousAttributes()
292
- previousXOrigin = if previous.xOrigin? then previous.xOrigin else @params.get('xOrigin')
293
- previousYOrigin = if previous.yOrigin? then previous.yOrigin else @params.get('yOrigin')
294
- @params.set
295
- xOrigin: previousXOrigin * @params.get('width') / previous.width
296
- yOrigin: previousYOrigin * @params.get('height') / previous.height
297
-
298
- # We don't need that during initialisation as well because it's
299
- # done by resetCanvas
300
- switch @params.get('scaleLink')
301
- when 'x'
302
- @params.set xScale: @linkedXScale()
303
- when 'y'
304
- @params.set yScale: @linkedYScale()
305
-
306
192
  xAxisValueLabel: (val, stepsize) ->
307
193
  @axisLabel(val, stepsize)
308
194
 
309
195
  yAxisValueLabel: (val, stepsize) ->
196
+ curve = @params.get('axisLabelingForCurve')
197
+ if curve?
198
+ curvePresenter = @Present(curve)
199
+ val = curvePresenter.unitValue(val)
200
+ stepsize = curvePresenter.unitValue(stepsize)
310
201
  @axisLabel(val - @params.get('yOffset'), stepsize)
311
202
 
312
- _axisLabel: (label) ->
313
- if typeof label is 'function'
314
- label.call(this)
315
- else if label?
316
- label
317
- else
318
- ''
203
+ genericAxisLabel: (axis) ->
204
+ axisLabel = @params.get("#{axis}AxisLabel")
205
+ return axisLabel.call(this) if axisLabel? and _.isFunction(axisLabel)
206
+ return axisLabel if axisLabel?
319
207
 
320
- xAxisLabel: -> @_axisLabel(@params.get('xAxisLabel'))
321
- yAxisLabel: -> @_axisLabel(@params.get('yAxisLabel'))
208
+ axisLabelLocale = @params.get("#{axis}AxisLabelLocale")
209
+ return @loadLocale(axisLabelLocale) if axisLabelLocale?
322
210
 
323
- renderCurves: ->
324
- # stub
211
+ curve = @params.get('axisLabelingForCurve')
212
+ if curve?
213
+ return @Present(curve).fullXAxisLabel() if axis is 'x'
214
+ return @Present(curve).fullYAxisLabel() if axis is 'y'
215
+
216
+ @loadLocale("graph.#{axis}AxisLabel", defaultValue: '')
217
+
218
+ xAxisLabel: -> @genericAxisLabel('x')
219
+ yAxisLabel: -> @genericAxisLabel('y')
220
+
221
+ # Stubs
222
+ beforeRenderCurves: ->
223
+ afterRenderCurves: ->
224
+ beforeRender: ->
325
225
 
326
226
  renderXAxis: (y) ->
327
227
  @context.setLineDash []
@@ -347,7 +247,7 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
347
247
  @context.textBaseline = 'middle'
348
248
  @context.textAlign = 'center'
349
249
 
350
- xRange = @maxRangeX()
250
+ xRange = @params.maxRangeX()
351
251
  xMin = -@xOrigin * xRange / @width
352
252
  xMax = (@width - @xOrigin) * xRange / @width
353
253
  xStepSize = @roundStepSize(100 * xRange / @width)
@@ -395,7 +295,7 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
395
295
  @context.textAlign = 'right'
396
296
  @context.textBaseline = 'middle'
397
297
 
398
- yRange = @maxRangeY()
298
+ yRange = @params.maxRangeY()
399
299
  yMin = -(@height - @yOrigin) * yRange / @height
400
300
  yMax = @yOrigin * yRange / @height
401
301
  yStepSize = @roundStepSize(100 * yRange / @height)
@@ -426,7 +326,7 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
426
326
  @context.beginPath()
427
327
 
428
328
  unless @params.get('xLabelPosition') is 'none'
429
- xRange = @maxRangeX() unless xRange?
329
+ xRange = @params.maxRangeX() unless xRange?
430
330
  xMin = -@xOrigin * xRange / @width
431
331
  xMax = (@width - @xOrigin) * xRange / @width
432
332
  xStepSize = @roundStepSize(100 * xRange / @width)
@@ -436,7 +336,7 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
436
336
  @context.lineTo(xPos, @height)
437
337
 
438
338
  unless @params.get('yLabelPosition') is 'none'
439
- yRange = @maxRangeY() unless yRange?
339
+ yRange = @params.maxRangeY() unless yRange?
440
340
  yMin = -(@height - @yOrigin) * yRange / @height
441
341
  yMax = @yOrigin * yRange / @height
442
342
  yStepSize = @roundStepSize(100 * yRange / @height)
@@ -448,8 +348,201 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
448
348
  @context.stroke()
449
349
  @context.closePath()
450
350
 
451
- beforeRender: ->
452
- # Stub to execute code before rendering...
351
+ getControlPoints: (x0, y0, x1, y1, x2, y2, tension) ->
352
+ d01 = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2))
353
+ d12 = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
354
+ fa = tension * d01 / (d01 + d12)
355
+ fb = tension - fa
356
+ p1x = x1 + fa * (x0 - x2)
357
+ p1y = y1 + fa * (y0 - y2)
358
+ p2x = x1 - fb * (x0 - x2)
359
+ p2y = y1 - fb * (y0 - y2)
360
+ [p1x, p1y, p2x, p2y]
361
+
362
+ drawSpline: (points, t, closed, xPos = ((x) -> x), yPos = ((y) -> y)) ->
363
+ if points.length <= 4
364
+ @context.moveTo(xPos(points[0]), yPos(points[1]))
365
+ @context.lineTo(xPos(points[2]), yPos(points[3]))
366
+ else
367
+ cp = []
368
+ n = points.length
369
+ points = _.clone(points)
370
+ if closed
371
+ points.push points[0], points[1], points[2], points[3]
372
+ points.unshift points[n - 1]
373
+ points.unshift points[n - 1]
374
+ for i in [0..n] by 2
375
+ cp = cp.concat(@getControlPoints(points[i], points[i+1], points[i+2], points[i+3], points[i+4], points[i+5], t))
376
+ cp = cp.concat(cp[0], cp[1])
377
+ for i in [0..n] by 2
378
+ @context.moveTo(xPos(points[i]), yPos(points[i+1]))
379
+ @context.bezierCurveTo(xPos(cp[2*i-2]), yPos(cp[2*i-1]), xPos(cp[2*i]), yPos(cp[2*i+1]), xPos(points[i+2]), yPos(points[i+3]))
380
+ else
381
+ for i in [0..(n-4)] by 2
382
+ cp = cp.concat(@getControlPoints(points[i], points[i+1], points[i+2], points[i+3], points[i+4], points[i+5], t))
383
+
384
+ @context.moveTo(xPos(points[0]), yPos(points[1]))
385
+ @context.quadraticCurveTo(xPos(cp[0]), yPos(cp[1]), xPos(points[2]), yPos(points[3]))
386
+ for i in [2..(n-3)] by 2
387
+ @context.moveTo(xPos(points[i]), yPos(points[i+1]))
388
+ @context.bezierCurveTo(xPos(cp[2*i-2]), yPos(cp[2*i-1]), xPos(cp[2*i]), yPos(cp[2*i+1]), xPos(points[i+2]), yPos(points[i+3]))
389
+ @context.moveTo(xPos(points[n-2]), yPos(points[n-1]))
390
+ @context.quadraticCurveTo(xPos(cp[2*n-10]), yPos(cp[2*n-9]), xPos(points[n-4]), yPos(points[n-3]))
391
+
392
+ drawCurve: (points, tension = 0, closed = false, xPos = ((x) -> x), yPos = ((y) -> y)) ->
393
+ if tension is 0
394
+ beginning = true
395
+ if points.length >= 2
396
+ @context.moveTo(xPos(points[0]), yPos(points[1]))
397
+ for i in [2...points.length] by 2
398
+ @context.lineTo(xPos(points[i]), yPos(points[i+1]))
399
+ else
400
+ @drawSpline(points, tension, closed, xPos, yPos)
401
+
402
+ renderCurves: ->
403
+ resultCurves = []
404
+ for calc, i in @model.get('calculators')
405
+ for curve, j in @params.sortedCurves()
406
+ func = curve.get('function')
407
+ resultCurves.push
408
+ curve: curve
409
+ result: calc[func](@xMin, @xMax)
410
+ yRange: @params.maxRangeY(func)
411
+ xRange: @params.maxRangeX(func)
412
+ calculatorIdx: i
413
+
414
+ @beforeRenderCurves(resultCurves)
415
+
416
+ for resultCurve in resultCurves
417
+ curve = resultCurve.curve
418
+ result = resultCurve.result
419
+ yRange = resultCurve.yRange
420
+ xRange = resultCurve.xRange
421
+ if _.isObject(result)
422
+ xPos = (x) => @xOrigin + x * @width / xRange
423
+ yPos = (y) => @yOrigin - (y + @yOffset) * @height / yRange
424
+
425
+ if curve.hasSubcurves()
426
+ for name, subcurve of curve.subcurves()
427
+ if result[name].points?
428
+ @renderCurve(subcurve, result[name], xPos, yPos, resultCurve.calculatorIdx)
429
+ else if result[name].radius?
430
+ @renderCircle(subcurve, result[name], xPos, yPos, resultCurve.calculatorIdx)
431
+ else
432
+ if result.points?
433
+ @renderCurve(curve, result, xPos, yPos, resultCurve.calculatorIdx)
434
+ else if result.radius?
435
+ @renderCircle(curve, result, xPos, yPos, resultCurve.calculatorIdx)
436
+
437
+ @afterRenderCurves(resultCurves)
438
+
439
+ renderCircle: (curve, result, xPos, yPos, calculatorIdx) ->
440
+ func = curve.get('function')
441
+ yRange = @maxRangeY(func)
442
+ @context.beginPath()
443
+ width = Math.abs(xPos(2*result.radius) - @xOrigin)
444
+ height = Math.abs(yPos(2*result.radius) - @yOrigin)
445
+ centerX = xPos(result.center[0])
446
+ centerY = yPos(result.center[1])
447
+
448
+ aX = centerX - width/2
449
+ aY = centerY - height/2
450
+ hB = (width / 2) * .5522848
451
+ vB = (height / 2) * .5522848
452
+ eX = aX + width
453
+ eY = aY + height
454
+ mX = aX + width / 2
455
+ mY = aY + height / 2
456
+ @context.moveTo(aX, mY);
457
+ @context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
458
+ @context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
459
+ @context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
460
+ @context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
461
+
462
+ @context.setLineDash(curve.lineDash(calculatorIdx))
463
+ if curve.strokeStyle()
464
+ @context.lineWidth = curve.get('lineWidth')
465
+ @context.strokeStyle = curve.strokeStyle()
466
+ @context.stroke()
467
+ if fillStyle = curve.get('fillStyle')
468
+ @context.fillStyle = fillStyle
469
+ @context.fill()
470
+ @context.closePath()
471
+
472
+ renderCurve: (curve, result, xPos, yPos, calculatorIdx) ->
473
+ @context.beginPath()
474
+ @context.setLineDash(curve.lineDash(calculatorIdx))
475
+ if result.multiplePaths
476
+ for setOfPoints, i in result.points
477
+ @drawCurve(setOfPoints, result.tension, false, xPos, yPos)
478
+ else
479
+ @drawCurve(result.points, result.tension, false, xPos, yPos)
480
+ if curve.strokeStyle()
481
+ @context.lineWidth = curve.get('lineWidth')
482
+ @context.strokeStyle = curve.strokeStyle()
483
+ @context.stroke()
484
+ if fillStyle = curve.get('fillStyle')
485
+ @context.fillStyle = fillStyle
486
+ @context.fill()
487
+ @context.closePath()
488
+
489
+ renderGuide: (guide) ->
490
+ valueAtPoint = @model.get(guide.attribute)
491
+
492
+ @context.beginPath()
493
+ @context.setLineDash []
494
+ @context.strokeStyle = '#999999'
495
+ @context.lineWidth = 2
496
+
497
+ # Border below legend
498
+ @context.moveTo(@width, 0)
499
+ @context.lineTo(0, 0)
500
+
501
+ if guide.orientation is 'vertical'
502
+ # Border next to range handler
503
+ @context.moveTo(0, @height)
504
+ @context.lineTo(@width, @height)
505
+ @context.lineTo(@width, @height-10)
506
+
507
+ # Range handler line
508
+ xPos = @xOrigin + valueAtPoint * @width / @xRange
509
+ @context.moveTo(xPos, 0)
510
+ @context.lineTo(xPos, @height)
511
+ else
512
+ # Border next to range handler
513
+ @context.lineTo(0, @height)
514
+
515
+ # Range handler line
516
+ yPos = - valueAtPoint * @height / @yRange + @yOrigin
517
+ @context.moveTo(0, yPos)
518
+ @context.lineTo(@width, yPos)
519
+
520
+ @context.stroke()
521
+ @context.closePath()
522
+
523
+ @context.beginPath()
524
+ # The fillStyle of the triangle should match the color of the
525
+ # background set for the legend and range handler in screen.styl:
526
+ @context.fillStyle = "#f2f2f2"
527
+ @context.lineWidth = 1
528
+
529
+ if guide.orientation is 'vertical'
530
+ @context.moveTo(xPos + 8, 0)
531
+ @context.lineTo(xPos, 8)
532
+ @context.lineTo(xPos - 8, 0)
533
+ @context.moveTo(xPos - 8, @height)
534
+ @context.lineTo(xPos, @height - 8)
535
+ @context.lineTo(xPos + 8, @height)
536
+ else
537
+ @context.moveTo(0, yPos - 8)
538
+ @context.lineTo(8, yPos)
539
+ @context.lineTo(0, yPos + 8)
540
+ @context.moveTo(@width, yPos - 8)
541
+ @context.lineTo(@width - 8, yPos)
542
+ @context.lineTo(@width, yPos + 8)
543
+ @context.stroke()
544
+ @context.fill()
545
+ @context.closePath()
453
546
 
454
547
  render: =>
455
548
  @width = @params.get('width')
@@ -485,5 +578,8 @@ class ELA.Views.BaseGraph extends ELA.Views.Canvas
485
578
  else
486
579
  @renderYAxis(yAxis.x)
487
580
  @renderCurves()
581
+
582
+ for guide in @params.get('guides')
583
+ @renderGuide(guide)
488
584
 
489
585
  this