#moodle #jsxgraph
#moodle #jsxgraph
Вопрос:
Я создал Moodle Formulas Questions в области кинематики с двумя досками. Хотя мне удалось заставить более простые вопросы только с одной доской работать безупречно, проблема с этим вопросом заключается в том, что связанные значения не вставляются в поля ввода формулы. Следовательно, учащийся не может отправить ответ, потому что, по сути, ничего не было заполнено. Тем не менее, остальная часть вопроса работает, как видно, когда правильные ответы заполнены в предварительном просмотре вопроса.
Я предоставляю XML-файл Moodle, чтобы упростить воспроизведение проблемы: questions_formulas_JSXGraph_2boards.xml
Вам нужна текущая версия Moodle с JSXGraph
установленным фильтром и типом вопроса Formulas
.
Основной код JSXGraph таков:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brd0 = JXG.JSXGraph.initBoard(BOARDID0, {
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [ 15, -0]} } },
showCopyright: false, showNavigation: false
});
var brd1 = JXG.JSXGraph.initBoard(BOARDID1, {
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [ 15, -0]} } },
showCopyright: false, showNavigation: false
});
// Board brd0 needs to be updated when changes in brd1 occur
brd1.addChild(brd0);
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
brd0.update();
brd1.update();
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return PV2.X(); });
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return PV3.Y(); });
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
</jsxgraph>
Возможно ли, что проблема вызвана неправильной передачей идентификаторов платы в
new JSXQuestion(BOARDID0, jsxCode, allowInputEntry=true);
Помимо этой проблемы, я хотел бы немного лучше понять JSXGraph:
- Возможно ли каким-то образом расположить несколько плат относительно друг друга? То есть выше, ниже, выровнено по правому краю, по центру и т.д.
- Имеет ли значение, инициализируются ли платы как ‘const’ или ‘var’?
- Необходимо / полезно / неправильно приостанавливать и не приостанавливать обновления платы в приведенном выше примере?
- Являются ли команды ручного обновления в коде необходимыми / полезными / бесполезными?
- Есть ли какие-либо очевидные ошибки в моем кодировании или использовании JSXGraph?
Ответ №1:
На самом деле, правильно, что наш фильтр в сочетании с формулами некорректно работает с несколькими досками. На данный момент объекту JSXQuestion передается только один идентификатор платы, и, таким образом, и, таким образом, он (и формулы) ничего не знают о второй плате. Это также одна из проблем, возникающих в вашем примере.
Кроме того, платы фактически должны быть инициализированы методом JSXQuestion.initBoard(), чтобы метод bindInput() работал. В конце концов, это основная проблема, почему ваш пример не работает.
Я посвящу себя этой проблеме после рождественских праздников и выпущу новую версию фильтра Moodle в январе. Возможно, к тому времени тоже появится что-то новое из JSXGraph.
К сожалению, до тех пор я не могу предложить вам грязный взлом, поскольку для этого требуется несколько основных изменений в фильтре.
Я надеюсь, что смогу рассказать вам больше в январе. Хорошего Рождества и будьте здоровы!
Андреас
Комментарии:
1. Большое спасибо за ответ! Я с нетерпением ждал, когда этот вопрос заработает. Было бы здорово иметь возможность использовать несколько досок в формулах. Кстати, весь проект потрясающий, и я в восторге от его использования и повышения полезности формирующих и суммирующих действий Moodle для моих студентов. Счастливого Рождества вам и будьте здоровы!
Ответ №2:
Теперь у меня было время рассмотреть вашу проблему, и я смог расширить фильтр Moodle. Начиная с новой версии v1.1.0-for3.10, в формулах также поддерживается несколько плат. Вы можете найти подробные инструкции о том, как его использовать и что учитывать здесь, на GitHub.
Новую версию плагина можно загрузить в каталоге плагинов.
Я взял на себя смелость изменить ваш пример из приведенного выше, и он работает для меня:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Create boards
var brds = question.initBoards( [
{ // attribs for BOARDID0
boundingbox: [-1, 11, 12, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'x in m',
label: {position: 'rt', offset: [ 15, -0]} } },
showCopyright: false, showNavigation: false
},
{ // attribs for BOARDID1
boundingbox: [-1, 3.5, 12, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: 't in s',
label: {position: 'rt', offset: [-0, 15], anchorX: 'right'} },
y: {withLabel:true, name: 'v_x in m/s',
label: {position: 'rt', offset: [ 15, -0]} } },
showCopyright: false, showNavigation: false
}
] );
var brd0 = brds[0];
var brd1 = brds[1];
console.log(brd0, brd1);
// Board brd0 needs to be updated when changes in brd1 occur
question.addChildsAsc();
/* not needed anymore
brd1.addChild(brd0);
*/
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Define lines and points on brd1
brd1.suspendUpdate();
var lV0 = brd1.create('segment', [[0,-10], [0,10]], {visible:false}),
lV3 = brd1.create('segment', [[-10,0], [20,0]], {visible:false});
var pV0 = brd1.create('glider', [0, v1, lV0], attrPmov({name: "pV0"}) ),
pV1 = brd1.create('point', [t1, v2], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v3], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('glider', [t3, 0, lV3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', ["X(pV1)", "Y(pV0)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV2)", "Y(pV1)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV3)", "Y(pV2)"], attrPsma() ) ;
brd1.create('polygonalchain', [ pV0, pV01, pV1, pV12, pV2, pV23, pV3 ], attrLine);
brd1.unsuspendUpdate();
// Define lines and points on brd1
// Q: Is it necessary/beneficial/wrong to suspendUpdate here?
// A: It can be beneficial if you use a lot of objects. In this case the benefit is not worth mentioning, I think.
brd0.suspendUpdate();
var lX1 = brd0.create('line', [[function(){return pV1.X();},-10], [function(){return pV1.X();},10]], attrGlid),
lX2 = brd0.create('line', [[function(){return pV2.X();},-10], [function(){return pV2.X();},10]], attrGlid),
lX3 = brd0.create('line', [[function(){return pV3.X();},-10], [function(){return pV3.X();},10]], attrGlid);
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('glider', [t1, x1, lX1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('glider', [t2, x2, lX2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('glider', [t3, x3, lX3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
brd0.unsuspendUpdate();
// Q: Are these updates necessary?
/* not with the new version
brd0.update();
brd1.update();
*/
/* not necessary anymore
question.board = brd0;
*/
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return pV2.X(); }); // typo here
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return pV3.Y(); }); // typo here
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDIDS, jsxCode, allowInputEntry=true); // use BOARDIDS here!!
</jsxgraph>
Я уже ответил на другие вопросы в коде.
Я надеюсь, что смогу вам помочь!
Приветствую, Андреас
Комментарии:
1. P.S. Ошибки предварительной проверки кода будут исправлены в следующей версии фильтра, когда мы выпустим новую версию JSXGraph.
2. Извините, что ответил так поздно… Большое спасибо, это фантастика, что вам удалось так быстро расширить фильтр. Также большое спасибо за прямое применение изменений к моему коду. Еще два вопроса: 1) Можно ли как-то расположить несколько досок относительно друг друга? То есть выше, ниже, выровнено по правому краю, по центру и т.д. 2) Имеет ли значение, инициализируются ли платы как ‘const’ или ‘var’?
3. Привет! 1) Можно управлять размером досок по атрибутам в теге. Например, спецификация
width =" 500,200 "height =" 500,200 "
гарантирует, что первая доска имеет размер 500x500px, а вторая 200x200px. Если задано меньше значений, чем количество досок, первое значение используется как стандартное. В качестве единиц здесь можно использовать все, что также возможно в css (rem,%, …). По умолчанию все доски окружены классом css.jsxgraph-boards
. Установленные здесь стандарты могут быть перезаписаны простым css.4. Смотрите github.com/jsxgraph /…
5. 2) Посмотрите главу «Постоянные объекты могут изменяться» на w3schools.com/js/js_const.asp
Ответ №3:
Просто для полноты картины я публикую свою окончательную версию кода JSXGraph для вопроса о формулах на основе решения Андреаса. Мои последние штрихи заключались в
- заставьте метки осей использовать LaTeX,
- используйте обработчик событий
.on('drag', ...)
для двухсторонних обновлений между диаграммами вместо использованияquestion.addChildsAsc()
.
Вот окончательный код:
<jsxgraph width="400" height="300" numberOfBoards="2" ext_formulas>
// JavaScript code to create the construction.
var jsxCode = function (question) {
// Import final coordinates after submission
var x0={x0};
var t1,t2,t3 , v1,v2,v3 , x1,x2,x3;
[t1,t2,t3 , v1,v2,v3 , x1,x2,x3] =
question.getAllValues([1,2,3 , 1,2,3 , x0,x0,x0 ]);
JXG.Options.point.infoboxDigits = 1;
JXG.Options.point.snapSizeX = 1;
JXG.Options.point.snapSizeY = 0.1;
// Attributes for points and lines
function attrPfix(addAttr={}) {
const attr = {fixed: true, visible: false, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPmov(addAttr={}) {
const attr = {fixed: question.isSolved, snapToGrid: true, withLabel: false};
return { ...attr, ...addAttr};
}
function attrPsma(addAttr={}) {
const attr = {visible: true, withLabel: false, color:'#4285F4', size: 1};
return { ...attr, ...addAttr};
}
const attrLine = {borders: {strokeColor:'#4285F4', strokeWidth: 3} };
const attrGlid = {visible:false};
// Create boards
var brds = question.initBoards( [
{ // attribs for BOARDID0
boundingbox: [-1, 12, 13, -11], axis:true,
defaultAxes: {
x: {withLabel: true, name: '$t\;\mathrm{(s)}$',
label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
y: {withLabel:true, name: '$x\;\mathrm{(m)}$',
label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
showCopyright: false, showNavigation: false
},
{ // attribs for BOARDID1
boundingbox: [-1, 3.8, 13, -3.5], axis:true,
defaultAxes: {
x: {withLabel: true, name: '$t\;\mathrm{(s)}$',
label: {position: 'rt', offset: [10, 26], anchorX: 'right', parse: false, fontSize: 12 } },
y: {withLabel:true, name: '$v_x\;\mathrm{(m/s)}$',
label: {position: 'rt', offset: [10, 15], parse: false, fontSize: 12 } } },
zoom: {enabled:false, wheel: false}, pan: {enabled:false, needTwoFingers: false},
showCopyright: false, showNavigation: false
}
] );
var brd0 = brds[0];
var brd1 = brds[1];
// console.log(brd0, brd1);
// Board brd0 needs to be updated when changes in brd1 occur
// question.addChildsAsc();
// Define lines and points on brd1
var pV1 = brd1.create('point', [t1, v1], attrPmov({name: "pV1"}) ),
pV2 = brd1.create('point', [t2, v2], attrPmov({name: "pV2"}) ),
pV3 = brd1.create('point', [t3, v3], attrPmov({name: "pV3"}) ),
pV01 = brd1.create('point', [0, "Y(pV1)"], attrPsma() ),
pV12 = brd1.create('point', ["X(pV1)", "Y(pV2)"], attrPsma() ),
pV23 = brd1.create('point', ["X(pV2)", "Y(pV3)"], attrPsma() ) ;
pV34 = brd1.create('point', ["X(pV3)", 0], attrPsma() ) ;
brd1.create('polygonalchain', [ pV01, pV1, pV12, pV2, pV23, pV3, pV34 ], attrLine);
// Define lines and points on brd0
var pX0 = brd0.create('point', [0, x0], attrPsma({fixed: true}) ),
pX1 = brd0.create('point', [t1, x1], attrPmov({face: 'diamond'}) ),
pX2 = brd0.create('point', [t2, x2], attrPmov({face: 'diamond'}) ),
pX3 = brd0.create('point', [t3, x3], attrPmov({face: 'diamond'}) );
brd0.create('polygonalchain', [ pX0, pX1, pX2, pX3 ], attrLine);
// Define dependencies
pV1.on('drag', function() { pX1.moveTo([this.X(), pX1.Y()], 0); });
pV2.on('drag', function() { pX2.moveTo([this.X(), pX2.Y()], 0); });
pV3.on('drag', function() { pX3.moveTo([this.X(), pX3.Y()], 0); });
pX1.on('drag', function() { pV1.moveTo([this.X(), pV1.Y()], 0); });
pX2.on('drag', function() { pV2.moveTo([this.X(), pV2.Y()], 0); });
pX3.on('drag', function() { pV3.moveTo([this.X(), pV3.Y()], 0); });
// Whenever the construction is altered the values of the points are sent to formulas.
question.bindInput(0, () => { return pV1.X(); });
question.bindInput(1, () => { return pV2.X(); });
question.bindInput(2, () => { return pV3.X(); });
question.bindInput(3, () => { return pV1.Y(); });
question.bindInput(4, () => { return pV2.Y(); });
question.bindInput(5, () => { return pV3.Y(); });
question.bindInput(6, () => { return pX1.Y(); });
question.bindInput(7, () => { return pX2.Y(); });
question.bindInput(8, () => { return pX3.Y(); });
};
// Execute the JavaScript code.
new JSXQuestion(BOARDIDS, jsxCode, allowInputEntry=false); // use BOARDIDS here!!
</jsxgraph>
Комментарии:
1. Очень приятно! Только один вопрос: можем ли мы в Исследовательском центре мобильного обучения с использованием цифровых медиа продолжать использовать ваш пример для дальнейшего обучения, презентаций и публикаций?
2. Да, с удовольствием. Я очень благодарен за предоставленную вами помощь и был бы рад каким-то образом вернуть. Я думаю, что я также задал несколько других приятных вопросов по формулам. Если вам интересно, просто скажите мне, как лучше всего поделиться ими с вами.
3. Это было бы очень хорошо! Вы можете найти мою контактную информацию почти в каждом файле в фильтре под информацией об авторских правах.