This repository was archived by the owner on May 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 865
/
Copy pathderivative-intuition.js
309 lines (254 loc) · 12.9 KB
/
derivative-intuition.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/* TODO(csilvers): fix these lint errors (http://eslint.org/docs/rules): */
/* eslint-disable comma-dangle, indent, max-len, no-undef, no-var */
/* To fix, remove an entry above, run ka-lint, and fix errors. */
define(function(require) {
require("../third_party/jquery.mobile.vmouse.js");
$.extend(KhanUtil, {
FN_COLOR: "#6495ED",
DDX_COLOR: "#FFA500",
TANGENT_COLOR: "#AAA",
TANGENT_LINE_LENGTH: 200,
TANGENT_GROWTH_FACTOR: 3,
TANGENT_SHIFT: 5,
// Wrap graphInit to create a 600x600px graph properly scaled to the given range
initAutoscaledGraph: function(range, options) {
var graph = KhanUtil.currentGraph;
options = $.extend({
xpixels: 500,
ypixels: 500,
xdivisions: 20,
ydivisions: 20,
labels: true,
unityLabels: true,
range: (typeof range === "undefined" ? [[-10, 10], [-10, 10]] : range)
}, options);
options.scale = [options.xpixels / (options.range[0][1] - options.range[0][0]),
options.ypixels / (options.range[1][1] - options.range[1][0])];
options.gridStep = [(options.range[0][1] - options.range[0][0]) / options.xdivisions,
(options.range[1][1] - options.range[1][0]) / options.ydivisions];
// Attach the resulting metrics to the graph for later reference
graph.xpixels = options.xpixels;
graph.ypixels = options.ypixels;
graph.range = options.range;
graph.scale = options.scale;
graph.graphInit(options);
},
// start the magic
initDerivativeIntuition: function(fnx, ddx, points) {
var graph = KhanUtil.currentGraph;
KhanUtil.fnx = fnx;
KhanUtil.ddx = ddx;
KhanUtil.points = points;
KhanUtil.highlight = false;
KhanUtil.dragging = false;
KhanUtil.ddxShown = false;
// to store the SVG paths
graph.tangentLines = [];
graph.tangentPoints = [];
graph.slopePoints = [];
graph.mouseTargets = [];
// graphie puts text spans on top of the SVG, which looks good, but gets
// in the way of mouse events. So we add another SVG element on top
// of everything else where we can add invisible shapes with mouse
// handlers wherever we want. Is there a better way?
graph.mouselayer = Raphael("ddxplot", graph.xpixels, graph.ypixels);
$(graph.mouselayer.canvas).css("z-index", 1);
Khan.scratchpad.disable();
// plot all the tangent lines first so they're underneath the tangent/slope points
$(points).each(function(index, xval) {
KhanUtil.plotTangentLine(index);
});
$(points).each(function(index, xval) {
// blue points
KhanUtil.plotTangentPoint(index);
// orange points and mouse magic
KhanUtil.plotSlopePoint(index);
});
// Once the problem loads, call setSlope() for each point to set the
// slopes to 0. This replicates the action of the user placing each point
// at zero and applies the same "close enough" test so very small slopes
// aren't graded wrong even if they look almost right.
$(Exercises).one("newProblem", function() {
$(points).each(function(index, xval) {
KhanUtil.setSlope(index, 0);
});
});
},
plotTangentLine: function(index) {
var graph = KhanUtil.currentGraph;
var xval = KhanUtil.points[index];
var yval = KhanUtil.fnx(xval);
// Now the fun bit: To make it clear that the tangent line only
// touches at a single point, it's shifted a little bit above or
// below the curve.
// The shifted pivot point; defaults to unshifted xval/yval in
// case we're dealing with an inflection point.
var xshift = xval;
var yshift = yval;
// The slope of a line perpendicular to the tangent line. It is
// along this direction that we shift the tangent line.
var perpslope = 0;
// First and second derivative at the point we're dealing with.
var ddx1 = KhanUtil.ddx(xval);
var ddx2 = (KhanUtil.ddx(xval - 0.001) - KhanUtil.ddx(xval + 0.001)) / 0.002;
if (ddx1 !== 0) {
// We want to shift *visually* perpendicular to the tangent line,
// so if the graph has different x and y scales, perpslope isn't
// quite as simple as (-1/slope)
perpslope = (-1 / (ddx1 * (graph.scale[1] / graph.scale[0]))) / (graph.scale[1] / graph.scale[0]);
// Second derivative tells us if the curve is concave up or down, thus which way to
// shift the tangent line to "get away" from the curve. If perpslope is negative,
// everything is reversed.
if ((ddx2 > 0 && perpslope > 0) || (ddx2 < 0 && perpslope < 0)) {
// atan(perpslope) is the direction to shift; cos() of that gives the x component; the rest of the mess normalizes for different x- and y-scales
xshift = xval + Math.cos(Math.atan(perpslope * (graph.scale[1] / graph.scale[0]))) * KhanUtil.TANGENT_SHIFT / (2 * graph.scale[0]);
yshift = perpslope * (xshift - xval) + yval;
} else if ((ddx2 < 0 && perpslope > 0) || (ddx2 > 0 && perpslope < 0)) {
xshift = xval - Math.cos(Math.atan(perpslope * (graph.scale[1] / graph.scale[0]))) * KhanUtil.TANGENT_SHIFT / (2 * graph.scale[0]);
yshift = perpslope * (xshift - xval) + yval;
}
} else {
// Slope is 0, so perpslope is undefined. Just shift up or down based on concavity
if (ddx2 < 0) {
yshift = yval - (KhanUtil.TANGENT_SHIFT / (2 * graph.scale[1]));
} else if (ddx2 > 0) {
yshift = yval + (KhanUtil.TANGENT_SHIFT / (2 * graph.scale[1]));
}
}
// at last the slightly nudged line is ready to draw
graph.style({
stroke: KhanUtil.TANGENT_COLOR,
strokeWidth: 2
}, function() {
graph.tangentLines[index] = graph.line(
[xshift - KhanUtil.TANGENT_LINE_LENGTH / (2 * graph.scale[0]), yshift],
[xshift + KhanUtil.TANGENT_LINE_LENGTH / (2 * graph.scale[0]), yshift]);
});
},
plotTangentPoint: function(index) {
var graph = KhanUtil.currentGraph;
var xval = KhanUtil.points[index];
graph.style({
fill: KhanUtil.FN_COLOR,
stroke: KhanUtil.FN_COLOR
}, function() {
graph.tangentPoints[index] = graph.ellipse([xval, KhanUtil.fnx(xval)], [4 / graph.scale[0], 4 / graph.scale[1]]);
});
},
plotSlopePoint: function(index) {
var graph = KhanUtil.currentGraph;
var xval = KhanUtil.points[index];
graph.style({
fill: KhanUtil.DDX_COLOR,
stroke: KhanUtil.DDX_COLOR
}, function() {
graph.slopePoints[index] = graph.ellipse([xval, 0], [4 / graph.scale[0], 4 / graph.scale[1]]);
});
var $ddxplot = $("#ddxplot");
var $solutionAreaText = $("div#solutionarea :text").eq(index);
var $solutionAreaLabel = $("div#solutionarea .answer-label").eq(index);
// the invisible shape in front of each point that gets mouse events
graph.mouseTargets[index] = graph.mouselayer.circle(
(xval - graph.range[0][0]) * graph.scale[0],
(graph.range[1][1] - 0) * graph.scale[1], 22);
graph.mouseTargets[index].attr({fill: "#000", "opacity": 0.0});
$(graph.mouseTargets[index][0]).css("cursor", "move");
$(graph.mouseTargets[index][0]).bind("vmousedown vmouseover vmouseout", function(event) {
event.preventDefault();
var graph = KhanUtil.currentGraph;
if (event.type === "vmouseover") {
KhanUtil.highlight = true;
if (!KhanUtil.dragging) {
graph.slopePoints[index].animate({ scale: 2 }, 50);
graph.tangentLines[index].animate({ "stroke": KhanUtil.DDX_COLOR }, 100);
}
} else if (event.type === "vmouseout") {
KhanUtil.highlight = false;
if (!KhanUtil.dragging) {
graph.slopePoints[index].animate({ scale: 1 }, 50);
graph.tangentLines[index].animate({ "stroke": KhanUtil.TANGENT_COLOR }, 100);
}
} else if (event.type === "vmousedown" && (event.which === 1 || event.which === 0)) {
event.preventDefault();
graph.tangentLines[index].toFront();
graph.tangentPoints[index].toFront();
graph.slopePoints[index].toFront();
graph.tangentLines[index].animate({ scale: KhanUtil.TANGENT_GROWTH_FACTOR }, 200);
KhanUtil.dragging = true;
var ddxplotTop = $ddxplot.offset().top;
$(document).bind("vmousemove vmouseup", function(event) {
event.preventDefault();
// mouseY is in pixels relative to the SVG; coordY is the scaled y-coordinate value
var mouseY = event.pageY - ddxplotTop;
mouseY = Math.max(10, Math.min(graph.ypixels - 10, mouseY));
var coordY = graph.range[1][1] - mouseY / graph.scale[1];
if (event.type === "vmousemove") {
$solutionAreaText.val(KhanUtil.roundTo(2, coordY));
$solutionAreaLabel.text(KhanUtil.roundTo(2, coordY));
graph.tangentLines[index].rotate(-Math.atan(coordY * (graph.scale[1] / graph.scale[0])) * (180 / Math.PI), true);
graph.slopePoints[index].attr("cy", mouseY);
graph.mouseTargets[index].attr("cy", mouseY);
} else if (event.type === "vmouseup") {
$(document).unbind("vmousemove vmouseup");
KhanUtil.setSlope(index, coordY);
KhanUtil.dragging = false;
graph.tangentLines[index].animate({ scale: 1 }, 200);
if (!KhanUtil.highlight) {
graph.slopePoints[index].animate({ scale: 1 }, 200);
graph.tangentLines[index].animate({ "stroke": KhanUtil.TANGENT_COLOR }, 100);
}
// If all the points are in the right place, reveal the derivative function
var answers = $.map($("div#solutionarea .answer-label"), function(x) {
return parseFloat($(x).text());
});
var correct = $.map(KhanUtil.points, function(x) {
return KhanUtil.roundTo(2, KhanUtil.ddx(x));
});
if (answers.join() === correct.join()) {
KhanUtil.revealDerivative(400);
}
}
});
}
});
},
// Set the slope for one point. Snap to the right answer if we're close enough.
setSlope: function(index, coordY) {
var graph = KhanUtil.currentGraph;
var answer = KhanUtil.ddx(KhanUtil.points[index]);
var degreesOff = Math.abs(Math.atan(answer * graph.scale[1] / graph.scale[0]) -
Math.atan(coordY * graph.scale[1] / graph.scale[0])) * (180 / Math.PI);
// How far off you're allowed to be
if (degreesOff < 7) {
coordY = answer;
}
$($("div#solutionarea :text")[index]).val(KhanUtil.roundTo(2, coordY));
$($("div#solutionarea .answer-label")[index]).text(KhanUtil.roundTo(2, coordY));
graph.tangentLines[index].rotate(-Math.atan(coordY * (graph.scale[1] / graph.scale[0])) * (180 / Math.PI), true);
graph.slopePoints[index].attr("cy", (graph.range[1][1] - coordY) * graph.scale[1]);
graph.mouseTargets[index].attr("cy", (graph.range[1][1] - coordY) * graph.scale[1]);
},
// Shows the derivative plot and equation
// Called when all the points are in the right place or as a hint
revealDerivative: function(duration) {
if (!KhanUtil.ddxShown) {
var graph = KhanUtil.currentGraph;
var ddxplot;
duration = duration || 0;
graph.style({
stroke: KhanUtil.DDX_COLOR,
strokeWidth: 1,
opacity: duration === 0 ? 1 : 0
}, function() {
ddxplot = graph.plot(function(x) {
return KhanUtil.ddx(x);
}, KhanUtil.tmpl.getVAR("XRANGE"));
});
$("span#ddxspan").show(); // for IE
$("span#ddxspan").fadeTo(duration, 1);
ddxplot.animate({ opacity: 1 }, duration);
KhanUtil.ddxShown = true;
}
}
});
});