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 +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
|