From ec906e8a2b626153050af5b4a55995f7e0ed15fb Mon Sep 17 00:00:00 2001 From: Duane Nykamp Date: Sat, 25 Feb 2023 04:38:08 +0000 Subject: [PATCH] invertible and symbolic line segment length --- .../DoenetML/tagSpecific/linesegment.cy.js | 64 +++++++- src/Core/Core.js | 24 ++- src/Core/components/LineSegment.js | 137 ++++++++++++++---- 3 files changed, 194 insertions(+), 31 deletions(-) diff --git a/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js b/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js index 73547c12a8..7c3da58e68 100644 --- a/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js +++ b/cypress/e2e/DoenetML/tagSpecific/linesegment.cy.js @@ -3362,6 +3362,10 @@ describe('LineSegment Tag Tests', function () { + + + +

`}, "*"); }); @@ -3372,7 +3376,9 @@ describe('LineSegment Tag Tests', function () { let t2x = 7, t2y = -2; let len = Math.sqrt((t1y - t2y) ** 2 + (t1x - t2x) ** 2); - cy.get('#\\/length').should('contain.text', String(Math.round(len*10**9)/10**9)) + cy.get('#\\/length').should('contain.text', String(Math.round(len * 10 ** 9) / 10 ** 9)) + cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(3,4)'); + cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−2)'); cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); @@ -3391,17 +3397,47 @@ describe('LineSegment Tag Tests', function () { }) cy.get('#\\/length').should('contain.text', String(len)) + cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,3)'); + cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−2)'); }) cy.window().then(async (win) => { let stateVariables = await win.returnAllStateVariables1(); - expect(Math.abs(stateVariables['/l'].stateValues.length)).eq(len); + expect(stateVariables['/l'].stateValues.length).eq(len); + }) + + + cy.get('#\\/milength textarea').type("{end}{backspace}10{enter}", { force: true }); + + cy.get('#\\/length').should('contain.text', '10') + cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)'); + cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−4.5)'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables['/l'].stateValues.length).eq(10); + }) + + + cy.log("ignore requested negative length"); + cy.get('#\\/milength textarea').type("{end}{backspace}{backspace}-3{enter}", { force: true }); + cy.get("#\\/bi").click(); + cy.get('#\\/bi2').should('have.text', 'true'); // so know that core has responded to both requests + + cy.get('#\\/length').should('contain.text', '10') + cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)'); + cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(7,−4.5)'); + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(stateVariables['/l'].stateValues.length).eq(10); }) cy.window().then(async (win) => { + t1y = 5.5; t2x = -9; t2y = 5; len = Math.sqrt((t1y - t2y) ** 2 + (t1x - t2x) ** 2); @@ -3411,8 +3447,9 @@ describe('LineSegment Tag Tests', function () { args: { x: t2x, y: t2y } }) - cy.get('#\\/length').should('contain.text', String(Math.round(len*10**8)/10**8)) - + cy.get('#\\/length').should('contain.text', String(Math.round(len * 10 ** 8) / 10 ** 8)) + cy.get('#\\/Ap .mjx-mrow').eq(0).should('have.text', '(7,5.5)'); + cy.get('#\\/Bp .mjx-mrow').eq(0).should('have.text', '(−9,5)'); }) cy.window().then(async (win) => { @@ -3421,6 +3458,25 @@ describe('LineSegment Tag Tests', function () { }) }); + it('lineSegment symbolic length', () => { + cy.window().then(async (win) => { + win.postMessage({ + doenetML: ` + a + + + `}, "*"); + }); + + cy.get('#\\/_text1').should('have.text', 'a')// to wait for page to load + + + cy.window().then(async (win) => { + let stateVariables = await win.returnAllStateVariables1(); + expect(me.fromAst(stateVariables['/l'].stateValues.length).equals(me.fromText('sqrt((x-u)^2+(y-v)^2)'))).eq(true); + }) + }); + it('label positioning', () => { cy.window().then(async (win) => { win.postMessage({ diff --git a/src/Core/Core.js b/src/Core/Core.js index c1d756ad2d..9bdefb0a4f 100644 --- a/src/Core/Core.js +++ b/src/Core/Core.js @@ -9340,7 +9340,29 @@ export default class Core { } } } else { - Object.assign(arrayInstructionInProgress.desiredValue, newInstruction.desiredValue); + if (depStateVarObj.nDimensions === 1) { + Object.assign(arrayInstructionInProgress.desiredValue, newInstruction.desiredValue); + } else { + // need to convert multidimensional array (newInstruction.desiredValue) + // to an object with multidimesional arrayKeys + // where each array key is a concatenation of the array indices, joined by commas + + let convert_md_array = (array, n_dim) => { + if (n_dim === 1) { + return Object.assign({}, array) + } else { + let new_obj = {} + for (let ind in array) { + let sub_obj = convert_md_array(array[ind], n_dim - 1); + for (let key in sub_obj) { + new_obj[`${ind},${key}`] = sub_obj[key] + } + } + return new_obj; + } + } + Object.assign(arrayInstructionInProgress.desiredValue, convert_md_array(newInstruction.desiredValue, depStateVarObj.nDimensions)) + } } diff --git a/src/Core/components/LineSegment.js b/src/Core/components/LineSegment.js index a8cc0be550..28849ef235 100644 --- a/src/Core/components/LineSegment.js +++ b/src/Core/components/LineSegment.js @@ -333,6 +333,117 @@ export default class LineSegment extends GraphicalComponent { } } + stateVariableDefinitions.length = { + public: true, + shadowingInstructions: { + createComponentOfType: "math", + }, + returnDependencies: () => ({ + nDimensions: { + dependencyType: "stateVariable", + variableName: "nDimensions", + }, + endpoints: { + dependencyType: "stateVariable", + variableName: "endpoints" + } + }), + definition({ dependencyValues }) { + let length2 = 0; + let epoint1 = dependencyValues.endpoints[0]; + let epoint2 = dependencyValues.endpoints[1]; + let all_numeric = true; + for (let dim = 0; dim < dependencyValues.nDimensions; dim++) { + let v1 = epoint1[dim].evaluate_to_constant(); + if (!Number.isFinite(v1)) { + all_numeric = false; + break; + } + let v2 = epoint2[dim].evaluate_to_constant(); + if (!Number.isFinite(v2)) { + all_numeric = false; + break; + } + let d = v1 - v2; + length2 += d * d; + } + + if (all_numeric) { + return { setValue: { length: me.fromAst(Math.sqrt(length2)) } }; + } + + length2 = ['+']; + for (let dim = 0; dim < dependencyValues.nDimensions; dim++) { + length2.push([ + '^', + ['+', epoint1[dim], ['-', epoint2[dim]]], + 2 + ]) + } + + return { + setValue: { + length: + me.fromAst(['apply', 'sqrt', length2]) + } + } + + }, + inverseDefinition({ desiredStateVariableValues, dependencyValues }) { + let midpoint = []; + let dir = []; + let epoint1 = dependencyValues.endpoints[0]; + let epoint2 = dependencyValues.endpoints[1]; + let all_numeric = true; + for (let dim = 0; dim < dependencyValues.nDimensions; dim++) { + let v1 = epoint1[dim].evaluate_to_constant(); + if (!Number.isFinite(v1)) { + all_numeric = false; + break; + } + let v2 = epoint2[dim].evaluate_to_constant(); + if (!Number.isFinite(v2)) { + all_numeric = false; + break; + } + midpoint.push((v1 + v2) / 2) + dir.push(v1 - v2); + } + + if (!all_numeric) { + return { success: false } + } + + // make dir be unit length + let dir_length = Math.sqrt(dir.reduce((a, c) => a + c * c, 0)); + dir = dir.map(x => x / dir_length); + + let desiredLength = desiredStateVariableValues.length.evaluate_to_constant(); + + if (!Number.isFinite(desiredLength) || desiredLength < 0) { + return { success: false } + } + + let desiredEndpoint1 = [], desiredEndpoint2 = []; + let halfDesiredlength = desiredLength / 2; + + for (let dim = 0; dim < dependencyValues.nDimensions; dim++) { + desiredEndpoint1.push(me.fromAst(midpoint[dim] + dir[dim] * halfDesiredlength)); + desiredEndpoint2.push(me.fromAst(midpoint[dim] - dir[dim] * halfDesiredlength)); + } + + return { + success: true, + instructions: [{ + setDependency: "endpoints", + desiredValue: [desiredEndpoint1, desiredEndpoint2] + }] + } + + + } + } + stateVariableDefinitions.numericalEndpoints = { isArray: true, @@ -494,32 +605,6 @@ export default class LineSegment extends GraphicalComponent { } } - stateVariableDefinitions.length = { - public: true, - shadowingInstructions: { - createComponentOfType: "number", - }, - returnDependencies: () => ({ - numericalEndpoints: { - dependencyType: "stateVariable", - variableName: "numericalEndpoints" - }, - nDimensions: { - dependencyType: "stateVariable", - variableName: "nDimensions", - } - }), - definition({ dependencyValues }) { - let ps = dependencyValues.numericalEndpoints; - let length2 = 0; - for (let dim = 0; dim < dependencyValues.nDimensions; dim++) { - length2 += (ps[1][dim] - ps[0][dim]) ** 2; - } - - return { setValue: { length: Math.sqrt(length2) } } - } - } - return stateVariableDefinitions; }