ela 3.2.0 → 3.3.0

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