Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of https://github.com/Khan/khan-exercises

  • Loading branch information...
commit 08abd643e0df124ecf43600e9ad85077e14c987e 2 parents 87bc7c5 + d1f63a4
Gary Anderson authored
View
110 exercises/graphing_circles.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html data-require="math math-format expressions interactive graphie">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Graphing circles</title>
+ <script src="../khan-exercise.js"></script>
+</head>
+
+<body>
+ <div class="exercise">
+ <div class="problems">
+ <div>
+ <div class="vars"
+ data-ensure="!(H === 0 && K === 0 && R === 2)">
+ <var id="H">randRange(-5, 5)</var>
+ <var id="K">randRange(-5, 5)</var>
+ <var id="R">randRange(1, 5)</var>
+ <var id="X2T">H === 0 ? "x^2" :
+ expr(["^", ["+", "x", -H], 2])
+ </var>
+ <var id="Y2T">K === 0 ? "y^2" :
+ expr(["^", ["+", "y", -K], 2])
+ </var>
+ </div>
+
+ <p class="question">
+ Graph the circle <code><var>expr(["+", X2T, Y2T])</var>
+ = <var>R * R</var></code>.
+ </p>
+
+ <div class="problem">
+ <br>
+ <div class="graphie" id="grid">
+ graphInit({
+ range: 11,
+ scale: 20,
+ axisArrows: "&lt;-&gt;",
+ tickStep: 1,
+ labelStep: 1,
+ gridOpacity: 0.05,
+ axisOpacity: 0.2,
+ tickOpacity: 0.4,
+ labelOpacity: 0.5
+ });
+
+ label( [ 0, 11 ], "y", "above" );
+ label( [ 11, 0 ], "x", "right" );
+
+ addMouseLayer();
+ graph.circle = addCircleGraph();
+ </div>
+ </div>
+
+ <div class="solution" data-type="custom">
+ <div class="instruction">
+ Drag the center point and perimeter of the circle
+ to graph the equation.
+ </div>
+ <div class="guess">[
+ graph.circle.center[0],
+ graph.circle.center[1],
+ graph.circle.radius]
+ </div>
+ <div class="validator-function">
+ if (_.isEqual(guess, [0, 0, 2])) {
+ return "";
+ }
+ return _.isEqual(guess, [H, K, R]);
+ </div>
+ <div class="show-guess">
+ graph.circle.setCenter(guess[0], guess[1]);
+ graph.circle.setRadius(guess[2]);
+ </div>
+ </div>
+
+ <div class="hints">
+ <p>
+ The equation of a circle with center
+ <code>(\blue{h}, \green{k})</code> and radius
+ <code>\pink{r}</code> is
+ <code>(x - \blue{h})^2 + (y - \green{k})^2 =
+ \pink{r}^2</code>.
+ </p>
+ <p>
+ We can rewrite the given equation as
+ <code>(x - \blue{<var>negParens(H)</var>})^2 + (y -
+ \green{<var>negParens(K)</var>})^2 =
+ \pink{<var>R</var>}^2</code>.
+ </p>
+ <div>
+ <p>
+ Thus, the center of the circle should be
+ <code>(\blue{<var>H</var>}, \green{<var>K</var>})
+ </code> and the radius should be
+ <code>\pink{<var>R</var>}</code>.
+ </p>
+ <div class="graphie" data-update="grid">
+ circle([H, K], R, {
+ stroke: PURPLE,
+ strokeWidth: 1,
+ strokeDasharray: "- "
+ }).toBack();
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</body>
+</html>
View
183 exercises/graphing_circles_2.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<html data-require="math math-format expressions interactive graphie">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Graphing circles 2</title>
+ <script src="../khan-exercise.js"></script>
+</head>
+
+<body>
+ <div class="exercise">
+ <div class="problems">
+ <div>
+ <div class="vars"
+ data-ensure="!(H === 0 && K === 0 && R === 2)">
+ <var id="H">randRange(-5, 5)</var>
+ <var id="K">randRange(-5, 5)</var>
+ <var id="R">randRange(1, 5)</var>
+ <var id="X2T">H === 0 ? "x^2" :
+ expr(["^", ["+", "x", -H], 2])
+ </var>
+ <var id="Y2T">K === 0 ? "y^2" :
+ expr(["^", ["+", "y", -K], 2])
+ </var>
+ <var id="D">-2 * H</var>
+ <var id="E">-2 * K</var>
+ <var id="F">H * H + K * K - R * R</var>
+ </div>
+
+ <p class="question">
+ Graph the circle <code><var>expr(["+", "x^2", "y^2",
+ D === 0 ? null : ["*", D, "x"],
+ E === 0 ? null : ["*", E, "y"],
+ F === 0 ? null : F])</var>
+ = 0</code>.
+ </p>
+
+ <div class="problem">
+ <br>
+ <div class="graphie" id="grid">
+ graphInit({
+ range: 11,
+ scale: 20,
+ axisArrows: "&lt;-&gt;",
+ tickStep: 1,
+ labelStep: 1,
+ gridOpacity: 0.05,
+ axisOpacity: 0.2,
+ tickOpacity: 0.4,
+ labelOpacity: 0.5
+ });
+
+ label( [ 0, 11 ], "y", "above" );
+ label( [ 11, 0 ], "x", "right" );
+
+ addMouseLayer();
+ graph.circle = addCircleGraph();
+ </div>
+ </div>
+
+ <div class="solution" data-type="custom">
+ <div class="instruction">
+ Drag the center point and perimeter of the circle
+ to graph the equation.
+ </div>
+ <div class="guess">[
+ graph.circle.center[0],
+ graph.circle.center[1],
+ graph.circle.radius]
+ </div>
+ <div class="validator-function">
+ if (_.isEqual(guess, [0, 0, 2])) {
+ return "";
+ }
+ return _.isEqual(guess, [H, K, R]);
+ </div>
+ <div class="show-guess">
+ graph.circle.setCenter(guess[0], guess[1]);
+ graph.circle.setRadius(guess[2]);
+ </div>
+ </div>
+
+ <div class="hints">
+ <p>
+ First, convert the equation to standard form by
+ completing the square.
+ </p>
+ <div>
+ <p>
+ Group the <code>\blue{x}</code> and
+ <code>\green{y}</code> terms on the left side and
+ move the constant term to the right side.
+ </p>
+ <p><code>\qquad
+ \blue{<span data-if="H !== 0">
+ (<var>expr(["+", "x^2", ["*", D, "x"]])</var>)
+ </span><span data-else>
+ (x^2)
+ </span>} +
+ \green{<span data-if="K !== 0">
+ (<var>expr(["+", "y^2", ["*", E, "y"]])</var>)
+ </span><span data-else>
+ (y^2)
+ </span>}
+ \quad = \quad <var>-F</var>
+ </code></p>
+ </div>
+ <div>
+ <p>
+ Add
+ <span data-if="H !== 0">
+ <code>\blue{<var>H * H</var>}</code> to both
+ sides to complete the square for the
+ <code>\blue{x}</code> term</span
+ ><span data-if="H !== 0 && K !== 0"> and</span
+ ><span data-if="K !== 0">
+ <code>\green{<var>K * K</var>}</code> to both
+ sides to complete the square for the
+ <code>\green{y}</code> term</span
+ >.
+ </p>
+ <p><code>\qquad
+ \blue{<span data-if="H !== 0">
+ (<var>
+ expr(["+", "x^2", ["*", D, "x"], H * H])
+ </var>)
+ </span><span data-else>
+ (x^2)
+ </span>}
+ + \green{<span data-if="K !== 0">
+ (<var>
+ expr(["+", "y^2", ["*", E, "y"], K * K])
+ </var>)
+ </span><span data-else>
+ (y^2)
+ </span>} \quad = \quad <var>-F</var>
+ <span data-if="H !== 0">
+ + \blue{<var>H * H</var>}
+ <span>
+ <span data-if="K !== 0">
+ + \green{<var>K * K</var>}
+ </span>
+ </code></p>
+ </div>
+ <div>
+ <p>Simplify and write each term as a square:</p>
+ <p><code>\qquad
+ \blue{<var>X2T</var>} + \green{<var>Y2T</var>}
+ \quad = \quad <var>R * R</var>
+ </code></p>
+ <p><code>\qquad
+ (x - \blue{<var>negParens(H)</var>})^2 + (y -
+ \green{<var>negParens(K)</var>})^2 \quad = \quad
+ \pink{<var>R</var>}^2
+ </code></p>
+ </div>
+ <p>
+ The equation of a circle with center
+ <code>(\blue{h}, \green{k})</code> and radius
+ <code>\pink{r}</code> is
+ <code>(x - \blue{h})^2 + (y - \green{k})^2 =
+ \pink{r}^2</code>.
+ </p>
+ <div>
+ <p>
+ Thus, the center of the circle should be
+ <code>(\blue{<var>H</var>}, \green{<var>K</var>})
+ </code> and the radius should be
+ <code>\pink{<var>R</var>}</code>.
+ </p>
+ <div class="graphie" data-update="grid">
+ circle([H, K], R, {
+ stroke: PURPLE,
+ strokeWidth: 1,
+ strokeDasharray: "- "
+ }).toBack();
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</body>
+</html>
View
331 exercises/law_of_cosines.html
@@ -0,0 +1,331 @@
+<!DOCTYPE html>
+<html data-require="math math-format graphie graphie-geometry angles">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Law of cosines</title>
+ <script src="../khan-exercise.js"></script>
+</head>
+<body>
+<div class="exercise">
+ <div class="problems">
+ <div id="find-side" data-calculator>
+ <div class="vars" data-ensure="
+ !TRIANGLE.isRight() &&
+ TRIANGLE.isScalene() &&
+ TRIANGLE.isNotDegenerate()">
+ <var id="SIDES">shuffle([null].
+ concat(randRangeUnique(5, 16, 2)))</var>
+ <var id="ANGLES">_.map(SIDES, function(s) {
+ if (s === null) {
+ return randRange(20, 140);
+ }
+ return null;
+ })</var>
+ <var id="TRIANGLE">solveTriangle({
+ sides: SIDES.slice(),
+ angles: ANGLES.slice(),
+ sideLabels: SIDES.slice(),
+ angleLabels: _.map(ANGLES, function(a) {
+ return a == null ? a : a + "^\\circ";
+ }),
+ vertexLabels: ["A", "B", "C"]
+ })</var>
+ <var id="UNKNOWN">SIDES.indexOf(null)</var>
+ <var id="UNKNOWN_MEASURE">["BC", "AC", "AB"][UNKNOWN]</var>
+ <var id="UNKNOWN_SIDE">"abc"[UNKNOWN]</var>
+ <var id="KNOWN_SIDE_1">SIDES[(UNKNOWN + 1) % 3]</var>
+ <var id="KNOWN_SIDE_2">SIDES[(UNKNOWN + 2) % 3]</var>
+ <var id="SOLUTION">roundTo(1, sqrt(KNOWN_SIDE_1 * KNOWN_SIDE_1
+ + KNOWN_SIDE_2 * KNOWN_SIDE_2 - 2 * KNOWN_SIDE_1 *
+ KNOWN_SIDE_2 * cos(ANGLES[UNKNOWN] * PI / 180)))
+ </var>
+ <!-- Limit rotation such that the final triangle fills up more
+ width than height -->
+ <var id="ROTATION">
+ rand(2) ? randRange(-20, 20) : randRange(160, 200)
+ </var>
+ </div>
+
+ <p class="question">
+ Find <code><var>UNKNOWN_MEASURE</var></code>.
+ </p>
+
+ <div class="problem">
+ <p>
+ Round to the nearest tenth.
+ </p>
+ <div class="graphie" id="triangle">
+ TRIANGLE = addTriangle(_.extend(TRIANGLE, {
+ xPos: 1,
+ yPos: 1,
+ width: 10,
+ height: 6,
+ rot: ROTATION
+ }));
+ init({
+ range: [[0, TRIANGLE.width + 2],
+ [0, TRIANGLE.height + 2]]
+ });
+
+ TRIANGLE.draw();
+ </div>
+ </div>
+ <div class="solution" data-forms="decimal"
+ data-inexact data-max-error="0.09"><var>SOLUTION</var>
+ </div>
+
+ <div class="hints">
+ <div>
+ <p>You can use the law of cosines:</p>
+ <p><code>\qquad
+ \pink{<var>UNKNOWN_SIDE</var>}^2 \quad = \quad
+ \blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}^2 +
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}^2 -
+ 2\blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}
+ \space\cos(\pink{<var>"ABC"[UNKNOWN]</var>})
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels = _.map(SIDES, function(s, n) {
+ return s == null ? "abc"[n] :
+ "abc"[n] + " = " + s;
+ });
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.sideLabels[UNKNOWN] + "}";
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.angleLabels[UNKNOWN] + "}";
+ TRIANGLE.sideLabels[(UNKNOWN + 1) % 3] = "\\blue{" +
+ TRIANGLE.sideLabels[(UNKNOWN + 1) % 3] + "}";
+ TRIANGLE.sideLabels[(UNKNOWN + 2) % 3] = "\\green{" +
+ TRIANGLE.sideLabels[(UNKNOWN + 2) % 3] + "}";
+ TRIANGLE.color = GRAY;
+ TRIANGLE.draw();
+ </div>
+ </div>
+
+ <div>
+ <p>Plug in the known values:</p>
+ <p><code>\qquad
+ \pink{<var>UNKNOWN_SIDE</var>}^2 \quad = \quad
+ \blue{<var>KNOWN_SIDE_1</var>}^2 +
+ \green{<var>KNOWN_SIDE_2</var>}^2 -
+ 2(\blue{<var>KNOWN_SIDE_1</var>})
+ (\green{<var>KNOWN_SIDE_2</var>})
+ \space\cos(\pink{<var>ANGLES[UNKNOWN]</var>^\circ})
+ </code></p>
+ </div>
+
+ <div>
+ <p><code>\qquad
+ \pink{<var>UNKNOWN_SIDE</var>}^2 \quad = \quad
+ <var>
+ KNOWN_SIDE_1 * KNOWN_SIDE_1 +
+ KNOWN_SIDE_2 * KNOWN_SIDE_2
+ </var> - <var>
+ 2 * KNOWN_SIDE_1 * KNOWN_SIDE_2
+ </var>
+ \cdot\cos(\pink{<var>ANGLES[UNKNOWN]</var>^\circ})
+ </code></p>
+ </div>
+
+ <div>
+ <p>Evaluate and simplify the right side:</p>
+ <p><code>\qquad
+ \pink{<var>UNKNOWN_SIDE</var>}^2 \quad \approx \quad
+ <var>roundTo(9,
+ KNOWN_SIDE_1 * KNOWN_SIDE_1 +
+ KNOWN_SIDE_2 * KNOWN_SIDE_2 -
+ 2 * KNOWN_SIDE_1 * KNOWN_SIDE_2 *
+ cos(ANGLES[UNKNOWN] * Math.PI / 180))
+ </var>
+ </code></p>
+ </div>
+
+ <div>
+ <p>Take the positive square root of both sides (we only
+ need to worry about the positive square root because the
+ side of a triangle can't have negative length):</p>
+ <p><code>\qquad
+ \pink{<var>UNKNOWN_SIDE</var>} \quad \approx \quad
+ \sqrt{<var>roundTo(9,
+ KNOWN_SIDE_1 * KNOWN_SIDE_1 +
+ KNOWN_SIDE_2 * KNOWN_SIDE_2 -
+ 2 * KNOWN_SIDE_1 * KNOWN_SIDE_2 *
+ cos(ANGLES[UNKNOWN] * Math.PI / 180))
+ </var>}
+ </code></p>
+ </div>
+
+ <div>
+ <p>Evaluate and round to the nearest tenth:</p>
+ <p><code>
+ \qquad \pink{<var>UNKNOWN_MEASURE</var>}
+ \quad = \quad \pink{<var>UNKNOWN_SIDE</var>}
+ \quad \approx \quad <var>SOLUTION</var>
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ UNKNOWN_SIDE + " \\approx " + SOLUTION + "}";
+ TRIANGLE.draw();
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="sss-find-angle" data-calculator>
+ <div class="vars" data-ensure="
+ !TRIANGLE.isRight() &&
+ TRIANGLE.isScalene() &&
+ TRIANGLE.isNotDegenerate()">
+ <var id="SIDES" data-ensure="
+ SIDES[1] + SIDES[2] > SIDES[0] &&
+ SIDES[0] + SIDES[2] > SIDES[1] &&
+ SIDES[0] + SIDES[1] > SIDES[2]
+ ">randRange(5, 15, 3)</var>
+ <var id="TRIANGLE">solveTriangle({
+ sides: SIDES.slice(),
+ angles: [null, null, null],
+ sideLabels: SIDES.slice(),
+ vertexLabels: ["A", "B", "C"]
+ })</var>
+ <var id="UNKNOWN">randRange(0, 2)</var>
+ <var id="UNKNOWN_ANGLE">"ABC"[UNKNOWN]</var>
+ <var id="KNOWN_SIDE_1">SIDES[(UNKNOWN + 1) % 3]</var>
+ <var id="KNOWN_SIDE_2">SIDES[(UNKNOWN + 2) % 3]</var>
+ <var id="COS_UNKNOWN">fraction(
+ KNOWN_SIDE_1 * KNOWN_SIDE_1 +
+ KNOWN_SIDE_2 * KNOWN_SIDE_2 -
+ SIDES[UNKNOWN] * SIDES[UNKNOWN],
+ 2 * KNOWN_SIDE_1 * KNOWN_SIDE_2)
+ </var>
+ <var id="SOLUTION">roundTo(1, acos(
+ (KNOWN_SIDE_1 * KNOWN_SIDE_1 +
+ KNOWN_SIDE_2 * KNOWN_SIDE_2 -
+ SIDES[UNKNOWN] * SIDES[UNKNOWN]) / (2 *
+ KNOWN_SIDE_1 * KNOWN_SIDE_2)) /
+ PI * 180)
+ </var>
+ <!-- Limit rotation such that the final triangle fills up more
+ width than height -->
+ <var id="ROTATION">
+ rand(2) ? randRange(-20, 20) : randRange(160, 200)
+ </var>
+ </div>
+
+ <p class="question">
+ Find <code>m\angle <var>UNKNOWN_ANGLE</var></code>.
+ </p>
+
+ <div class="problem">
+ <p>
+ Round to the nearest degree.
+ </p>
+ <div class="graphie" id="triangle">
+ TRIANGLE = addTriangle(_.extend(TRIANGLE, {
+ xPos: 1,
+ yPos: 1,
+ width: 10,
+ height: 6,
+ rot: ROTATION
+ }));
+ init({
+ range: [[0, TRIANGLE.width + 2],
+ [0, TRIANGLE.height + 2]]
+ });
+
+ TRIANGLE.draw();
+ </div>
+ </div>
+ <div class="solution" data-type="multiple">
+ <span class="sol" data-forms="integer" data-inexact
+ data-max-error="0.5"><var>SOLUTION</var>
+ </span><code>\Large{^\circ}</code>
+ </div>
+
+ <div class="hints">
+ <div>
+ <p>You can use the law of cosines:</p>
+ <p><code>\qquad
+ \pink{<var>"abc"[UNKNOWN]</var>}^2 \quad = \quad
+ \blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}^2 +
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}^2 -
+ 2\blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}
+ \space\cos(\pink{<var>"ABC"[UNKNOWN]</var>})
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels = _.map(SIDES, function(s, n) {
+ return "abc"[n] + " = " + s;
+ });
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.sideLabels[UNKNOWN] + "}";
+ TRIANGLE.vertexLabels[UNKNOWN] = "\\pink{" +
+ "ABC"[UNKNOWN] + "}";
+ TRIANGLE.sideLabels[(UNKNOWN + 1) % 3] = "\\blue{" +
+ TRIANGLE.sideLabels[(UNKNOWN + 1) % 3] + "}";
+ TRIANGLE.sideLabels[(UNKNOWN + 2) % 3] = "\\green{" +
+ TRIANGLE.sideLabels[(UNKNOWN + 2) % 3] + "}";
+ TRIANGLE.color = GRAY;
+ TRIANGLE.draw();
+ </div>
+ </div>
+
+ <div>
+ <p>
+ Rewrite the law of cosines to solve for
+ <code>\cos(\pink{<var>"ABC"[UNKNOWN]</var>})</code>:
+ </p>
+ <p><code>\qquad
+ \cos(\pink{<var>"ABC"[UNKNOWN]</var>}) \quad = \quad
+ \dfrac{
+ \blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}^2 +
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}^2 -
+ \pink{<var>"abc"[UNKNOWN]</var>}^2
+ }{2\blue{<var>"abc"[(UNKNOWN + 1) % 3]</var>}
+ \green{<var>"abc"[(UNKNOWN + 2) % 3]</var>}}
+ </code></p>
+ </div>
+
+ <div>
+ <p>Plug in the known values:</p>
+ <p><code>\qquad
+ \cos(\pink{<var>"ABC"[UNKNOWN]</var>}) \quad = \quad
+ \dfrac{
+ \blue{<var>KNOWN_SIDE_1</var>}^2 +
+ \green{<var>KNOWN_SIDE_2</var>}^2 -
+ \pink{<var>SIDES[UNKNOWN]</var>}^2
+ }{2(\blue{<var>KNOWN_SIDE_1</var>})
+ (\green{<var>KNOWN_SIDE_2</var>})}
+ </code></p>
+ </div>
+
+ <div>
+ <p>Simplify the right side:</p>
+ <p><code>\qquad
+ \cos(\pink{<var>"ABC"[UNKNOWN]</var>}) \quad = \quad
+ <var>COS_UNKNOWN</var>
+ </code></p>
+ </div>
+
+ <div>
+ <p>
+ Evaluate the inverse cosine to find
+ <code>m\angle <var>UNKNOWN_ANGLE</var></code>
+ and round to the nearest degree:
+ </p>
+ <p><code>\qquad
+ \pink{m\angle <var>UNKNOWN_ANGLE</var>}
+ \quad = \quad \cos^{-1}\left(<var>COS_UNKNOWN</var>\right)
+ \quad \approx \quad \pink{<var>SOLUTION</var>^\circ}
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{" +
+ SOLUTION + "^\\circ}";
+ TRIANGLE.draw();
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</html>
View
308 exercises/law_of_sines.html
@@ -0,0 +1,308 @@
+<!DOCTYPE html>
+<html data-require="math graphie graphie-geometry angles">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>Law of sines</title>
+ <script src="../khan-exercise.js"></script>
+</head>
+<body>
+<div class="exercise">
+ <div class="problems">
+
+ <div id="find-side" data-calculator>
+ <div class="vars" data-ensure="ANGLE1 !== 90 ||
+ ANGLE2 !== 90 ||
+ ANGLE1 + ANGLE2 !== 90">
+ <var id="ANGLE1">randRange(20, 140)</var>
+ <var id="ANGLE2">randRange(20, 160 - ANGLE1)</var>
+ <var id="SIDELEN">randRange(5, 25)</var>
+ <var id="KNOWN, UNKNOWN">shuffle([0, 1, 2])</var>
+ <var id="SIDES">[
+ KNOWN === 0 ? SIDELEN : null,
+ KNOWN === 1 ? SIDELEN : null,
+ KNOWN === 2 ? SIDELEN : null
+ ]</var>
+ <var id="GIVE_OPPOSITE">!!rand(2)</var>
+ <!-- ANGLE1 is defined as always opposite the known side.
+ ANGLE2 is opposite to the unknwon side if GIVE_OPPOSITE is
+ true, otherwise ANGLE2 is adjacent the known side -->
+ <var id="ANGLES">[
+ KNOWN === 0 ? ANGLE1 :
+ (UNKNOWN === 0 ^ GIVE_OPPOSITE ? null : ANGLE2),
+ KNOWN === 1 ? ANGLE1 :
+ (UNKNOWN === 1 ^ GIVE_OPPOSITE ? null : ANGLE2),
+ KNOWN === 2 ? ANGLE1 :
+ (UNKNOWN === 2 ^ GIVE_OPPOSITE ? null : ANGLE2)
+ ]</var>
+ <var id="TRIANGLE">solveTriangle({
+ sides: SIDES.slice(),
+ angles: ANGLES.slice(),
+ sideLabels: SIDES.slice(),
+ angleLabels: _.map(ANGLES, function(a) {
+ return a == null ? a : a + "^\\circ";
+ }),
+ vertexLabels: ["A", "B", "C"]
+ })</var>
+ <var id="UNKNOWN_MEASURE">["BC", "AC", "AB"][UNKNOWN]</var>
+ <var id="SOLUTION">roundTo(1,TRIANGLE.sides[UNKNOWN])</var>
+ <!-- Limit rotation such that the final triangle fills up more
+ width than height -->
+ <var id="ROTATION">
+ rand(2) ? randRange(-20, 20) : randRange(160, 200)
+ </var>
+ </div>
+
+ <p class="question">
+ Find <code><var>UNKNOWN_MEASURE</var></code>.
+ </p>
+
+ <div class="problem">
+ <p>
+ Round to the nearest tenth.
+ </p>
+ <div class="graphie" id="triangle">
+ TRIANGLE = addTriangle(_.extend(TRIANGLE, {
+ xPos: 1,
+ yPos: 1,
+ width: 10,
+ height: 6,
+ rot: ROTATION
+ }));
+ init({
+ range: [[0, TRIANGLE.width + 2],
+ [0, TRIANGLE.height + 2]]
+ });
+
+ TRIANGLE.draw();
+
+ </div>
+ </div>
+ <div class="solution" data-forms="decimal"
+ data-inexact data-max-error="0.09"><var>SOLUTION</var>
+ </div>
+
+ <div class="hints">
+ <div>
+ <p>You can use the law of sines:</p>
+ <p><code>
+ \qquad \dfrac{BC}{\sin(m\angle A)} \quad =
+ \quad \dfrac{AC}{\sin(m\angle B)} \quad =
+ \quad \dfrac{AB}{\sin(m\angle C)}
+ </code></p>
+ </div>
+
+ <div data-if="!GIVE_OPPOSITE">
+ <p>
+ Fill in the unknown angle using the fact that the
+ angles of a triangle always sum to
+ <code>180^\circ</code>.
+ </p>
+ <p><code>
+ \qquad 180^\circ - <var>ANGLE1</var>^\circ -
+ <var>ANGLE2</var>^\circ \quad = \quad
+ \pink{<var>TRIANGLE.angles[UNKNOWN]</var>^\circ}
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.angles[UNKNOWN] + "^\\circ}";
+ TRIANGLE.draw();
+ </div>
+ </div>
+
+ <div>
+ <p>
+ Set up a useful relationship using the law of sines:
+ </p>
+ <p><code>
+ \qquad \dfrac{\pink{<var>UNKNOWN_MEASURE</var>}}{\sin(
+ \pink{<var>TRIANGLE.angles[UNKNOWN]</var>^\circ})}
+ \quad = \quad\dfrac{\blue{
+ <var>TRIANGLE.sides[KNOWN]</var>}}{\sin(\blue{
+ <var>TRIANGLE.angles[KNOWN]</var>^\circ})}
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels[KNOWN] = "\\blue{" +
+ TRIANGLE.sides[KNOWN] + "}";
+ TRIANGLE.angleLabels[KNOWN] = "\\blue{" +
+ TRIANGLE.angles[KNOWN] + "^\\circ}";
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ UNKNOWN_MEASURE + "}";
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.angles[UNKNOWN] + "^\\circ}";
+ TRIANGLE.color = GRAY;
+ TRIANGLE.draw();
+ </div>
+ </div>
+
+ <div>
+ <p>
+ Solve for the unknown side:
+ </p>
+ <p><code>
+ \qquad \pink{<var>UNKNOWN_MEASURE</var>} \quad = \quad
+ \dfrac{\blue{<var>TRIANGLE.sides[KNOWN]</var>}
+ \cdot
+ \sin(\pink{<var>TRIANGLE.angles[UNKNOWN]</var>^\circ})
+ }{
+ \sin(\blue{<var>TRIANGLE.angles[KNOWN]</var>^\circ})}
+ </code></p>
+ </div>
+
+ <div>
+ <p>Evaluate and round to the nearest tenth:</p>
+ <p><code>
+ \qquad \pink{<var>UNKNOWN_MEASURE</var>}
+ \quad \approx \quad <var>SOLUTION</var>
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ SOLUTION + "}";
+ TRIANGLE.draw();
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="find-angle" data-calculator>
+ <div class="vars" data-apply="replace" data-ensure="
+ !TRIANGLE.isRight() && TRIANGLE.isScalene">
+ <var id="SIDES" data-ensure="
+ SIDES[1] + SIDES[2] > SIDES[0] &&
+ SIDES[0] + SIDES[2] > SIDES[1] &&
+ SIDES[0] + SIDES[1] > SIDES[2]
+ ">randRange(5, 15, 3)</var>
+ <var id="KNOWN, UNKNOWN">shuffle([0, 1, 2])</var>
+ <var id="TRIANGLE">solveTriangle({
+ sides: SIDES.slice(),
+ angles: [null, null, null],
+ sideLabels: SIDES.slice(),
+ vertexLabels: ["A", "B", "C"]
+ })</var>
+ <var id="ANGLES">_.map(TRIANGLE.angles, round)</var>
+ <var id="UNKNOWN_MEASURE">"ABC"[UNKNOWN]</var>
+ <var id="SOLUTION">round(Math.asin((TRIANGLE.sides[UNKNOWN] *
+ sin(ANGLES[KNOWN] * Math.PI / 180)) /
+ TRIANGLE.sides[KNOWN]) / Math.PI * 180)
+ </var>
+ <!-- Limit rotation such that the final triangle fills up more
+ width than height -->
+ <var id="ROTATION">
+ rand(2) ? randRange(-20, 20) : randRange(160, 200)
+ </var>
+ </div>
+
+ <p class="question">
+ Find <code>m\angle <var>UNKNOWN_MEASURE</var></code>.
+ </p>
+
+ <div class="problem">
+ <p>
+ Round to the nearest degree.
+ </p>
+ <div class="graphie" id="triangle">
+ TRIANGLE.angleLabels = [
+ KNOWN === 0 ? ANGLES[0] + "^\\circ" : null,
+ KNOWN === 1 ? ANGLES[1] + "^\\circ" : null,
+ KNOWN === 2 ? ANGLES[2] + "^\\circ" : null
+ ];
+ TRIANGLE = addTriangle(_.extend(TRIANGLE, {
+ xPos: 1,
+ yPos: 1,
+ width: 10,
+ height: 6,
+ rot: ROTATION
+ }));
+ init({
+ range: [[0, TRIANGLE.width + 2],
+ [0, TRIANGLE.height + 2]]
+ });
+
+ TRIANGLE.draw();
+ </div>
+ </div>
+ <div class="solution" data-type="multiple">
+ <span class="sol" data-forms="integer" data-inexact
+ data-max-error="0.5"><var>SOLUTION</var>
+ </span><code>\Large{^\circ}</code>
+ </div>
+
+ <div class="hints">
+ <div>
+ <p>You can use the law of sines:</p>
+ <p><code>
+ \qquad \dfrac{BC}{\sin(m\angle A)} \quad =
+ \quad \dfrac{AC}{\sin(m\angle B)} \quad =
+ \quad \dfrac{AB}{\sin(m\angle C)}
+ </code></p>
+ </div>
+
+ <div>
+ <p>
+ Set up a useful relationship using the law of sines:
+ </p>
+ <p><code>
+ \qquad \dfrac{\pink{<var>SIDES[UNKNOWN]</var>}}{\sin(
+ \pink{m\angle <var>UNKNOWN_MEASURE</var>})}
+ \quad = \quad\dfrac{\blue{
+ <var>TRIANGLE.sides[KNOWN]</var>}}{\sin(\blue{
+ <var>ANGLES[KNOWN]</var>^\circ})}
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.sideLabels[KNOWN] = "\\blue{" +
+ TRIANGLE.sides[KNOWN] + "}";
+ TRIANGLE.angleLabels[KNOWN] = "\\blue{" +
+ ANGLES[KNOWN] + "^\\circ}";
+ TRIANGLE.sideLabels[UNKNOWN] = "\\pink{" +
+ TRIANGLE.sides[UNKNOWN] + "}";
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{?}";
+ TRIANGLE.color = GRAY;
+ TRIANGLE.draw();
+ </div>
+ </div>
+
+ <div>
+ <p>
+ Solve for the sine of the unknown angle:
+ </p>
+ <p><code>
+ \qquad \sin(\pink{m\angle <var>UNKNOWN_MEASURE</var>})
+ \quad = \quad
+ \dfrac{\pink{<var>TRIANGLE.sides[UNKNOWN]</var>}
+ \cdot
+ \sin(\blue{<var>ANGLES[KNOWN]</var>^\circ})
+ }{\blue{<var>TRIANGLE.sides[KNOWN]</var>}}
+ </code></p>
+ </div>
+
+ <div>
+ <p>Evaluate the right side:</p>
+ <p><code>
+ \qquad \sin(\pink{m\angle <var>UNKNOWN_MEASURE</var>})
+ \quad \approx \quad
+ <var>roundTo(9, (TRIANGLE.sides[UNKNOWN] *
+ sin(ANGLES[KNOWN] * Math.PI / 180)) /
+ TRIANGLE.sides[KNOWN])</var>
+ </code></p>
+ </div>
+
+ <div>
+ <p>
+ Evaluate the inverse sine to find
+ <code>m\angle <var>UNKNOWN_MEASURE</var></code> and round to
+ the nearest degree:
+ </p>
+ <p><code>
+ \qquad \pink{m\angle <var>UNKNOWN_MEASURE</var>}
+ \quad \approx \quad <var>SOLUTION</var>^\circ
+ </code></p>
+ <div class="graphie" data-update="triangle">
+ TRIANGLE.angleLabels[UNKNOWN] = "\\pink{" +
+ SOLUTION + "^\\circ}";
+ TRIANGLE.draw();
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</html>
View
3  khan-exercise.js
@@ -316,7 +316,8 @@ var Khan = (function() {
"math-model": ["ast"],
"simplify": ["math-model", "ast", "expr-helpers", "expr-normal-form", "steps-helpers"],
"congruency": ["angles", "interactive"],
- "graphie-3d": ["graphie", "matrix"]
+ "graphie-3d": ["graphie", "matrix"],
+ "graphie-geometry": ["graphie", "matrix"]
},
warnTimeout: function() {
View
268 utils/graphie-geometry.js
@@ -970,3 +970,271 @@ $.extend(KhanUtil, {
return shape;
}
});
+
+
+// The following triangley code creates hopefully more legible triangles
+// TODO(eater): Collapse this stuff into the existing triangle code above
+// without breaking a bunch of stuff (and refactor things so
+// Quadrilateral no longer inherits from Triangle)
+//
+// angles[0]
+// /\
+// / \
+// sides[1] / \ sides[2]
+// / \
+// /________\
+// angles[2] sides[0] angles[1]
+//
+
+// The following solves the triangle by filling in any side and angle measures
+// that were not included.
+//
+// The sides and angles included must unambiguously specify one triangle. duh.
+// If not, behavior is undefined.
+//
+KhanUtil.solveTriangle = function(triangle) {
+ var sides = triangle.sides;
+ var angles = triangle.angles;
+ var numSides = _.reduce(sides, function(n, side) {
+ return n + (typeof side === "number" ? 1 : 0);
+ }, 0);
+ var numAngles = _.reduce(angles, function(n, angle) {
+ return n + (typeof angle === "number" ? 1 : 0);
+ }, 0);
+
+ // If we have 2 sides, we must have the angle opposite the unknown side.
+ // (SAS is the only valid postulate in this case)
+ // We can use the law of cosines to find the third side
+ if (numSides === 2) {
+ var missingSide = sides.indexOf(null);
+ var side1 = sides[(missingSide + 1) % 3];
+ var side2 = sides[(missingSide + 2) % 3];
+ sides[missingSide] = Math.sqrt(side1 * side1 + side2 * side2 - 2 *
+ side1 * side2 * Math.cos(angles[missingSide] * Math.PI / 180));
+ numSides = 3;
+ }
+
+ // If we have all three sides, we can use the law of cosines to find all
+ // the angles
+ if (numSides === 3) {
+ // Use law of cosines to find all the angles
+ angles = _.map(angles, function(angle, n) {
+ var oppSide = sides[n];
+ var adjSide1 = sides[(n + 1) % 3];
+ var adjSide2 = sides[(n + 2) % 3];
+ return Math.acos((adjSide1 * adjSide1 + adjSide2 * adjSide2 -
+ oppSide * oppSide) / (2 * adjSide1 * adjSide2));
+ });
+ angles = _.map(angles, KhanUtil.toDegrees);
+ }
+
+ // If we have 2 angles, we can easily fill in the third angle
+ if (numAngles === 2) {
+ var missingAngle = angles.indexOf(null);
+ angles[missingAngle] = 180 - angles[(missingAngle + 1) % 3] -
+ angles[(missingAngle + 2) % 3];
+ numAngles = 3;
+ }
+
+ // 3 angles and 1 side is enough to figure out the rest of the sides using
+ // the law of sines
+ if (numAngles === 3 && numSides >= 1) {
+ var knownSide = sides.indexOf(sides[0] || sides[1] || sides[2]);
+ sides[(knownSide + 1) % 3] = (sides[knownSide] *
+ Math.sin(angles[(knownSide + 1) % 3] * Math.PI / 180))
+ / Math.sin(angles[knownSide] * Math.PI / 180);
+ sides[(knownSide + 2) % 3] = (sides[knownSide] *
+ Math.sin(angles[(knownSide + 2) % 3] * Math.PI / 180))
+ / Math.sin(angles[knownSide] * Math.PI / 180);
+ }
+
+ triangle.sides = sides;
+ triangle.angles = angles;
+
+ triangle.isRight = function() {
+ return (this.angles[0] === 90 || this.angles[1] === 90 ||
+ this.angles[2] === 90);
+ };
+
+ triangle.isScalene = function() {
+ return (this.angles[0] !== this.angles[1] &&
+ this.angles[1] !== this.angles[2] &&
+ this.angles[0] !== this.angles[2]);
+ }
+
+ triangle.isNotDegenerate = function() {
+ return (this.sides[1] + this.sides[2] > this.sides[0] &&
+ this.sides[0] + this.sides[2] > this.sides[1] &&
+ this.sides[0] + this.sides[1] > this.sides[2]);
+ };
+
+ return triangle;
+};
+
+
+KhanUtil.addTriangle = function(triangle) {
+ triangle = $.extend({
+ sides: [],
+ angles: [],
+ points: [],
+ sideLabels: [],
+ angleLabels: [],
+ vertexLabels: [],
+ labels: [],
+ rot: 0,
+ xPos: 0,
+ yPos: 0,
+ width: 10,
+ height: 10,
+ color: KhanUtil.BLUE
+ }, triangle);
+
+ var getDistance = function(point1, point2) {
+ return Math.sqrt((point1[0] - point2[0]) * (point1[0] - point2[0])
+ + (point1[1] - point2[1]) * (point1[1] - point2[1]));
+ };
+
+ var rotatePoint = function(point, angle) {
+ var matrix = KhanUtil.makeMatrix([
+ [Math.cos(angle), -Math.sin(angle), 0],
+ [Math.sin(angle), Math.cos(angle), 0],
+ [0, 0, 1]
+ ]);
+ var vector = KhanUtil.makeMatrix([[point[0]], [point[1]], [1]]);
+ var prod = KhanUtil.matrixMult(matrix, vector);
+ return [prod[0], prod[1]];
+ };
+
+ var findCenterPoints = function(triangle, points) {
+ var Ax = points[0][0];
+ var Ay = points[0][1];
+ var Bx = points[1][0];
+ var By = points[1][1];
+ var Cx = points[2][0];
+ var Cy = points[2][1];
+ var D = 2 * (Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By));
+ var a = triangle.sides[0];
+ var b = triangle.sides[1];
+ var c = triangle.sides[2];
+ var P = a + b + c;
+ var x1 = (a * Ax + b * Bx + c * Cx) / P;
+ var y1 = (a * Ay + b * By + c * Cy) / P;
+ var x = ((Ay * Ay + Ax * Ax) * (By - Cy) + (By * By + Bx * Bx) *
+ (Cy - Ay) + (Cy * Cy + Cx * Cx) * (Ay - By)) / D;
+ var y = ((Ay * Ay + Ax * Ax) * (Cx - Bx) + (By * By + Bx * Bx) *
+ (Ax - Cx) + (Cy * Cy + Cx * Cx) * (Bx - Ax)) / D;
+ return {
+ circumCenter: [x, y],
+ centroid: [1 / 3 * (Ax + Bx + Cx), 1 / 3 * (Ay + By + Cy)],
+ inCenter: [x1, y1]
+ };
+ };
+
+ triangle.draw = function() {
+ var graphie = KhanUtil.currentGraph;
+ if (triangle.set != null) {
+ triangle.set.remove();
+ }
+ _.each(triangle.labels, function(lbl) {
+ lbl.remove();
+ });
+ triangle.set = graphie.raphael.set();
+ triangle.set.push(graphie.path(triangle.points.concat([true]),{
+ stroke: triangle.color
+ }));
+
+ var centerPoints = findCenterPoints(triangle, triangle.points);
+ _(3).times(function(i) {
+ if (triangle.angleLabels[i] != null) {
+ var ang = Math.atan2(centerPoints.inCenter[1] -
+ triangle.points[i][1], centerPoints.inCenter[0] -
+ triangle.points[i][0]);
+
+ // The angle measure label needs to be further from the vertex
+ // for small angles and closer for large angles. This is an
+ // empirically determined formula for figuring out how far.
+ var labelDist = (3.51470560176242 - 0.5687298702748785) *
+ Math.exp(-0.037587715462826674 * triangle.angles[i]) +
+ 0.5687298702748785;
+
+ triangle.labels.push(graphie.label([
+ triangle.points[i][0] + Math.cos(ang) * labelDist,
+ triangle.points[i][1] + Math.sin(ang) * labelDist],
+ triangle.angleLabels[i], "center"));
+ }
+ });
+
+ _(3).times(function(i) {
+ if (triangle.sideLabels[i] != null) {
+ var x = (triangle.points[(i + 1) % 3][0] +
+ triangle.points[(i + 2) % 3][0]) / 2;
+ var y = (triangle.points[(i + 1) % 3][1] +
+ triangle.points[(i + 2) % 3][1]) / 2;
+ var ang;
+ var labelDist = 0.4;
+ if (triangle.angles[i] < 90) {
+ ang = Math.atan2(y - centerPoints.circumCenter[1],
+ x - centerPoints.circumCenter[0]);
+ } else if (triangle.angles[i] > 90) {
+ ang = Math.atan2(centerPoints.circumCenter[1] - y,
+ centerPoints.circumCenter[0] - x);
+ } else {
+ ang = Math.atan2(y - centerPoints.centroid[1],
+ x - centerPoints.centroid[0]);
+ }
+
+ triangle.labels.push(graphie.label([x, y],
+ triangle.sideLabels[i], ang));
+ }
+ });
+
+ _(3).times(function(i) {
+ if (triangle.vertexLabels[i] != null) {
+ var ang = Math.atan2(triangle.points[i][1] -
+ centerPoints.inCenter[1], triangle.points[i][0] -
+ centerPoints.inCenter[0]);
+ var labelDist = 0.4;
+
+ var lbl = graphie.label([
+ triangle.points[i][0] + Math.cos(ang) * labelDist,
+ triangle.points[i][1] + Math.sin(ang) * labelDist],
+ triangle.vertexLabels[i], "center");
+ lbl.css("color", triangle.color);
+ triangle.labels.push(lbl);
+ }
+ });
+ };
+
+ triangle.points[2] = [0, 0];
+ triangle.points[1] = [triangle.sides[0], 0];
+ triangle.points[0] = [Math.cos(triangle.angles[2] * Math.PI / 180) *
+ triangle.sides[1], Math.sin(triangle.angles[2] * Math.PI / 180) *
+ triangle.sides[1]];
+
+ // Rotate the triangle
+ triangle.points[0] = rotatePoint(triangle.points[0], triangle.rot *
+ Math.PI / 180);
+ triangle.points[1] = rotatePoint(triangle.points[1], triangle.rot *
+ Math.PI / 180);
+ triangle.points[2] = rotatePoint(triangle.points[2], triangle.rot *
+ Math.PI / 180);
+
+ // Scale and translate the triangle such that it fits within the
+ // specified width/height constraint and is positioned at xPos, yPos
+ var minX = _.min(_.map(triangle.points, function(p) { return p[0]; }));
+ var maxX = _.max(_.map(triangle.points, function(p) { return p[0]; }));
+ var minY = _.min(_.map(triangle.points, function(p) { return p[1]; }));
+ var maxY = _.max(_.map(triangle.points, function(p) { return p[1]; }));
+ var xScale = triangle.width / (maxX - minX);
+ var yScale = triangle.height / (maxY - minY);
+ var scale = _.min([xScale, yScale]);
+ triangle.width = (maxX - minX) * scale;
+ triangle.height = (maxY - minY) * scale;
+
+ triangle.points = _.map(triangle.points, function(p) {
+ return [(p[0] - minX) * scale + triangle.xPos,
+ (p[1] - minY) * scale + triangle.yPos];
+ });
+
+ return triangle;
+};
View
169 utils/interactive.js
@@ -1120,8 +1120,175 @@ $.extend(KhanUtil, {
return sorter;
- }
+ },
+
+ // center: movable point
+ // radius: int
+ // circ: graphie circle
+ // perim: invisible mouse target for dragging/changing radius
+ addCircleGraph: function(options) {
+ var graphie = KhanUtil.currentGraph;
+ var circle = $.extend({
+ center: [0, 0],
+ radius: 2
+ }, options);
+
+ circle.centerPoint = KhanUtil.addMovablePoint({
+ graph: graphie,
+ coord: circle.center,
+ normalStyle: {
+ stroke: KhanUtil.BLUE,
+ fill: KhanUtil.BLUE
+ },
+ snapX: 0.5,
+ snapY: 0.5
+ });
+ circle.circ = graphie.circle(circle.center, circle.radius, {
+ stroke: KhanUtil.BLUE,
+ fill: KhanUtil.ORANGE,
+ fillOpacity: 0
+ });
+ circle.perim = graphie.mouselayer.circle(
+ graphie.scalePoint(circle.center)[0],
+ graphie.scalePoint(circle.center)[1],
+ graphie.scaleVector(circle.radius)[0]).attr({
+ "stroke-width": 20,
+ "opacity": 0.0
+ });
+
+ $(circle.centerPoint.mouseTarget[0]).on(
+ "vmouseover vmouseout", function(event) {
+ if (circle.centerPoint.highlight) {
+ circle.circ.animate({
+ stroke: KhanUtil.ORANGE,
+ "fill-opacity": 0.05
+ }, 50);
+ } else {
+ circle.circ.animate({
+ stroke: KhanUtil.BLUE,
+ "fill-opacity": 0
+ }, 50);
+ }
+ });
+
+ circle.toFront = function() {
+ circle.circ.toFront();
+ circle.perim.toFront();
+ circle.centerPoint.visibleShape.toFront();
+ circle.centerPoint.mouseTarget.toFront();
+ };
+
+ circle.centerPoint.onMove = function(x, y) {
+ circle.toFront();
+ circle.circ.attr({
+ cx: graphie.scalePoint(x)[0],
+ cy: graphie.scalePoint(y)[1],
+ });
+ circle.perim.attr({
+ cx: graphie.scalePoint(x)[0],
+ cy: graphie.scalePoint(y)[1],
+ });
+ };
+
+ circle.centerPoint.onMoveEnd = function(x, y) {
+ circle.center = [x, y];
+ };
+
+ // circle.setCenter(x, y) moves the circle to the specified
+ // x, y coordinate as if the user had dragged it there.
+ circle.setCenter = function(x, y) {
+ circle.centerPoint.setCoord([x, y]);
+ circle.centerPoint.onMove(x, y);
+ circle.center = [x, y];
+ };
+
+ // circle.setRadius(r) sets the circle's radius to the specified
+ // value as if the user had dragged it there.
+ circle.setRadius = function(r) {
+ circle.radius = r;
+ circle.perim.attr({
+ r: graphie.scaleVector(r)[0],
+ });
+ circle.circ.attr({
+ rx: graphie.scaleVector(r)[0],
+ ry: graphie.scaleVector(r)[1]
+ });
+ };
+
+ $(circle.perim[0]).css("cursor", "move");
+ $(circle.perim[0]).on(
+ "vmouseover vmouseout vmousedown", function(event) {
+ if (event.type === "vmouseover") {
+ circle.highlight = true;
+ if (!KhanUtil.dragging) {
+ circle.circ.animate({
+ stroke: KhanUtil.ORANGE,
+ "fill-opacity": 0.05
+ }, 50);
+ circle.centerPoint.visibleShape.animate({
+ stroke: KhanUtil.ORANGE,
+ fill: KhanUtil.ORANGE
+ }, 50);
+ }
+
+ } else if (event.type === "vmouseout") {
+ circle.highlight = false;
+ if (!circle.dragging) {
+ circle.circ.animate({
+ stroke: KhanUtil.BLUE,
+ "fill-opacity": 0
+ }, 50);
+ circle.centerPoint.visibleShape.animate({
+ stroke: KhanUtil.BLUE,
+ fill: KhanUtil.BLUE
+ }, 50);
+ }
+
+ } else if (event.type === "vmousedown" &&
+ (event.which === 1 || event.which === 0)) {
+ event.preventDefault();
+ circle.toFront();
+
+ $(document).on("vmousemove vmouseup", function(event) {
+ event.preventDefault();
+ circle.dragging = true;
+ KhanUtil.dragging = true;
+
+ if (event.type === "vmousemove") {
+ // mouse{X|Y} are in pixels relative to the SVG
+ var mouseX = event.pageX - $(graphie.raphael.
+ canvas.parentNode).offset().left;
+ var mouseY = event.pageY - $(graphie.raphael.
+ canvas.parentNode).offset().top;
+ // can't go beyond 10 pixels from the edge
+ mouseX = Math.max(10, Math.min(graphie.xpixels - 10,
+ mouseX));
+ mouseY = Math.max(10, Math.min(graphie.ypixels - 10,
+ mouseY));
+
+ // coord{X|Y} are the scaled coordinate values
+ var coordX = mouseX / graphie.scale[0] +
+ graphie.range[0][0];
+ var coordY = graphie.range[1][1] - mouseY /
+ graphie.scale[1];
+
+ var radius = KhanUtil.getDistance(
+ circle.centerPoint.coord, [coordX, coordY]);
+ radius = Math.max(1,
+ Math.round(radius / 0.5) * 0.5);
+ circle.setRadius(radius);
+ } else if (event.type === "vmouseup") {
+ $(document).off("vmousemove vmouseup");
+ circle.dragging = false;
+ KhanUtil.dragging = false;
+ }
+ });
+ }
+ });
+
+ return circle;
+ }
});

0 comments on commit 08abd64

Please sign in to comment.
Something went wrong with that request. Please try again.