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 +5 -5
- data/app/js/lib/models/base_app.coffee +6 -5
- data/app/js/lib/models/canvas_params.coffee +11 -0
- data/app/js/lib/models/graph_params.coffee +167 -0
- data/app/js/lib/views/base_app.coffee +54 -11
- data/app/js/lib/views/canvas.coffee +9 -16
- data/app/js/lib/views/{base_graph.coffee → graph.coffee} +250 -154
- data/app/js/lib/views/graph_view.coffee +27 -25
- data/app/js/lib/views/viewport.coffee +26 -12
- data/app/views/general/app.jst.hamlc +1 -9
- data/app/views/general/app_headline.jst.hamlc +10 -0
- data/lib/ela/version.rb +1 -1
- metadata +232 -231
- data/app/js/lib/views/curve_graph.coffee +0 -163
- data/app/js/lib/views/interpolated_graph.coffee +0 -294
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a2f4ca90c26f32467f5e7d3821d25a853273455ef4c9b7a254064bf570af599
|
4
|
+
data.tar.gz: 45df5d9aa6cbd546d0c373b22d1eee98fcbc41faf5ab058b6ea55e0e79e99ce6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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')
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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.
|
26
|
-
'tap header .poised.
|
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(.
|
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
|
-
|
49
|
-
|
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
|
-
|
52
|
-
@$('.
|
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
|
-
|
157
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
3
|
+
@Params: ELA.Models.CanvasParams
|
4
4
|
|
5
|
-
|
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
|
19
|
-
|
20
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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.
|
3
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@
|
58
|
-
|
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
|
-
|
81
|
-
|
26
|
+
@listenTo(@model.curves, 'change:selected', @requestRepaint)
|
27
|
+
@listenTo(@model, 'change:calculators', @requestRepaint)
|
82
28
|
|
83
|
-
|
84
|
-
|
29
|
+
for guide in @params.get('guides')
|
30
|
+
@listenTo(@model, "change:#{guide.attribute}", @requestRepaint)
|
85
31
|
|
86
32
|
events:
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
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
|
-
|
321
|
-
|
208
|
+
axisLabelLocale = @params.get("#{axis}AxisLabelLocale")
|
209
|
+
return @loadLocale(axisLabelLocale) if axisLabelLocale?
|
322
210
|
|
323
|
-
|
324
|
-
|
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
|
-
|
452
|
-
|
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
|