Skip to content
Browse files

Add exercises: Law of sines and Law of cosines

Test Plan: Tested multiple seeds for each new problem type outside devappserver

Reviewers: stephanie, yunfangjuan

Reviewed By: yunfangjuan

Differential Revision: http://phabricator.khanacademy.org/D776
  • Loading branch information...
1 parent 96591ea commit a5882e34cf6d6b534147013653d1af0655e63cf4 @beneater beneater committed Oct 2, 2012
Showing with 909 additions and 1 deletion.
  1. +331 −0 exercises/law_of_cosines.html
  2. +308 −0 exercises/law_of_sines.html
  3. +2 −1 khan-exercise.js
  4. +268 −0 utils/graphie-geometry.js
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;
+};

0 comments on commit a5882e3

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