リストの値にスライダーを吸い付かせる+ラベルを数式表示(CAS利用版)
(ご注意)
本記事で紹介する情報は、web版GeoGebra(クラシック6)にのみ有効です。ローカル版GeoGebraではうまく機能しません。また、記事公開後のGeoGebraのアップデートにより、正常に機能しなくなる可能性があります。あらかじめご了承ください。
課題
スライダーaを、最小値-1、最大値1、増分0.001で作成する。
リストlist1を、以下の定義で作成する。
list1 = Sort(Join({{1 / 2, sqrt(2) / 2, sqrt(3) / 2, -1 / 2, (-sqrt(2)) / 2, (-sqrt(3)) / 2}, Unique(Flatten(Zip(Sequence(α π, α, -4, 4, β), β, {1, 1 / 4, 1 / 6})))}))
上記list1は、πや√、分数を用いた数値が混在したリストである。
スライダーaを動かして、list1の値付近に近づいたとき、その値にaを吸い付かせたい。
そして、ルート、π、分数を含む値に吸い付かせたとき、aのラベルを、(小数表示ではなく)ルート、π、分数を用いた表記(数式表示)にしたい。
以上の挙動を実現したい。
以前、下記記事で紹介した方法は、π表記に対応していなかった。
http://usidesu.hatenablog.com/entry/2020/10/28/034229
今回、CASに数式を渡して、LaTeX表記を取得する方法をとったことで、πにも対応した。
サンプル
つくりかた
※aとlist1は作成済みであることが前提です。
step1
下記のJavaScriptを、グローバルJavaScript欄にコピペする。
//handlerは、'click'ならonClick、それ以外ならonUpdate //setJavaScript('k', 'update', scrText); function setJavaScript(objName, handler, script) { //オブジェクト存在確認 var isexist = ggbApplet.exists(objName); if (isexist) { var objType = ggbApplet.getObjectType(objName); var onwhat = (handler == 'click') ? 'val' : 'onUpdate'; var xmlText = '<element type=\"' + objType + '\" label=\"' + objName + '\"><javascript ' + onwhat + '=\"' + script + '\"/></element>'; ggbApplet.evalXML(xmlText); } } //スライダーの情報を取得(戻り値string) //var info = getSliderInfo('a'); function getSliderInfo(sliderName) { //xmlデータを取得 var xmlStr = ggbApplet.getXML(sliderName); //DOMにパース var parser = new DOMParser(); var dom = parser.parseFromString(xmlStr, "text/xml"); //sliderタグ内の情報を取得 var sld = dom.getElementsByTagName('slider'); //オブジェクトがスライダーでない場合、undefinedを返して終了 if (sld.length == 0) { return undefined; } //オブジェクトとしてアウトプット var output = new Object(); for (var k = 0; k < sld[0].attributes.length; k++) { var nameText = sld[0].attributes[k].name; var val = sld[0].attributes[k].value; if(val=='true'){output[nameText] = true;} else if(val=='false'){output[nameText] = false;} else{output[nameText] = ggbApplet.getValue(val);} } //始点 var startX = output.x; var startY = output.y; if (output.absoluteScreenLocation) { startX = ggbApplet.getValue('x(Corner[1])+(x(Corner[3])-x(Corner[1]))*((' + startX + '+1)/(x(Corner[5])+2))'); startY = ggbApplet.getValue('y(Corner[3])-(y(Corner[3])-y(Corner[1]))*((' + startY + '+1)/(y(Corner[5])+2))'); } //ピクセル座標(画面上固定)の場合 //console.log('始点:('+startX+','+startY+')'); //console.log('width='+output.width); //終点 var endX = output.x + (output.horizontal ? output.width : 0); var endY = output.y + (output.horizontal ? 0 : output.width); if (output.absoluteScreenLocation) { endX = ggbApplet.getValue(startX + '+(x(Corner[3])-x(Corner[1]))*((' + (output.horizontal ? output.width : 0) + ')/(x(Corner[5])+2))'); endY = ggbApplet.getValue(startY + '+(y(Corner[3])-y(Corner[1]))*((' + (output.horizontal ? 0 : output.width) + ')/(y(Corner[5])+2))'); } //ピクセル座標(画面上固定)の場合 //console.log('終点:('+endX+','+endY+')'); //outputに始点・終点情報を記録 output['startX'] = startX; output['startY'] = startY; output['endX'] = endX; output['endY'] = endY; return output; } //スライダー点を作成 //makeSliderPoint('k', 'l1'); function makeSliderPoint(sliderName, listName) { //スライダー情報を取得 var info = getSliderInfo(sliderName); //スライダーでなければ処理終了 if (!info) { return undefined; } //端点用の点オブジェクトを作成 ggbApplet.evalCommand('StartOf' + sliderName + '=(0,0)'); ggbApplet.evalCommand('GoalOf' + sliderName + '=(5,0)'); //スライダーの最小値・最大値を記録する数値オブジェクトを作成 var xmlTextMin = '<expression label=\"minOf' + sliderName + '\" exp=\"{0}\"/><element type=\"list\" label=\"minOf' + sliderName + '\"></element>'; ggbApplet.evalXML(xmlTextMin); var xmlTextMax = '<expression label=\"maxOf' + sliderName + '\" exp=\"{0}\"/><element type=\"list\" label=\"maxOf' + sliderName + '\"></element>'; ggbApplet.evalXML(xmlTextMax); //スライダー点を作成 ggbApplet.evalCommand('PointOf' + sliderName + ' = Dilate(GoalOf' + sliderName + ', (' + sliderName + ' - minOf' + sliderName + '(1)) / (maxOf' + sliderName + '(1) - minOf' + sliderName + '(1)), StartOf' + sliderName + ')'); } //StartOf~, GoalOf~, minOf~, maxOf~にスライダー情報を書き込む。スライダーのupdateスクリプトに記述する用。 //evalSliderInfo('k', 'l1'); function evalSliderInfo(sliderName, listName) { //スライダー情報を取得 var info = getSliderInfo(sliderName); if(!info){return;} //スライダーの端点の座標を、StartOf~とGoalOf~に記録 ggbApplet.evalCommand('SetValue[StartOf' + sliderName + ',(' + info.startX + ',' + info.startY + ')]'); ggbApplet.evalCommand('SetValue[GoalOf' + sliderName + ',(' + info.endX + ',' + info.endY + ')]'); //スライダーの最小値、最大値を、min_a, max_aに記録 ggbApplet.evalCommand('SetValue[minOf' + sliderName + ',{' + info.min + '}]'); ggbApplet.evalCommand('SetValue[maxOf' + sliderName + ',{' + info.max + '}]'); } //各「〜of+sliderName」をリネームする。 function renamePartsOfSlider(oldName,newName){ var headNameArr = ['indexOf','commonTextOf','formulaTextOf','StartOf','GoalOf','minOf','maxOf','PointOf','combinedTextOf']; for(var k=0; k<headNameArr.length; k++){ ggbApplet.renameObject(headNameArr[k]+oldName, headNameArr[k]+newName); } } //スライダーのUpdateスクリプト function sliderUpdateScript(listName,diff){ var oldName = ggbApplet.getValueString('nameOfSlider');//変更前の名前 ggbApplet.evalCommand('UpdateConstruction[]'); var newName = ggbApplet.getValueString('nameOfSlider');//変更後の名前 //alert(oldName+'→'+newName); if(oldName!=newName){ renamePartsOfSlider(oldName,newName); return; } //吸い付き ggbApplet.evalCommand('If[abs('+newName+'-x(ClosestPoint(Zip((α, 0), α, '+listName+'), ('+newName+', 0))))<'+diff+',SetValue['+newName+',x(ClosestPoint(Zip((α, 0), α, '+listName+'), ('+newName+', 0)))]]'); //スライダー情報を書き込み evalSliderInfo(newName, listName); } //ラベルを表すテキストを作成(吸い付き値のみCASで渡した数式表記) //makeLabelText('k','l1'); function makeLabelText(sliderName,listName){ //listNameの定義式を取得 var equationText = ggbApplet.getDefinitionString(listName); //casに入力 var casNum = ggbApplet.getCASObjectNumber()+1; ggbApplet.evalCommand('$'+casNum+'='+equationText); //FormulaTextをとる ggbApplet.evalCommand('ftOf'+listName+'=FormulaText[$'+casNum+']'); //FormulaTextのCopyFreeObjectをとる ggbApplet.evalCommand('cfOf'+listName+'=CopyFreeObject[ftOf'+listName+']'); //LaTeX形式テキストのリストに整形 var rawText = ggbApplet.getValueString('ftOf'+listName); var lengthOfRawText = rawText.length; var centerText = rawText.slice(7,lengthOfRawText-9); var wrappedText = '\"'+centerText+'\"'; var replacedText = wrappedText.replace(/,/g, '\",\"'); ggbApplet.evalCommand('latexTextListOf'+listName+'={'+replacedText+'}'); //(吸い付き時の)スライダーの値が、リストの何番目の要素と等しいか→indexを返す ggbApplet.evalCommand('indexOf'+sliderName+'=If('+sliderName+' ≟ x(ClosestPoint(Zip((α, 0), α, '+listName+'), ('+sliderName+', 0))), IndexOf('+sliderName+', '+listName+'), 1)'); //現在のスライダーの値に応じたFormulaText ggbApplet.evalCommand('formulaTextOf'+sliderName+'=\"\"+Element[latexTextListOf'+listName+',indexOf'+sliderName+']'); //LaTeXをON var xmlformulaTextLatex = '<element type=\"text\" label=\"formulaTextOf' + sliderName + '\"><isLaTeX val=\"true\"/></element>'; ggbApplet.evalXML(xmlformulaTextLatex); //一般テキスト ggbApplet.evalCommand('commonTextOf' + sliderName + '=\"\"+'+sliderName); //LaTeXをON var xmlCommonTextLatex = '<element type=\"text\" label=\"commonTextOf' + sliderName + '\"><isLaTeX val=\"true\"/></element>'; ggbApplet.evalXML(xmlCommonTextLatex); //結合テキスト ggbApplet.evalCommand('combinedTextOf' + sliderName + ' = If(' + sliderName + ' ≟ x(ClosestPoint(Zip((α, 0), α, ' + listName + '), (' + sliderName + ', 0))), formulaTextOf' + sliderName + ', commonTextOf' + sliderName + ')'); //LaTeXをON var xmlCombinedTextLatex = '<element type=\"text\" label=\"combinedTextOf' + sliderName + '\"><isLaTeX val=\"true\"/></element>'; ggbApplet.evalXML(xmlCombinedTextLatex); } //テキストの開始位置を設定 //setTextStartPoint('text1','(3,4)'); //setTextStartPoint('text1','A'); //setTextStartPoint('text1','Midpoint[(0,0),(1,1)]'); function setTextStartPoint(objName, expText) { //オブジェクト存在確認 var isexist = ggbApplet.exists(objName); if (isexist) { var objType = ggbApplet.getObjectType(objName); var xmlText = '<element type=\"' + objType + '\" label=\"' + objName + '\"><startPoint exp=\"' + expText + '\"/></element>'; ggbApplet.evalXML(xmlText); } } //上記の総括関数、各種オブジェクトのスタイルを調整 //snapSliderToListAndMakeFormulaTextLabel('k','l1', 0.05); function snapSliderToListAndMakeFormulaTextLabel(sliderName, listName, diff) { //スライダー点を作成 makeSliderPoint(sliderName, listName); //スライダー情報を書き込み evalSliderInfo(sliderName, listName); //リネーム監視用オブジェクトnameOfSliderを作成 ggbApplet.evalCommand('nameOfSlider=Name('+sliderName+')'); //sliderにon Update JavaScriptを書き込み setJavaScript(sliderName, 'update', 'sliderUpdateScript("'+listName+'",'+diff+');'); //ラベルを表すテキストを作成(吸い付き値のみformula指定) makeLabelText(sliderName, listName); //update //ggbApplet.evalCommand('UpdateConstruction[]'); //alert('ここまで問題なし'); //return; //各種テキストの開始位置を設定 setTextStartPoint('formulaTextOf' + sliderName, 'PointOf' + sliderName); setTextStartPoint('commonTextOf' + sliderName, 'PointOf' + sliderName); setTextStartPoint('combinedTextOf' + sliderName, 'PointOf' + sliderName); //alert('ここまで問題なし'); //return; //sliderの値をリストの第一要素に揃える ggbApplet.evalCommand('SetValue[' + sliderName + ',' + listName + '(1)]'); //alert('ここまで問題なし'); //return; //不可視調整 ggbApplet.setVisible('StartOf' + sliderName, false); ggbApplet.setVisible('GoalOf' + sliderName, false); ggbApplet.setVisible('minOf' + sliderName, false); ggbApplet.setVisible('maxOf' + sliderName, false); ggbApplet.setVisible('PointOf' + sliderName, false); ggbApplet.setVisible('formulaTextOf' + sliderName, false); ggbApplet.setVisible('commonTextOf' + sliderName, false); ggbApplet.setVisible('indexOf' + sliderName, false); ggbApplet.setVisible('ftOf' + listName, false); ggbApplet.setVisible('cfOf' + listName, false); ggbApplet.setVisible('latexTextListOf' + listName, false); ggbApplet.setVisible('nameOfSlider', false); //alert('ここまで問題なし'); //return; //補助オブジェクト化 ggbApplet.setAuxiliary('StartOf' + sliderName, true); ggbApplet.setAuxiliary('GoalOf' + sliderName, true); ggbApplet.setAuxiliary('minOf' + sliderName, true); ggbApplet.setAuxiliary('maxOf' + sliderName, true); ggbApplet.setAuxiliary('PointOf' + sliderName, true); ggbApplet.setAuxiliary('formulaTextOf' + sliderName, true); ggbApplet.setAuxiliary('commonTextOf' + sliderName, true); ggbApplet.setAuxiliary('indexOf' + sliderName, true); ggbApplet.setAuxiliary('ftOf' + listName, true); ggbApplet.setAuxiliary('cfOf' + listName, true); ggbApplet.setAuxiliary('latexTextListOf' + listName, true); ggbApplet.setAuxiliary('nameOfSlider', true); //alert('ここまで問題なし'); //return; //スライダーの初期ラベルを非表示 ggbApplet.setLabelVisible('' + sliderName, false); //実行完了通知 alert('実行完了しました。'); }
step2
ボタンを作成し、そのOn Click ハンドラに、以下のスクリプトを記述する。
snapSliderToListAndMakeFormulaTextLabel('a','list1', 0.1);
なお、ハンドラ下部の「GeoGebra Script」を「JavaScript」に変更するのを忘れないよう、注意されたい。
step3
アプレットを保存し、再び開く。これによって、step1で作ったグローバルJavaScriptが読み込まれる。
step4
ボタンをクリックする。これによって、目的の挙動が実現される。作成されたラベルは、テキストオブジェクトであり、適宜スタイルの調整が可能である。
list1の要素や、スライダーの名前は、自由に変更可能である。
なお、複数のスライダーには対応していない。
吸い付き感度の調整
スライダーのOn Updateスクリプト内の数字は、吸い付きの感度を表す。ここを変更することで、吸い付き感度を調整できる。
XMLデータを利用する
データの取得(DOM)およびシリアライズ
//アプレットの設計をDOMとして取得 function getGGBDom() { //ggbApplet+idの場合に対応 var idStr; if (typeof document.getElementsByClassName('notranslate')[0] === 'undefined') { idStr = 'ggbApplet'; } else { idStr = document.getElementsByClassName('notranslate')[0].getAttribute('id'); } console.log(idStr); var ggbApplet = (new Function("return " + idStr))(); //xmlデータを取得 var xmlStr = ggbApplet.getXML(); //DOMにパース var parser = new DOMParser(); var dom = parser.parseFromString(xmlStr, "text/xml"); return dom; }
//domをxmlにシリアライズ function domToXml(dom) { var serializer = new XMLSerializer(); var xmlStr = serializer.serializeToString(dom); return xmlStr; }
// 特定のオブジェクトのxmlデータをDOMにシリアライズ function getDOM(objName) { var xmlText = ggbApplet.getXML(objName); if (xmlText == "") { return; } var parser = new DOMParser(); var doc = parser.parseFromString(xmlText, "text/xml"); return doc; } //DOMをeval function evalDOM(dom) { var serializer = new XMLSerializer(); var xmlStr = serializer.serializeToString(dom); console.log(xmlStr); ggbApplet.evalXML(xmlStr); } //要素を編集 // editElem("text1", "isLaTeX", {val:"false"}); // editElem("text1", "objColor", {r:"0", g:"255"}); function editElem(objName, tagName, attributesObj) { var doc = getDOM(objName); var parentElem = doc.getElementsByTagName("element")[0] var targetElem = doc.getElementsByTagName(tagName)[0]; if (targetElem) { var attributeNamesOfTargetElem = targetElem.getAttributeNames(); // console.log(attributeNamesOfTargetElem); for (var abName of attributeNamesOfTargetElem) { console.log(abName); if (attributesObj[abName] === undefined) { attributesObj[abName] = targetElem.getAttribute(abName); } } targetElem.remove(); } console.log(attributesObj); var newElem = doc.createElement(tagName); for (var [key, value] of Object.entries(attributesObj)) { newElem.setAttribute(key, value); } console.log(newElem); parentElem.appendChild(newElem); evalDOM(doc); var doc = getDOM(objName); console.log(doc); }