うしブログ

うしブログ

趣味で運営する、GeoGebraの専門ブログ。

(作業メモ)StartPoint要検証(2行の場合;テキスト変更時未定義問題)

(要修復)ToggleButton・RollPolygonWithoutSlipping・貯金時計・直感力トレーニング

リストの値にスライダーを吸い付かせる+ラベルを数式表示(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表記を取得する方法をとったことで、πにも対応した。

 

サンプル

スライダー吸い付きCAS版 – GeoGebra

 

つくりかた

※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(&quot;'+listName+'&quot;,'+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スクリプト内の数字は、吸い付きの感度を表す。ここを変更することで、吸い付き感度を調整できる。