File:  [LON-CAPA] / loncom / homework / functionplotresponse.pm
Revision 1.107: download - view: text, annotated - select for diffs
Mon Jan 19 15:35:53 2015 UTC (9 years, 3 months ago) by goltermann
Branches: MAIN
CVS tags: HEAD
authoring space overhaul
this update tries to improve the user experience of the authoring space.

added codemirror for xml editor and script tags in colorful editor
added possibility to deactivate codemirror in author settings
added dropdown menu to insert problem templates into xml editor (thanks to tobias reinhardt)
added feature of saving current scrollposition on save when editing problems
added possibility to fold blocks in colorful editor, this state will be saved and restored
added shortcuts to create empty problems, html files and directories

and other smaller features and bugfixes

# LearningOnline Network with CAPA
# Functionplot responses
#
# $Id: functionplotresponse.pm,v 1.107 2015/01/19 15:35:53 goltermann Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

package Apache::functionplotresponse;
use strict;
use Apache::response();
use Apache::lonlocal;
use Apache::lonnet;
use Apache::run;
use LONCAPA;
 
BEGIN {
  &Apache::lonxml::register('Apache::functionplotresponse',('functionplotresponse','backgroundplot','spline',
                                                            'plotobject','plotvector',
                                                            'functionplotvectorrule','functionplotvectorsumrule',
                                                            'drawvectorsum',
                                                            'functionplotcustomrule',
                                                            'functionplotrule','functionplotruleset',
                                                            'functionplotelements'));
}

#
# Use old Java or HTML5/Javascript for GeoGebra? Depends on browser!
# Return a true value if HTML5 should be used.

sub useHTML5 {
    if ($env{'browser.type'} eq 'chrome') {
        if ($env{'browser.version'} >= 14) {
            return 1;
        }
    } elsif ($env{'browser.type'} eq 'safari') {
        if ($env{'browser.os'} eq 'mac') {
            my ($prefix,$version) = ($env{'browser.version'} =~ /^(\d*)(\d{3})\./); 
            if ((!$env{'browser.mobile'}) || 
                (($env{'browser.mobile'}) && length($prefix))) {
                if ($version >= 536) {
                    return 1;
                }
            }
        }
    } elsif ($env{'browser.type'} eq 'mozilla') {
        if ($env{'browser.info'} =~ /^firefox\-(\d+)/) {
            my $firefox = $1;
            if ((($env{'browser.os'} eq 'mac') && ($firefox >= 20)) ||
                (($env{'browser.os'} eq 'unix') && ($firefox >= 17)) ||
                (($env{'browser.os'} eq 'win') && ($firefox >= 14))) {
                return 1;
            }
        }
    } elsif ($env{'browser.type'} eq 'explorer') { 
        if (($env{'browser.os'} eq 'win') && ($env{'browser.version'} == 10)) {
            return 1;
        }
    }
    return 0;
}

#
# HTML5 version does not understand "_" in IDs
#
sub appid {
    my ($id)=@_;
    $id=~s/\_/rid/gs;
    $id=~s/\W//gs;
    return $id;
}

#
# Routines to start the applet (Java) or the HTML5/JavaScript
#
# There can be a number of applets on a page, each called ggbApplet_$id, 
# where $id is the "_"-concatenated part and responseid
#

sub geogebra_startcode {
    my ($id,$width,$height)=@_;
    if (&useHTML5()) {
        return &html5_geogebra_startcode(@_);
    } else {
        return &java_geogebra_startcode(@_).
               &java_geogebra_code_param();
    }
}

sub geogebra_endcode {
    if (&useHTML5()) {
        return '';
    } else {
        return &java_geogebra_endcode();
    }
}

sub geogebra_default_parameters {
    my ($id)=@_;
    if (&useHTML5()) {
        return '';
    } else {
        return &java_geogebra_default_parameters($id);
    }
}
# === Java code

sub java_geogebra_startcode {
    my ($id,$width,$height)=@_;
    my $appid=&appid($id);
    $width=int(1.*$width);
    $height=int(1.*$height);
    unless ($width) { $width=700; }
    unless ($height) { $height=400; }
    return (<<ENDSTARTCODE);
<applet name="ggbApplet$appid" code="geogebra.GeoGebraApplet" archive="geogebra.jar"
         codebase="/adm/geogebra/"  width="$width" height="$height" MAYSCRIPT>
       <param name="java_arguments" value="-Xmx512m -Djnlp.packEnabled=true"/>
ENDSTARTCODE
}

sub java_geogebra_endcode {
    return &Apache::lonhtmlcommon::java_not_enabled()."</applet>\n";
}

sub java_geogebra_code_param {
    return '<param name="ggbBase64" value="'.&geogebra_internal_program().'" />';
}

# === HTML5 code

sub html5_geogebra_startcode {
    my ($id,$width,$height)=@_;
    my $appid=&appid($id);
    $width=int(1.*$width);
    $height=int(1.*$height);
    unless ($width) { $width=700; }
    unless ($height) { $height=400; }
    my $code=&geogebra_internal_program();
    return (<<ENDSTARTCODE);
<article class="geogebraweb" data-param-enableLabelDrags="false" data-param-enableShiftDragZoom="false" 
data-param-width="$width" data-param-height="$height" data-param-id="ggbApplet$appid" 
data-param-ggbbase64="$code"></article>
ENDSTARTCODE
}

#
# This is the internal GeoGebra bytecode which defines the spline functions
#
sub geogebra_internal_program {
    return
'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';
}

#
# The standard set of parameters inside <applet>
#
sub java_geogebra_default_parameters {
   my ($id)=@_;
   my $appid=&appid($id);
   return(<<ENDDEFAULTPARAMETERS);
        <param name="image" value="/adm/lonIcons/lonanim.gif"  />
        <param name="boxborder" value="false"  />
        <param name="centerimage" value="true"  />
	<param name="cache_archive" value="geogebra.jar, geogebra_main.jar, geogebra_gui.jar, geogebra_cas.jar, geogebra_export.jar, geogebra_algos.jar, geogebra_javascript.jar, geogebra_properties.jar, jlatexmath.jar, jlm_cyrillic.jar, jlm_greek.jar" />
	<param name="cache_version" value="4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0,4.4.3.0" />
        <param name="framePossible" value="false" />

        <param name="showResetIcon" value="false" />
        <param name="showAnimationButton" value="false" />
        <param name="enableRightClick" value="false" />
        <param name="errorDialogsActive" value="true" />
        <param name="enableLabelDrags" value="false" />
        <param name="showMenuBar" value="false" />
        <param name="showToolBar" value="false" />
        <param name="showToolBarHelp" value="false" />
        <param name="showAlgebraInput" value="false" />
        <param name="enableShiftDragZoom" value="false" />
        <param name="allowRescaling" value="false" />
        <param name="enableLabelDrags" value="false" />
        <param name="ggbOnInitParam" value="ggbApplet$appid" />
ENDDEFAULTPARAMETERS
}

#
# This subroutine is called by LON-CAPA at </problem>
# Each applet on the page will call function ggbOnInit when it is done loading
# This function in turn will call the respective function registered by start_init_script
# Which one of the registered functions is called is determined by ggbOnInitParam, which GeoGebra passes to ggbOnInit
#

sub init_script {
   if ($#Apache::functionplotresponse::callscripts>=0) {
      my $script='';
      foreach my $id (@Apache::functionplotresponse::callscripts) {
          $script.="if (param=='ggbApplet".&appid($id)."') { loaded_$id=true; }\n";
      }
      $script.="if (".join(' && ',map { "loaded_$_" } (@Apache::functionplotresponse::callscripts)).
               ") { setTimeout('ggbInitAll()',200) }";
      my $calls=join("\n",map { "ggbInit_$_();" } (@Apache::functionplotresponse::callscripts)); 
      my $html5init='';
      if (&useHTML5()) {
          $html5init=
           '<script type="text/javascript" language="javascript" src="/adm/geogebra/web/web.nocache.js"></script>';
      }
      return (<<ENDGGBINIT);
$html5init
<script type="text/javascript">
// <![CDATA[
// Function that each applet will call when loaded
// It will pass "its" parameter
// Set flags for when an applet is loaded, wait till all are loaded, and then some
function ggbOnInit(param) {
$script
}
function ggbInitAll() {
$calls
}
// ]]>
</script>
ENDGGBINIT
   }
}

#
# Each Geogebra applet is supposed to call this when parameters change
# Changes the hidden fields on the web page
#
sub update_script {
    my ($id)=@_;
    my $appid=&appid($id);
    return (<<ENDUPDATESCRIPT);
<script type="text/javascript">
// <![CDATA[
function updatePointCoordinates_$id(coordinateName) {
            var x = document.ggbApplet$appid.getXcoord(coordinateName);
            var y = document.ggbApplet$appid.getYcoord(coordinateName);
            document.lonhomework.elements["HWVAL_$id\_" + coordinateName + "_x"].value = x;
            document.lonhomework.elements["HWVAL_$id\_" + coordinateName + "_y"].value = y;
        }
// ]]>
</script>
ENDUPDATESCRIPT
}

#
# Register the above update-handler for a variable
#

sub update_register {
   my ($id,$variable)=@_;
   my $appid=&appid($id);
   return "document.ggbApplet$appid.registerObjectUpdateListener('$variable','updatePointCoordinates_$id');\n";
}

#
# Set a point coordinate variable
#
sub set_point_coordinate {
   my ($id,$variable,$x,$y,$fixed)=@_;
   my $appid=&appid($id);
   my $mult=($fixed?'a*':'');
# Get rid of wild exponents, make sure it's a number
   $x=1.*$x;
   $y=1.*$y;
# GeoGebra does not understand "E"
   $x=~s/[e|E]/\*10\^/;
   $x=~s/\+//;
   $y=~s/[e|E]/\*10\^/;
   $y=~s/\+//;
   return (<<ENDSETVARIABLE);
document.ggbApplet$appid.evalCommand("a=1");
document.ggbApplet$appid.evalCommand("$variable=$mult($x,$y)");
document.ggbApplet$appid.setLabelVisible("$variable",false);
ENDSETVARIABLE
}

#
# Set a slope coordinate variable
#
sub set_slope_coordinate {
   my ($id,$variable,$xrel,$yrel,$xmin,$xmax,$ymin,$ymax,$pointname,$fixed)=@_;
   my $appid=&appid($id);
   my $xvariable=$variable.'x';
   my $yvariable=$variable.'y';
   my $domain=$xmax-$xmin;
   my $range=$ymax-$ymin;
   my $xinterval=$domain/100.;
   my $yinterval=$range/200.;
   my $mult=($fixed?'a*':'');
   return (<<ENDSETSVARIABLE);
document.ggbApplet$appid.evalCommand("a=1");
document.ggbApplet$appid.evalCommand("$xvariable=Slider[$xinterval,$domain,$xinterval]");
document.ggbApplet$appid.setVisible("$xvariable", false);
document.ggbApplet$appid.evalCommand("$xvariable=$xrel");
document.ggbApplet$appid.evalCommand("$yvariable=Slider[-$range,$range,$yinterval]");
document.ggbApplet$appid.setVisible("$yvariable", false);
document.ggbApplet$appid.evalCommand("$yvariable=$yrel");
document.ggbApplet$appid.evalCommand("$variable=$mult($xvariable+x($pointname),$yvariable+y($pointname))");
document.ggbApplet$appid.setLabelVisible("$variable", false);
ENDSETSVARIABLE
}

#
# Input field name for a coordinate variable
#

sub field_name {
    my ($id,$variable,$name)=@_;
    return "HWVAL_$id\_$variable\_$name";
}

#
# Generate an input field for a coordinate variable
#

sub generate_input_field {
    my ($id,$variable,$x,$y)=@_;
    $Apache::functionplotresponse::inputfields.=
       "<input type='hidden' name='".&field_name($id,$variable,'x')."' value='$x' />\n".
       "<input type='hidden' name='".&field_name($id,$variable,'y')."' value='$y' />\n";
}

#
# Initialize a new point coordinate variable at set a listener on it
#
sub new_point_coordinate {
    my ($id,$variable,$x,$y,$fixed)=@_;
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$variable,'x')})) {
       $x=$Apache::functionplotresponse::previous{&field_name($id,$variable,'x')};
    }
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$variable,'y')})) {
       $y=$Apache::functionplotresponse::previous{&field_name($id,$variable,'y')};
    }
    &generate_input_field($id,$variable,$x,$y);
    return &set_point_coordinate($id,$variable,$x,$y,$fixed).&update_register($id,$variable);
}

#
# Initialize a new slope coordinate variable at set a listener on it
#
sub new_slope_coordinate {
    my ($id,$variable,$x,$y,$pointname,$xp,$yp,$xmin,$xmax,$ymin,$ymax,$fixed)=@_;
#
# $variable: name of the slope point
# $x, $y: coordinates of the slope point
# $pointname: name of the associated point point
# $xp $yp: coordinates of the point point
#
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$variable,'x')})) {
       $x=$Apache::functionplotresponse::previous{&field_name($id,$variable,'x')};
    }
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$variable,'y')})) {
       $y=$Apache::functionplotresponse::previous{&field_name($id,$variable,'y')};
    }
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$pointname,'x')})) {
       $xp=$Apache::functionplotresponse::previous{&field_name($id,$pointname,'x')};
    }
    if (defined($Apache::functionplotresponse::previous{&field_name($id,$pointname,'y')})) {
       $yp=$Apache::functionplotresponse::previous{&field_name($id,$pointname,'y')};
    }

    &generate_input_field($id,$variable,$x,$y);
    my $xrel=$x-$xp;
    my $yrel=$y-$yp;
    return &set_slope_coordinate($id,$variable,$xrel,$yrel,$xmin,$xmax,$ymin,$ymax,$pointname,$fixed).&update_register($id,$variable);
}

#
# This registers the init-function call for ggbOnInit, which LON-CAPA will place at </problem>
# It then starts the right headers
#
sub start_init_script {
    my ($id)=@_;
# Add id to the list of ggbInit_$id functions that need to be called
    push(@Apache::functionplotresponse::callscripts,$id);
# ... and open this function
    return (<<ENDSTARTINIT);
<script type="text/javascript">
// <![CDATA[
// variable that will eventually be passed back to the server
var coordinateMap_$id = [];
// flag for not loaded yet
var loaded_$id=false;
// Init-function for applet
function ggbInit_$id() {
ENDSTARTINIT
}

#
# This sets the axes inside ggbInit_$id
#

sub axes_script {
    my ($id,$xmin,$xmax,$ymin,$ymax,$xvisible,$yvisible,$gvisible)=@_;
    my $appid=&appid($id);
    return (<<ENDAXESSCRIPT);
            // changes (xmin, xmax, ymin, ymax)
            document.ggbApplet$appid.setCoordSystem($xmin,$xmax,$ymin,$ymax);

            // makes the (x,y) axis (in)visible
            document.ggbApplet$appid.setAxesVisible($xvisible,$yvisible);
            // makes the grid (in)visible
            document.ggbApplet$appid.setGridVisible($gvisible);
ENDAXESSCRIPT
}

sub axes_label {
    my ($id,$xmin,$xmax,$ymin,$ymax,$xlabel,$ylabel)=@_;
    my $appid=&appid($id);
    unless ($xlabel || $ylabel) { return ''; }
    my $return='document.ggbApplet'.$appid.'.evalCommand("topRight=Corner[3]");';
    if ($xlabel) {
      if (($ymin<0) && ($ymax>0)) {
       $return.=(<<ENDXAXISLABELSCRIPT);
document.ggbApplet$appid.evalCommand("Xlabel=(x(topRight)-AxisStepX[],AxisStepY[]/6)");
document.ggbApplet$appid.setVisible("Xlabel",false);
document.ggbApplet$appid.evalCommand("Text[\\"$xlabel\\", Xlabel]");
ENDXAXISLABELSCRIPT
      } else {
       $return.=(<<ENDXOFFAXISLABEL);
document.ggbApplet$appid.evalCommand("LowerRight=Corner[2]");
document.ggbApplet$appid.evalCommand("Text[\\"$xlabel\\", (x(LowerRight) - AxisStepX[], y(LowerRight) + AxisStepY[] / 2)]");
ENDXOFFAXISLABEL
      }
    }
    if ($ylabel) {
      if (($xmin<0) && ($xmax>0)) {
       $return.=(<<ENDYAXISLABELSCRIPT);
document.ggbApplet$appid.evalCommand("Ylabel=(AxisStepX[]/6,y(topRight)-AxisStepY[]/3)");
document.ggbApplet$appid.setVisible("Ylabel",false);
document.ggbApplet$appid.evalCommand("Text[\\"$ylabel\\", Ylabel]");
ENDYAXISLABELSCRIPT
      } else {
       $return.=(<<ENDYOFFAXISLABEL);
document.ggbApplet$appid.evalCommand("UpperLeft=Corner[4]");
document.ggbApplet$appid.evalCommand("Text[\\"$ylabel\\", (x(UpperLeft) + AxisStepX[] / 5, y(UpperLeft) - AxisStepY[] / 1.8)]");
ENDYOFFAXISLABEL
      }
    }
    return $return;
}

#
# Subroutine to produce background and answer plots
#

sub plot_script {
   my ($id,$function,$fixed,$label,$color,$xmin,$xmax,$thickness)=@_;
   my $appid=&appid($id);
   $label=~s/\W//g;
   if (($label) && ($label!~/^[A-Za-z]/)) {
      $label='C'.$label;
   }
   my $visible=0;
   if ($label) {
      $visible="1";
   } else {
      $Apache::functionplotresponse::counter++;
      $label='C'.$Apache::functionplotresponse::counter;
   }
   my $rc=0;
   my $gc=0;
   my $bc=0;
   if ($color) {
      my ($rh,$gh,$bh)=($color=~/(..)(..)(..)/);
      $rc=hex($rh);
      $gc=hex($gh);
      $bc=hex($bh);
   }
   if ($fixed) {
      return "document.ggbApplet$appid.evalCommand('$label=Function[$function,$xmin,$xmax]');\n".
             ($visible?'':"document.ggbApplet$appid.setLabelVisible('$label', false);\n").
             ($color?"document.ggbApplet$appid.setColor('$label',$rc,$gc,$bc);\n":'').
             ($thickness?"document.ggbApplet$appid.setLineThickness('$label',$thickness);\n":'');
   } else {
       return "document.ggbApplet$appid.evalCommand('y=$function');\n";
   }
}

#
# Subroutine to produce objects
#

sub plotobject_script {
   my ($id,$label,$x,$y)=@_;
   my $appid=&appid($id);
   unless ($label) {
      $Apache::functionplotresponse::counter++;
      $label='O'.$Apache::functionplotresponse::counter;
   }
   &generate_input_field($id,$label,$x,$y);
   return "document.ggbApplet$appid.evalCommand('a=1');\n".
          "document.ggbApplet$appid.setVisible('a', false);\n".
          "document.ggbApplet$appid.setLabelVisible('a', false);\n".
          "document.ggbApplet$appid.evalCommand('$label=a*($x,$y)');\n".
          "document.ggbApplet$appid.setVisible('$label', true);\n".
          "document.ggbApplet$appid.setLabelVisible('$label', true);\n";
}

#
# Subroutine to produce vectors
#

sub plotvector_script {
   my ($id,$label,$xs,$ys,$xe,$ye,$xmin,$xmax,$fixed)=@_;
   my $appid=&appid($id);
   unless ($label) {
      $Apache::functionplotresponse::counter++;
      $label='V'.$Apache::functionplotresponse::counter;
   }
   my $startlabel=$label.'Start';
   my $endlabel=$label.'End';
   my $pointlabel=$label.'Point';
   my $pointx=2.*($xmax-$xmin)+$xmax;
   my $anglelabel=$label.'Angle';
   return 
       &new_point_coordinate($id,$startlabel,$xs,$ys,$fixed).
       &new_point_coordinate($id,$endlabel,$xe,$ye,$fixed).
       (<<ENDVECTOR);
document.ggbApplet$appid.evalCommand("$label=Vector[$startlabel,$endlabel]");
document.ggbApplet$appid.setLabelVisible("$label",true);
document.ggbApplet$appid.setLineThickness("$label",8);
document.ggbApplet$appid.evalCommand("$pointlabel=($pointx,y($startlabel))");
document.ggbApplet$appid.evalCommand("$anglelabel=Angle[$pointlabel,$startlabel,$endlabel]");
document.ggbApplet$appid.setLabelVisible("$anglelabel",true);
document.ggbApplet$appid.setLabelStyle("$anglelabel",VALUE=2);
ENDVECTOR
}

#
# Answer spline display
# 
# points: x,y,slope_x,slope_y

sub answer_spline_script {
   my ($id,@points)=@_;
   my $appid=&appid($id);
   my $order=int(($#points+1)/4);
   if ($order<2) { $order=2; }
   if ($order>8) { $order=8; }
   $Apache::functionplotresponse::counter++;
   my $label='CSpline'.$Apache::functionplotresponse::counter;
   my $output='document.ggbApplet'.$appid.'.evalCommand("'.$label.'=Spline'.$order.'[';
   for (my $i=0;$i<=$#points;$i+=4) {
      $output.="($points[$i],$points[$i+1]),($points[$i+2],$points[$i+3]),";
   }
   $output=~s/\,$//;
   $output.=']");'."\n";
   for (my $i=2; $i<2*$order; $i+=2) {
       $output.='document.ggbApplet'.$appid.'.setColor("'.$label.'_'.($i>=10?'{':'').$i.($i>=10?'}':'').'",0,170,0);'."\n";
   }
   for (my $i=1; $i<2*$order; $i+=2) {
       $output.='document.ggbApplet'.$appid.'.setVisible("'.$label.'_'.($i>=10?'{':'').$i.($i>=10?'}':'').'",false);'."\n";
   }

   return $output;
}

#
# Subroutine that generates code for spline $label based on stored information
#

sub generate_spline {
   my ($id,$label,$xmin,$xmax,$ymin,$ymax,$fixed)=@_;
   my $appid=&appid($id);
   my $result='';
   my $order=$Apache::functionplotresponse::splineorder{$label};
   my $x=$Apache::functionplotresponse::splineinitx{$label};
   my $y=$Apache::functionplotresponse::splineinity{$label};
   my $sx=$Apache::functionplotresponse::splinescalex{$label};
   my $sy=$Apache::functionplotresponse::splinescaley{$label};
   my @coords=();
   foreach my $i (1..$order) {
       $result.=&new_point_coordinate($id,$label.'P'.$i,$x,$y,$fixed);
       my $xp=$x;
       $x+=$sx/(2.*($order-1));
       push(@coords,$label.'P'.$i);
       $result.=&new_slope_coordinate($id,$label.'S'.$i,$x,$y+$sy,$label.'P'.$i,$xp,$y,$xmin,$xmax,$ymin,$ymax,$fixed);
       $x+=$sx/(2.*($order-1));
       push(@coords,$label.'S'.$i);
   }
   $result.='document.ggbApplet'.$appid.'.evalCommand("Spline'.$order.'['.join(',',@coords).']");'."\n";
   return $result;
}

#
# Object
#

sub start_plotobject {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $internalid = $Apache::inputtags::part.'_'.$Apache::inputtags::response[-1];
   my $x=&Apache::lonxml::get_param('x',$parstack,$safeeval);
   my $y=&Apache::lonxml::get_param('y',$parstack,$safeeval);
   my $label=&Apache::lonxml::get_param('label',$parstack,$safeeval);
   $label=~s/\W//gs;
   $label=ucfirst($label);
   unless ($label) { $label="NewObject"; }
   if ($target eq 'web') {
      my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-3);
      unless (defined($x)) { $x=$xmin; }
      unless (defined($y)) { $y=$ymin; }
      $result.=&plotobject_script($internalid,$label,$x,$y);
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Plot Object').
             &Apache::edit::text_arg('Label on Plot:','label',
                                     $token,'16').
             &Apache::edit::text_arg('x:','x',
                                     $token,'8').
             &Apache::edit::text_arg('y:','y',
                                     $token,'8').
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    $env{'form.'.&Apache::edit::html_element_name('label')}=ucfirst($env{'form.'.&Apache::edit::html_element_name('label')});
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,$safeeval,'label','x','y');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
  }
  return $result;
}

sub end_plotobject {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}

#
# Vector
#

sub start_plotvector {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $internalid = $Apache::inputtags::part.'_'.$Apache::inputtags::response[-1];
   my $tailx=&Apache::lonxml::get_param('tailx',$parstack,$safeeval);
   my $taily=&Apache::lonxml::get_param('taily',$parstack,$safeeval);
   my $tipx=&Apache::lonxml::get_param('tipx',$parstack,$safeeval);
   my $tipy=&Apache::lonxml::get_param('tipy',$parstack,$safeeval);

   my $label=&Apache::lonxml::get_param('label',$parstack,$safeeval);
   $label=~s/\W//gs;
   $label=ucfirst($label);
   unless ($label) { $label="NewVector"; }
   if ($Apache::functionplotresponse::vectorlabels{$label}) {
       &Apache::lonxml::warning(&mt('Vector labels must be unique: [_1]',$label));
   }
   $Apache::functionplotresponse::vectorlabels{$label}=1;
   if ($target eq 'web') {
      my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-3);
      unless (defined($tailx)) { $tailx=$xmin; }
      unless (defined($taily)) { $taily=$ymin; }
      unless (defined($tipx)) { $tipx=$xmin; }
      unless (defined($tipy)) { $tipy=$ymin; }
      my $fixed=0;
      if ((&Apache::response::show_answer()) || (&Apache::response::check_status()>=2)) { $fixed=1; }
      $result.=&plotvector_script($internalid,$label,$tailx,$taily,$tipx,$tipy,$xmin,$xmax,$fixed);
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Plot Vector').
             &Apache::edit::text_arg('Label on Plot:','label',
                                     $token,'16').
             &Apache::edit::text_arg('Tail x:','tailx',
                                     $token,'8').
             &Apache::edit::text_arg('Tail y:','taily',
                                     $token,'8').
             &Apache::edit::text_arg('Tip x:','tipx',
                                     $token,'8').
             &Apache::edit::text_arg('Tip y:','tipy',
                                     $token,'8').

             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    $env{'form.'.&Apache::edit::html_element_name('label')}=ucfirst($env{'form.'.&Apache::edit::html_element_name('label')});
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,$safeeval,'label','tailx','taily','tipx','tipy');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
  }
  return $result;
}

sub end_plotvector {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}


#
# Vector sum - have GeoGebra draw a sum of specified vectors to help students draw
#

sub start_drawvectorsum {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result='';
    my $internalid = $Apache::inputtags::part.'_'.$Apache::inputtags::response[-1];
    my $internalappid=&appid($internalid);
    my $tailx=&Apache::lonxml::get_param('tailx',$parstack,$safeeval);
    my $taily=&Apache::lonxml::get_param('taily',$parstack,$safeeval);
    my $showvalue=&Apache::lonxml::get_param('showvalue',$parstack,$safeeval);
    my $vectorlist=&Apache::lonxml::get_param('vectorlist',$parstack,$safeeval);
    my $label=&Apache::lonxml::get_param('label',$parstack,$safeeval);
    $label=~s/\W//gs;
    $label=ucfirst($label);
    unless ($label) { $label="NewVector"; }
    if ($target eq 'web') {
        my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-3);
        unless (defined($tailx)) { $tailx=$xmin; }
        unless (defined($taily)) { $taily=$ymin; }
        unless (defined($vectorlist)) { $vectorlist=''; }
        my @vectors=split(/\,/,$vectorlist);
        if ($#vectors>0) {
            my @sumx=();
            my @sumy=();
            foreach my $thisvector (@vectors) {
                $thisvector=~s/\W//gs;
                $thisvector=ucfirst($thisvector);
                unless ($thisvector) { next; }
                unless ($Apache::functionplotresponse::vectorlabels{$thisvector}) {
                    &Apache::lonxml::warning(&mt('Vectors must be defined before using them for drawing vector sums: [_1]',$thisvector));
                    next;
                }
                my $vectorx=$thisvector.'X';
                my $vectory=$thisvector.'Y';
                $result.=(<<ENDADDVEC);
document.ggbApplet$internalappid.evalCommand("$vectorx=x($thisvector)");
document.ggbApplet$internalappid.evalCommand("$vectory=y($thisvector)");
document.ggbApplet$internalappid.evalCommand("Include$thisvector$label=Checkbox[]");
ENDADDVEC
                push(@sumx,"If[Include$thisvector$label,$vectorx,0]");
                push(@sumy,"If[Include$thisvector$label,$vectory,0]");
            }
            $result.="document.ggbApplet$internalappid.evalCommand(".'"'."xTot$label=".join('+',@sumx).'");'."\n";
            $result.="document.ggbApplet$internalappid.evalCommand(".'"'."yTot$label=".join('+',@sumy).'");'."\n";
            my $show=0;
            if ($showvalue=~/yes/i) {
                $show=1;
            }
            $result.=(<<ENDMAKEVECTOR);
document.ggbApplet$internalappid.evalCommand("$label=Vector[($tailx,$taily),($tailx+xTot$label,$taily+yTot$label)]");
document.ggbApplet$internalappid.setLabelVisible("$label",true);
document.ggbApplet$internalappid.setLineThickness("$label",8);
document.ggbApplet$internalappid.setColor("$label",255,0,0);
document.ggbApplet$internalappid.setLabelStyle("$label",VALUE=$show);
ENDMAKEVECTOR
        }
    } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Draw Vector Sum').
             &Apache::edit::text_arg('Label on Plot:','label',
                                     $token,'16').
             &Apache::edit::text_arg('Tail x:','tailx',
                                     $token,'8').
             &Apache::edit::text_arg('Tail y:','taily',
                                     $token,'8').
             &Apache::edit::select_arg('Show Value:','showvalue',
                                  ['yes','no'],$token).'<br />'.
             &Apache::edit::text_arg('Vector List:','vectorlist',
                                     $token,'40').
             &Apache::edit::end_row();
    } elsif ($target eq 'modified') {
        $env{'form.'.&Apache::edit::html_element_name('label')}=ucfirst($env{'form.'.&Apache::edit::html_element_name('label')});
        my $constructtag=&Apache::edit::get_new_args($token,$parstack,$safeeval,'label','tailx','taily','showvalue','vectorlist');
        if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
    }
    return $result;
}


sub end_drawvectorsum {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
    my $result='';
    if ($target eq 'edit') {
        $result=&Apache::edit::end_table();
    }
    return $result;
}



#
# <backgroundplot function="..." fixed="yes/no" />
#
sub start_backgroundplot {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $internalid = $Apache::inputtags::part.'_'.$Apache::inputtags::response[-1];
   my $function=&Apache::lonxml::get_param('function',$parstack,$safeeval);
   my $xinitial=&Apache::lonxml::get_param('xinitial',$parstack,$safeeval);
   my $xfinal=&Apache::lonxml::get_param('xfinal',$parstack,$safeeval);
   my $label=&Apache::lonxml::get_param('label',$parstack,$safeeval);
   my $color=&Apache::lonxml::get_param('color',$parstack,$safeeval);
   $color=~s/[^a-fA-F0-9]//gs;
   unless (length($color)==6) { $color=''; }
   my $fixed=(&Apache::lonxml::get_param('fixed',$parstack,$safeeval)=~/on|true|yes|1/i?1:0);
 
   unless ($function) { $function="0"; }
   if ($target eq 'web') {
      my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-3);
      unless (defined($xinitial)) { $xinitial=$xmin; }
      unless (defined($xfinal)) { $xfinal=$xmax; }
      $result.=&plot_script($internalid,$function,$fixed,$label,$color,$xinitial,$xfinal);
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Background Function Plot').
             &Apache::edit::text_arg('Function:','function',
                                     $token,'16').
             &Apache::edit::text_arg('Initial x-value (optional):','xinitial',
                                     $token,'8').
             &Apache::edit::text_arg('Final x-value (optional):','xfinal',
                                     $token,'8').
             &Apache::edit::text_arg('Label on Plot:','label',
                                     $token,'8').
             &Apache::edit::text_arg('Color (hex code):','color',
                                     $token,'8', 'colorchooser').
             &Apache::edit::select_arg('Fixed location:','fixed',
                                  ['yes','no'],$token).
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                 $safeeval,'function','label','xinitial','xfinal','color','fixed');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
  }
  return $result;
}

sub end_backgroundplot {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}

#
# <functionplotrule ... />
#
sub start_functionplotrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
   $Apache::functionplotresponse::counter++;
   if ($label=~/\W/) {
      &Apache::lonxml::warning(&mt('Rule indices should only contain alphanumeric characters.'));
   }
   $label=~s/\W//gs;
   unless ($label) {
      $label='R'.$Apache::functionplotresponse::counter;
   } else {
      $label='R'.$label;
   }

   if ($target eq 'grade') {
# Simply remember - in order - for later
      my $beginninglabel=&Apache::lonxml::get_param('xinitiallabel',$parstack,$safeeval);
      my $endinglabel=&Apache::lonxml::get_param('xfinallabel',$parstack,$safeeval);
      if (($beginninglabel=~/\W/) || ($endinglabel=~/W/)) {
          &Apache::lonxml::warning(&mt('Rule labels must be alphanumeric.'));
      }
      $beginninglabel=~s/\W//gs;
      $endinglabel=~s/\W//gs;
      my $relationship=&Apache::lonxml::get_param('relationship',$parstack,$safeeval);
      $relationship=~s/\W//gs;
      $relationship=lc($relationship);
      unless ($relationship=~/^(eq|ge|gt|le|lt|ne)$/) {
          &Apache::lonxml::warning(&mt('Rule relationship not defined.'));
          $relationship='eq';
      }
      my $derivative=&Apache::lonxml::get_param('derivativeorder',$parstack,$safeeval);
      unless (($derivative==-1) || ($derivative==0) || ($derivative==1) || ($derivative==2)) {
         &Apache::lonxml::warning(&mt('Rule derivative not defined.'));
         $derivative=0;
      }
      push(@Apache::functionplotresponse::functionplotrules,join(':',(
           $label,
           $derivative,
           &Apache::lonxml::get_param('xinitial',$parstack,$safeeval),
           $beginninglabel,
           &Apache::lonxml::get_param('xfinal',$parstack,$safeeval),
           $endinglabel,
           &Apache::lonxml::get_param('minimumlength',$parstack,$safeeval),
           &Apache::lonxml::get_param('maximumlength',$parstack,$safeeval),
           $relationship,
           &Apache::lonxml::get_param('value',$parstack,$safeeval),
           &Apache::lonxml::get_param('percenterror',$parstack,$safeeval)
          )));
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Function Plot Graph Rule').
             &Apache::edit::text_arg('Index/Name:','index',
                                     $token,'10').'&nbsp;'.
             &Apache::edit::select_arg('Function:','derivativeorder',
                                  [['0','Function itself'],
                                   ['1','First derivative'],
                                   ['2','Second derivative'],
                                   ['-1','Integral']],$token).'<br />'.
             &Apache::edit::text_arg('Initial x-value:','xinitial',
                                      $token,'8').
             &Apache::edit::select_or_text_arg('Initial x-value label:','xinitiallabel',
                                               [['start','Start of Plot'],
                                                ['end','End of Plot']],$token,'8').'<br />'.

             &Apache::edit::text_arg('Final x-value (optional):','xfinal',
                                      $token,'8').
             &Apache::edit::select_or_text_arg('Final x-value label (optional):','xfinallabel',
                                               [['end','End of Plot']],$token,'8').'<br />'.
             &Apache::edit::text_arg('Minimum length for range (optional):','minimumlength',
                                     $token,'8').
             &Apache::edit::text_arg('Maximum length for range (optional):','maximumlength',
                                     $token,'8').'<br />'.
             &Apache::edit::select_or_text_arg(&mt('Relationship:'),'relationship',
                                  [['eq','equal'],
                                   ['ne','not equal'],
                                   ['ge','greater than or equal'],
                                   ['gt','greater than'],
                                   ['lt','less than'],
                                   ['le','less than or equal']],$token).
             $result.= &Apache::edit::select_or_text_arg('Value:','value',
                                               [['undef','not defined']],$token,'30').
             &Apache::edit::text_arg('Percent error:','percenterror',
                                     $token,'8').
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    if (($env{'form.'.&Apache::edit::html_element_name('xinitial')} ne '') && ($env{'form.'.&Apache::edit::html_element_name('xinitiallabel')} eq 'start')) {
       $env{'form.'.&Apache::edit::html_element_name('xinitiallabel')}='';
    }
    if (($env{'form.'.&Apache::edit::html_element_name('xfinal')} ne '') && ($env{'form.'.&Apache::edit::html_element_name('xfinallabel')} eq 'end')) {
       $env{'form.'.&Apache::edit::html_element_name('xfinallabel')}='';
    }
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                 $safeeval,'index','derivativeorder',
                                                           'xinitial','xinitiallabel','xfinal','xfinallabel',
                                                           'minimumlength','maximumlength',
                                                           'relationship','value','percenterror');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
   }
   return $result;
}

sub end_functionplotrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}


#
# <functionplotvectorrule ... />
#
sub start_functionplotvectorrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
   $Apache::functionplotresponse::counter++;
   if ($label=~/\W/) {
      &Apache::lonxml::warning(&mt('Rule indices should only contain alphanumeric characters.'));
   }
   $label=~s/\W//gs;
   unless ($label) {
      $label='R'.$Apache::functionplotresponse::counter;
   } else {
      $label='R'.$label;
   }

   if ($target eq 'grade') {
# Simply remember - in order - for later

      my $id=$Apache::inputtags::response[-1];
      my $partid=$Apache::inputtags::part;
      my $internalid = $partid.'_'.$id;

      my $vector=&Apache::lonxml::get_param('vector',$parstack,$safeeval);
      $vector=~s/\W//gs;
      $vector=ucfirst($vector);

      push(@Apache::functionplotresponse::functionplotvectorrules,join(':',(
           $label,
           'vector',
           $internalid,
           $vector,
           &Apache::lonxml::get_param('attachpoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('notattachpoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('tailpoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('tippoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('nottailpoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('nottippoint',$parstack,$safeeval),
           &Apache::lonxml::get_param('length',$parstack,$safeeval),
           &Apache::lonxml::get_param('angle',$parstack,$safeeval),
           &Apache::lonxml::get_param('lengtherror',$parstack,$safeeval),
           &Apache::lonxml::get_param('angleerror',$parstack,$safeeval),
          )));
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Function Plot Vector Rule').
             &Apache::edit::text_arg('Index/Name:','index',
                                     $token,'10').'&nbsp;'.
             &Apache::edit::text_arg('Vector:','vector',
                                      $token,'16').'<br />'.
             &Apache::edit::text_arg('Attached to object:','attachpoint',
                                      $token,'16').
             &Apache::edit::text_arg('Not attached to object:','notattachpoint',
                                      $token,'16').'<br />'.
             &Apache::edit::text_arg('Tail attached to object:','tailpoint',
                                      $token,'16').
             &Apache::edit::text_arg('Tip attached to object:','tippoint',
                                      $token,'16').
             &Apache::edit::text_arg('Tail not attached to object:','nottailpoint',
                                      $token,'16').
             &Apache::edit::text_arg('Tip not attached to object:','nottippoint',
                                      $token,'16').'<br />'.
             &Apache::edit::text_arg('Length:','length',
                                     $token,'30').
             &Apache::edit::text_arg('Absolute error length:','lengtherror',
                                     $token,'8').'<br />'.
             &Apache::edit::text_arg('Angle:','angle',
                                     $token,'30').
             &Apache::edit::text_arg('Absolute error angle:','angleerror',
                                     $token,'8').
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    $env{'form.'.&Apache::edit::html_element_name('vector')}=ucfirst($env{'form.'.&Apache::edit::html_element_name('vector')});
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                 $safeeval,'index','vector','attachpoint','notattachpoint',
                                                           'tailpoint','tippoint','nottailpoint','nottipoint',
                                                           'length','angle',
                                                           'lengtherror','angleerror');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
   }
   return $result;
}

sub end_functionplotvectorrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}

#
# <functionplotvectorsumrule ... />
#
sub start_functionplotvectorsumrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
   $Apache::functionplotresponse::counter++;
   if ($label=~/\W/) {
      &Apache::lonxml::warning(&mt('Rule indices should only contain alphanumeric characters.'));
   }
   $label=~s/\W//gs;
   unless ($label) {
      $label='R'.$Apache::functionplotresponse::counter;
   } else {
      $label='R'.$label;
   }
   if ($target eq 'grade') {
# Simply remember - in order - for later
      my $id=$Apache::inputtags::response[-1];
      my $partid=$Apache::inputtags::part;
      my $internalid = $partid.'_'.$id;
      my $vectors=&Apache::lonxml::get_param('vectors',$parstack,$safeeval);
      push(@Apache::functionplotresponse::functionplotvectorrules,join(':',(
           $label,
           'sum',
           $internalid,
           $vectors,
           &Apache::lonxml::get_param('length',$parstack,$safeeval),
           &Apache::lonxml::get_param('angle',$parstack,$safeeval),
           &Apache::lonxml::get_param('lengtherror',$parstack,$safeeval),
           &Apache::lonxml::get_param('angleerror',$parstack,$safeeval),
          )));
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Function Plot Vector Sum Rule').
             &Apache::edit::text_arg('Index/Name:','index',
                                     $token,'10').'&nbsp;'.
             &Apache::edit::text_arg('Comma-separated list of vectors:','vectors',
                                      $token,'30').'<br />'.
             &Apache::edit::text_arg('Sum vector length:','length',
                                     $token,'30').
             &Apache::edit::text_arg('Absolute error length:','lengtherror',
                                     $token,'8').'<br />'.
             &Apache::edit::text_arg('Sum vector angle:','angle',
                                     $token,'30').
             &Apache::edit::text_arg('Absolute error angle:','angleerror',
                                     $token,'8').
             &Apache::edit::end_row();
   } elsif ($target eq 'modified') {
      my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                   $safeeval,'index','vectors',
                                                             'length','angle',
                                                             'lengtherror','angleerror');
      if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
   }
   return $result;
}

sub end_functionplotvectorsumrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}

#
# <functionplotcustom ... />
#
sub start_functionplotcustomrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
   $Apache::functionplotresponse::counter++;
   if ($label=~/\W/) {
      &Apache::lonxml::warning(&mt('Rule indices should only contain alphanumeric characters.'));
   }
   $label=~s/\W//gs;
   unless ($label) {
      $label='R'.$Apache::functionplotresponse::counter;
   } else {
      $label='R'.$label;
   }
   &Apache::lonxml::register('Apache::response',('answer'));
   if ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Function Plot Custom Rule').
             &Apache::edit::text_arg('Index/Name:','index',$token,'10').
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
      my $constructtag=&Apache::edit::get_new_args($token,$parstack,$safeeval,'index');
      if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
   }
   return $result;
}

sub end_functionplotcustomrule {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
      $result=&Apache::edit::end_table();
   } elsif ($target eq 'grade') {
# Simply remember - in order - for later
      my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
      $Apache::functionplotresponse::counter++;
      if ($label=~/\W/) {
         &Apache::lonxml::warning(&mt('Rule indices should only contain alphanumeric characters.'));
      }
      $label=~s/\W//gs;
      unless ($label) {
         $label='R'.$Apache::functionplotresponse::counter;
      } else {
         $label='R'.$label;
      }
      push(@Apache::functionplotresponse::functionplotvectorrules,join(':',(
           $label,
           'custom',
           &escape($Apache::response::custom_answer[-1])
          )));
   }
   &Apache::lonxml::deregister('Apache::response',('answer'));
   return $result;
}



#
# <spline index="..." order="1,2,3,4" initx="..." inity="..." scalex="..." scaley="..." />
#
# Unfortunately, GeoGebra seems to want all splines after everything else, so we need to store them
#
sub start_spline {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'web') {
      my $label=&Apache::lonxml::get_param('index',$parstack,$safeeval);
      $Apache::functionplotresponse::counter++;
      if ($label=~/\W/) {
         &Apache::lonxml::warning(&mt('Spline indices should only contain alphanumeric characters.'));
      }
      $label=~s/\W//gs;
      unless ($label) { 
         $label='S'.$Apache::functionplotresponse::counter; 
      } else {
         $label='S'.$label;
      }
      if ($Apache::functionplotresponse::splineorder{$label}) {
         &Apache::lonxml::error(&mt('Spline indices must be unique.'));
      }

      my $order=&Apache::lonxml::get_param('order',$parstack,$safeeval);
      if ($order<2) { $order=2; }
      if ($order>8) { $order=8; }
      $Apache::functionplotresponse::splineorder{$label}=$order;

      my $x=&Apache::lonxml::get_param('initx',$parstack,$safeeval);
      unless ($x) { $x=0; }
      $Apache::functionplotresponse::splineinitx{$label}=$x;

      my $y=&Apache::lonxml::get_param('inity',$parstack,$safeeval);
      unless ($y) { $y=0; }
      $Apache::functionplotresponse::splineinity{$label}=$y;

      my $sx=&Apache::lonxml::get_param('scalex',$parstack,$safeeval);
      unless ($sx) { $sx=$order; }
      $Apache::functionplotresponse::splinescalex{$label}=$sx;

      my $sy=&Apache::lonxml::get_param('scaley',$parstack,$safeeval);
      unless ($sy) { $sy=2; }
      $Apache::functionplotresponse::splinescaley{$label}=$sy;
   } elsif ($target eq 'edit') {
        $result=&Apache::edit::tag_start($target,$token,'Spline').
             &Apache::edit::text_arg('Index:','index',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::select_arg('Order:','order',
                                  ['2','3','4','5','6','7','8'],$token).'&nbsp;'.
             &Apache::edit::text_arg('Initial x-value:','initx',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::text_arg('Initial y-value:','inity',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::text_arg('Scale x:','scalex',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::text_arg('Scale y:','scaley',
                                     $token,'4').
             &Apache::edit::end_row();
  } elsif ($target eq 'modified') {
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                 $safeeval,'index','order','initx','inity',
                                                           'scalex','scaley');
    if ($constructtag) { $result=&Apache::edit::rebuild_tag($token); }
  }
  return $result;
}

sub end_spline {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   if ($target eq 'edit') {
       $result=&Apache::edit::end_table();
   }
   return $result;
}
 
sub end_init_script {
    return (<<ENDENDINIT);
}
// ]]>
</script>
ENDENDINIT
}

#
# Storing and restoring spline coordinates from part answers
#
sub decode_previous_answer {
   my ($answer)=@_;
   foreach my $coordinate (split(/\,/,$answer)) {
      my ($key,$value)=split(/\=/,$coordinate);
      $Apache::functionplotresponse::previous{$key}=$value;
   }
}

sub get_answer_from_form_fields {
   my ($id)=@_;
   my $answer='';
   my %coords=();
   foreach my $field (keys(%env)) {
      if ($field=~/^form\.HWVAL\_$id/) {
         $field=~/^form\.(.*)$/;
         $coords{$1}=$env{$field};
      }
   }
   $answer=join(',',map { $_.'='.$coords{$_} } (sort(keys(%coords))));
   return ($answer,%coords);
}

#
# The following functions calculate the cubic-hermite splines server-side
#

sub cubic_hermite {
   my ($t,$p1,$s1,$p2,$s2)=@_;
   return (2.*$t*$t*$t-3.*$t*$t+1.)*$p1 + 3.*($t*$t*$t-2.*$t*$t+$t)*($s1-$p1)+
          (-2.*$t*$t*$t+3.*$t*$t)  *$p2 + 3.*($t*$t*$t-$t*$t)      *($s2-$p2);
}

#
# d/dt(...)
# 

sub ddt_cubic_hermite {
   my ($t,$p1,$s1,$p2,$s2)=@_;
   return (6.*$t*$t-6.*$t) *$p1 + 3.*(3.*$t*$t-4.*$t+1.)*($s1-$p1)+
          (-6.*$t*$t+6.*$t)*$p2 + 3.*(3.*$t*$t-2.*$t)   *($s2-$p2);
}

#
# d^2/dt^2(...)
#

sub d2dt2_cubic_hermite {
   my ($t,$p1,$s1,$p2,$s2)=@_;
   return (12.*$t-6.) *$p1 + 3.*(6.*$t-4.)*($s1-$p1)+
          (-12.*$t+6.)*$p2 + 3.*(6.*$t-2.)*($s2-$p2);
}

#
# Array index calculation
#
sub array_index {
   my ($xmin,$xmax,$x)=@_;
   if ($x ne '') {
      return int(($x-$xmin)/($xmax-$xmin)*400.+0.5);
   } else {
      return undef;
   }
}

#
# Populate the arrays
#

sub populate_arrays {
    my ($id,$xmin,$xmax,$ymin,$ymax)=@_;
    for (my $i=0; $i<=400; $i++) {
       $Apache::functionplotresponse::actualxval[$i]=undef;
       $Apache::functionplotresponse::func[$i]=undef;
       $Apache::functionplotresponse::dfuncdx[$i]=undef;
       $Apache::functionplotresponse::d2funcd2x[$i]=undef;
    }
    unless ($xmax>$xmin) { return 'no_func'; }
# Run over all splines in response
    foreach my $label (split(/\,/,$env{"form.HWVAL_AllSplines_$id"})) {
        my $xiold=-1;
# Run over all points in spline
        for (my $i=1; $i<$env{"form.HWVAL_SplineOrder_".$id."_".$label}; $i++) {
            my $ni=$i+1;
            my @xparms=($env{'form.HWVAL_'.$id.'_'.$label.'P'.$i.'_x'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'S'.$i.'_x'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'P'.$ni.'_x'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'S'.$ni.'_x'});
            my @yparms=($env{'form.HWVAL_'.$id.'_'.$label.'P'.$i.'_y'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'S'.$i.'_y'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'P'.$ni.'_y'},
                        $env{'form.HWVAL_'.$id.'_'.$label.'S'.$ni.'_y'});
# Run in small steps over spline parameter
            for (my $t=0; $t<=1; $t+=0.0001) {
                my $xreal=&cubic_hermite($t,@xparms);
                my $xi=&array_index($xmin,$xmax,$xreal);
                if ($xi<$xiold) { return 'no_func'; }
                if (($xi>$xiold) && ($xi>=0) && ($xi<=400)) {
                   $xiold=$xi;
                   $Apache::functionplotresponse::actualxval[$xi]=$xreal;
# Function value
                   my $funcval=&cubic_hermite($t,@yparms);

# Do we already have a value for this point, and is it different from the new one?
                   if ((defined($Apache::functionplotresponse::func[$xi])) &&
                       (abs($Apache::functionplotresponse::func[$xi]-$funcval)>($ymax-$ymin)/100.)) { 
                       return 'no_func'; 
                   }
# Okay, remember the new point
                   $Apache::functionplotresponse::func[$xi]=$funcval;

                   if (defined($funcval)) {
                      if ($xi<$Apache::functionplotresponse::functionplotrulelabels{'start'}) {
                         $Apache::functionplotresponse::functionplotrulelabels{'start'}=$xi;
                      }
                      if ($xi>$Apache::functionplotresponse::functionplotrulelabels{'end'}) {
                         $Apache::functionplotresponse::functionplotrulelabels{'end'}=$xi;
                      }
                   }
# Chain rule
# dy/dx=dy/dt/(dx/dt)
                   my $dxdt=&ddt_cubic_hermite($t,@xparms);
                   if ($dxdt) {
                      $Apache::functionplotresponse::dfuncdx[$xi]=&ddt_cubic_hermite($t,@yparms)/$dxdt;
# Second derivative
                      $Apache::functionplotresponse::d2funcdx2[$xi]=
                         ($dxdt*&d2dt2_cubic_hermite($t,@yparms)-&ddt_cubic_hermite($t,@yparms)*&d2dt2_cubic_hermite($t,@xparms))/
                         ($dxdt*$dxdt*$dxdt);
                   }
                }
            }
        }
    }
}

#
# Implementation of <functionplotresponse>
#

sub start_functionplotresponse {
  my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  my $result='';
# To remember the splines - somehow, they need to come last
  undef %Apache::functionplotresponse::splineorder;
  undef %Apache::functionplotresponse::splineinitx;
  undef %Apache::functionplotresponse::splineinity;
  undef %Apache::functionplotresponse::splinescalex;
  undef %Apache::functionplotresponse::splinescaley;
# Remember input fields, etc
  undef %Apache::functionplotresponse::previous;
  $Apache::functionplotresponse::inputfields='';
  $Apache::functionplotresponse::counter=0;
# Remember vectors
  undef %Apache::functionplotresponse::vectorlabels;
# Remember rules
  undef @Apache::functionplotresponse::functionplotrules;
  undef @Apache::functionplotresponse::functionplotvectorrules;
# Remember failed rules
  if ($target eq 'grade') {
     undef @Apache::functionplotresponse::failedrules;
  }
# Delete previous awards
  undef $Apache::functionplotresponse::awarddetail;
# Part and ID
  my $partid=$Apache::inputtags::part;
  my $id=&Apache::response::start_response($parstack,$safeeval);
# Internal ID to mark the applet and its coordinates
  my $internalid = $partid.'_'.$id;
# Previous answer
  &decode_previous_answer($Apache::lonhomework::history{"resource.$partid.$id.submission"});

# Parameters of <functionplotresponse>
  my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval);
  my $xaxisvisible=(&Apache::lonxml::get_param('xaxisvisible',$parstack,$safeeval)=~/on|true|yes|1/i?'true':'false');
  my $yaxisvisible=(&Apache::lonxml::get_param('yaxisvisible',$parstack,$safeeval)=~/on|true|yes|1/i?'true':'false');
  my $gridvisible=(&Apache::lonxml::get_param('gridvisible',$parstack,$safeeval)=~/on|true|yes|1/i?'true':'false');
  my $xlabel=&Apache::lonxml::get_param('xlabel',$parstack,$safeeval);
  my $ylabel=&Apache::lonxml::get_param('ylabel',$parstack,$safeeval);
  if ($target eq 'edit') {
    $result.=&Apache::edit::start_table($token)
       .'<tr><td><span class="LC_nobreak">'.&Apache::loncommon::insert_folding_button().&mt('Function Plot Question').'</span></td>'
       .'<td><span class="LC_nobreak">'.&mt('Delete?').' '
       .&Apache::edit::deletelist($target,$token).'&nbsp;&nbsp;&nbsp;'
       .&Apache::edit::insertlist($target,$token).'&nbsp;&nbsp;&nbsp;'
       .&Apache::loncommon::help_open_topic('Function_Plot_Response_Question','Function Plot Responses')
       .'</span></td>'
       ."<td>&nbsp;"
       .&Apache::edit::end_row()
       .&Apache::edit::start_spanning_row()
       ."\n";
    $result.=&Apache::edit::text_arg('Width (pixels):','width',
                                     $token,'6').'&nbsp;'.
             &Apache::edit::text_arg('Height (pixels):','height',
                                     $token,'6').'<br />'.
             &Apache::edit::text_arg('Label x-axis:','xlabel',
                                     $token,'6').'&nbsp;'.
             &Apache::edit::text_arg('Minimum x-value:','xmin',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::text_arg('Maximum x-value:','xmax',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::select_arg('x-axis visible:','xaxisvisible',
                                  ['yes','no'],$token).'<br />'.
             &Apache::edit::text_arg('Label y-axis:','ylabel',
                                     $token,'6').'&nbsp;'.
             &Apache::edit::text_arg('Minimum y-value:','ymin',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::text_arg('Maximum y-value:','ymax',
                                     $token,'4').'&nbsp;'.
             &Apache::edit::select_arg('y-axis visible:','yaxisvisible',
                                  ['yes','no'],$token).'<br />'.
             &Apache::edit::select_arg('Grid visible:','gridvisible',
                                  ['yes','no'],$token).'<br />'.
             &Apache::edit::text_arg('Background plot(s) for answer (function(x):xmin:xmax,function(x):xmin:xmax,x1:y1:sx1:sy1:x2:y2:sx2:sy2,...):',
                                         'answerdisplay',$token,'50').
             &Apache::edit::end_row().&Apache::edit::start_spanning_row();
  } elsif ($target eq 'modified') {
    my $constructtag=&Apache::edit::get_new_args($token,$parstack,
                                                 $safeeval,'width','height','xlabel','xmin','xmax','ylabel','ymin','ymax',
                                                           'xaxisvisible','yaxisvisible','gridvisible','answerdisplay');
    if ($constructtag) { $result = &Apache::edit::rebuild_tag($token); }

  } elsif ($target eq 'meta') {
       $result=&Apache::response::meta_package_write('functionplotresponse');
  } elsif (($target eq 'answer') &&
            ($env{'form.answer_output_mode'} ne 'tex') &&
            ($Apache::lonhomework::viewgrades == 'F')) {
      my (undef,undef,$udom,$uname)=&Apache::lonnet::whichuser();
      $uname =~s/\W//g;
      $udom  =~s/\W//g;
      my $function_name =
                join('_','LONCAPA_scriptvars',$uname,$udom,
                     $env{'form.counter'},$Apache::lonxml::curdepth);
      &Apache::lonxml::add_script_result(
          &Apache::loncommon::modal_adhoc_window($function_name,700,500,
             '<pre style="background-color:#ffffff;">'.$Apache::functionplotresponse::ruleslog.'</pre>',
              &mt('Rules Log'))."<br />");
  }
  return $result;
}

sub compare_rel {
   my ($relationship,$value,$realval,$tol)=@_;
# is the real value undefined?
   unless (defined($realval)) {
# the real value is not defined
      if ($relationship eq 'eq') {
         if ($value eq 'undef') {
            return 1;
         } else {
            return 0;
         }
      } elsif ($relationship eq 'ne') {
         if ($value eq 'undef') {
            return 0;
         } else {
            return 1;
         }
      } else {
         return 0;
      }
   }

# is the expected value undefined?
   if ($value eq 'undef') {
# but by now we know that the real value is defined
      return 0;
   }

# both are defined.
   if ($relationship eq 'gt') {
      return ($realval>$value);
   } elsif ($relationship eq 'ge') {
      return ($realval>$value-$tol);
   } elsif ($relationship eq 'lt') {
      return ($realval<$value);
   } elsif ($relationship eq 'le') {
      return ($realval<$value+$tol);
   } elsif ($relationship eq 'ne') {
      return (abs($value-$realval)>$tol);
   } else {
      return (abs($value-$realval)<$tol);
   }
   return 0;
}

sub addlog {
   my ($text)=@_;
   $text=~s/\'/\\\'/g;
   $Apache::functionplotresponse::ruleslog.=$text.'<br />';
}

sub actualval {
   my ($i,$xmin,$xmax)=@_;
   return $xmin+$i/400.*($xmax-$xmin);
}

sub fpr_val {
   my ($arg)=@_;
   return &actualval($Apache::functionplotresponse::functionplotrulelabels{$arg},
                     $Apache::functionplotresponse::fpr_xmin,
                     $Apache::functionplotresponse::fpr_xmax);
}

sub fpr_f {
   my ($arg)=@_;
   return $Apache::functionplotresponse::func[&array_index($Apache::functionplotresponse::fpr_xmin,
                                                           $Apache::functionplotresponse::fpr_xmax,
                                                           $arg)];
}

sub fpr_dfdx {
   my ($arg)=@_;
   return $Apache::functionplotresponse::dfuncdx[&array_index($Apache::functionplotresponse::fpr_xmin,
                                                              $Apache::functionplotresponse::fpr_xmax,
                                                              $arg)];
}

sub fpr_d2fdx2 {
   my ($arg)=@_;
   return $Apache::functionplotresponse::d2funcdx2[&array_index($Apache::functionplotresponse::fpr_xmin,
                                                                $Apache::functionplotresponse::fpr_xmax,
                                                                $arg)];
}

sub fpr_vectorcoords {
   my ($arg)=@_;
   $arg=~s/\W//gs;
   $arg=ucfirst($arg);
   my $id=$Apache::inputtags::response[-1];
   my $partid=$Apache::inputtags::part;
   my $internalid = $partid.'_'.$id;
   return ($env{'form.HWVAL_'.$internalid.'_'.$arg.'Start_x'},
           $env{'form.HWVAL_'.$internalid.'_'.$arg.'End_x'},
           $env{'form.HWVAL_'.$internalid.'_'.$arg.'Start_y'},
           $env{'form.HWVAL_'.$internalid.'_'.$arg.'End_y'});
}

sub fpr_objectcoords {
   my ($arg)=@_;
   $arg=~s/\W//gs;
   $arg=ucfirst($arg);
   my $id=$Apache::inputtags::response[-1];
   my $partid=$Apache::inputtags::part;
   my $internalid = $partid.'_'.$id;
   return ($env{'form.HWVAL_'.$internalid.'_'.$arg.'_x'},
           $env{'form.HWVAL_'.$internalid.'_'.$arg.'_y'});
}

sub fpr_vectorlength {
   my ($arg)=@_;
   my ($xs,$xe,$ys,$ye)=&fpr_vectorcoords($arg);
   return sqrt(($xe-$xs)*($xe-$xs)+($ye-$ys)*($ye-$ys));
}

sub fpr_vectorangle {
   my ($arg)=@_;
   my ($xs,$xe,$ys,$ye)=&fpr_vectorcoords($arg);
   my $angle=57.2957795*atan2(($ye-$ys),($xe-$xs));
   if ($angle<0) { $angle=360+$angle; }
   return $angle;
}

sub vectorcoords {
   my ($id,$label)=@_;
   return ($env{'form.HWVAL_'.$id.'_'.$label.'Start_x'},
           $env{'form.HWVAL_'.$id.'_'.$label.'End_x'},
           $env{'form.HWVAL_'.$id.'_'.$label.'Start_y'},
           $env{'form.HWVAL_'.$id.'_'.$label.'End_y'});
}

sub objectcoords {
   my ($id,$label)=@_;
   return ($env{'form.HWVAL_'.$id.'_'.$label.'_x'},
           $env{'form.HWVAL_'.$id.'_'.$label.'_y'});
}

sub attached {
   my ($id,$vector,$objects,$xmin,$xmax,$ymin,$ymax)=@_;
   my ($xs,$xe,$ys,$ye)=&vectorcoords($id,$vector);
   my $tolx=($xmax-$xmin)/100.;
   my $toly=($ymax-$ymin)/100.;
   my $tail=0;
   my $tip=0;
   foreach my $obj (split(/\s*\,\s*/,$objects)) {
      $obj=~s/\W//g;
      unless ($obj) { next; }
      $obj=ucfirst($obj);
      my ($xo,$yo)=&objectcoords($id,$obj);
      &addlog("Proximity $vector ($xs,$ys)-($xe,$ye) to $obj ($xo,$yo)");
      if ((abs($xs-$xo)<$tolx) && (abs($ys-$yo)<$toly)) {
         $tail=1;
         &addlog("Attached tail: $obj"); 
      }
      if ((abs($xe-$xo)<$tolx) && (abs($ye-$yo)<$toly)) { 
         $tip=1;
         &addlog("Attached tip: $obj"); 
      }
   }
   &addlog("Result tail:$tail tip:$tip");
   return($tail,$tip);
}

 
sub vectorangle {
   my ($x,$y)=@_;
   my $angle=57.2957795*atan2($y,$x);
   if ($angle<0) { $angle=360+$angle; }
   return $angle;
}

sub vectorlength {
   my ($x,$y)=@_;
   return sqrt($x*$x+$y*$y);
}

sub relvector {
   my ($xs,$xe,$ys,$ye)=@_;
   return ($xe-$xs,$ye-$ys);
}

sub plotvectorlength {
   return &vectorlength(&relvector(&vectorcoords(@_)));
}

sub plotvectorangle {
   return &vectorangle(&relvector(&vectorcoords(@_)));
}


#
# Evaluate a functionplotvectorrule
#

sub functionplotvectorrulecheck {
   my ($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)=@_;
   &addlog("=================");
   my ($label,$type)=split(/\:/,$rule);
   if ($type eq 'vector') {
      return &vectorcheck($rule,$xmin,$xmax,$ymin,$ymax,$safeeval);
   } elsif ($type eq 'sum') {
      return &sumcheck($rule,$xmin,$xmax,$ymin,$ymax,$safeeval);
   } elsif ($type eq 'custom') {
      return &customcheck($rule,$safeeval);
   }
}

sub vectorcheck {
   my ($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)=@_;
   my ($label,$type,$id,$vector,
       $attachpoint,$notattachpoint,
       $tailpoint,$tippoint,$nottailpoint,$nottippoint,
       $length,$angle,$lengtherror,$angleerror)=split(/\:/,$rule);
   &addlog("Vector Rule $label for vector ".$vector);
   if ($length ne '') {
      &addlog("Checking for length $length with error $lengtherror");
      $length=&Apache::run::run($length,$safeeval);
      &addlog("Length evaluated to $length");
      my $thislength=&plotvectorlength($id,$vector);
      &addlog("Found length $thislength");
      if (abs($thislength-$length)>$lengtherror) {
         &setfailed($label);
         return 0;
      }
   }
   if ($angle ne '') {
      &addlog("Checking for angle $angle with error $angleerror");
      $angle=&Apache::run::run($angle,$safeeval);
      &addlog("Angle evaluated to $angle");
      my $thisangle=&plotvectorangle($id,$vector);
      &addlog("Found angle $thisangle");
      my $anglediff=abs($thisangle-$angle);
      &addlog("Angle difference: $anglediff");
      if ($anglediff>360.-$anglediff) {
         $anglediff=360.-$anglediff;
      }
      &addlog("Smallest angle difference: $anglediff");
      if ($anglediff>$angleerror) {
         &setfailed($label);
         return 0;
      }
   }
   if ($attachpoint ne '') {
      &addlog("Checking attached: ".$attachpoint);
      my ($tail,$tip)=&attached($id,$vector,$attachpoint,$xmin,$xmax,$ymin,$ymax);
      unless ($tail || $tip) {
         &setfailed($label);
         return 0;
      }
   }
   if ($notattachpoint ne '') {
      &addlog("Checking not attached: ".$notattachpoint);
      my ($tail,$tip)=&attached($id,$vector,$notattachpoint,$xmin,$xmax,$ymin,$ymax);
      if ($tail || $tip) {
         &setfailed($label);
         return 0;
      }
   }
   if ($tailpoint ne '') {
      &addlog("Checking tail: ".$tailpoint);
      my ($tail,$tip)=&attached($id,$vector,$tailpoint,$xmin,$xmax,$ymin,$ymax);
      unless ($tail) {
         &setfailed($label);
         return 0;
      }
   }
   if ($nottailpoint ne '') {
      &addlog("Checking not tail: ".$nottailpoint);
      my ($tail,$tip)=&attached($id,$vector,$nottailpoint,$xmin,$xmax,$ymin,$ymax);
      if ($tail) {
         &setfailed($label);
         return 0;
      }
   }
   if ($tippoint ne '') {
      &addlog("Checking tip: ".$tippoint);
      my ($tail,$tip)=&attached($id,$vector,$tippoint,$xmin,$xmax,$ymin,$ymax);
      unless ($tip) {
         &setfailed($label);
         return 0;
      }
   }
   if ($nottippoint ne '') {
      &addlog("Checking not tip: ".$nottippoint);
      my ($tail,$tip)=&attached($id,$vector,$nottippoint,$xmin,$xmax,$ymin,$ymax);
      if ($tip) {
         &setfailed($label);
         return 0;
      }
   }

   &addlog("Rule $label passed.");
   return 1;
}

sub sumcheck {
   my ($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)=@_;
   my ($label,$type,$id,$vectors,$length,$angle,$lengtherror,$angleerror)=split(/\:/,$rule);
   &addlog("Vector Sum Rule $label for vectors ".$vectors);
   my $sumx=0;
   my $sumy=0;
   foreach my $sv (split(/\s*\,\s*/,$vectors)) {
      my ($rx,$ry)=&relvector(&vectorcoords($id,$sv));
      $sumx+=$rx;
      $sumy+=$ry;
   }
   &addlog("Sum vector ($sumx,$sumy)");
   if ($length ne '') {
      &addlog("Checking length $length with error $lengtherror");
      $length=&Apache::run::run($length,$safeeval);
      &addlog("Evaluated to $length");
      my $thislength=&vectorlength($sumx,$sumy);
      &addlog("Actual length $thislength");
      if (abs($length-$thislength)>$lengtherror) {
                  &setfailed($label);
         return 0;
      }
   }
   if ($angle ne '') {
      &addlog("Checking angle $angle with error $angleerror");
      $angle=&Apache::run::run($angle,$safeeval);
      &addlog("Evaluated to $angle");
      my $thisangle=&vectorangle($sumx,$sumy);
      &addlog("Actual angle $thisangle");
      my $anglediff=abs($thisangle-$angle);
      &addlog("Angle difference: $anglediff");
      if ($anglediff>360.-$anglediff) {
         $anglediff=360.-$anglediff;
      }
      &addlog("Smallest angle difference: $anglediff");
      if ($anglediff>$angleerror) {
         &setfailed($label);
         return 0;
      }
   }
   &addlog("Rule $label passed.");
   return 1;
}

sub customcheck {
   my ($rule,$safeeval)=@_;
   my ($label,$type,$prg)=split(/\:/,$rule);
   &addlog("Custom Rule ".$label);
   my $result=&Apache::run::run(&unescape($prg),$safeeval);
   &addlog("Algorithm returned $result");
   unless ($result) {
      &setfailed($label);
      return 0;
   }
   &addlog("Rule $label passed.");
   return 1;
}

#
# Evaluate a functionplotrule
#
 
sub functionplotrulecheck {
   my ($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)=@_;

   my ($label,$derivative,$xinitial,$xinitiallabel,$xfinal,$xfinallabel,$minimumlength,$maximumlength,$relationship,$value,$percent)
      =split(/\:/,$rule);
   $percent=($percent>0?$percent:5);
   &addlog("=================");
   &addlog("Rule $label for ".($derivative<0?'integral':('function itself','first derivative','second derivative')[$derivative])." $relationship $value");
#
# Evaluate the value
#
   if (($value=~/\D/) && ($value ne 'undef')) {
      $Apache::functionplotresponse::fpr_xmin=$xmin;
      $Apache::functionplotresponse::fpr_xmax=$xmax;
      $value=&Apache::run::run($value,$safeeval);
      &addlog("Value evaluated to $value");
   }

#
# Minimum and maximum lengths of the interval
#
   if ((defined($minimumlength)) || (defined($maximumlength))) {
      &addlog("Minimumlength $minimumlength Maximumlength $maximumlength");
   }
   my $li=0;
   my $lh=400;

# Special case: the upper boundary was not defined
# and needs to be set to the value where
# the condition is not true anymore => set flag

   my $findupper=0;
   if (($xfinal eq '')
    && (!defined($Apache::functionplotresponse::functionplotrulelabels{$xfinallabel}))
    && ($xfinallabel)) {
       $findupper=1;
   }

# if a hard value is set for the boundaries, it overrides the label
   if (($xinitial ne '') && ($xinitiallabel ne '') && ($xinitiallabel ne 'start')) {
      $li=&array_index($xmin,$xmax,$xinitial);
      $Apache::functionplotresponse::functionplotrulelabels{$xinitiallabel}=$li;
   }
   if (($xfinal ne '') && ($xfinallabel ne '') && ($xfinallabel ne 'end')) {
      $lh=&array_index($xmin,$xmax,$xfinal);
      $Apache::functionplotresponse::functionplotrulelabels{$xfinallabel}=$lh;
   }
# if the label is defined, use it
   if (defined($Apache::functionplotresponse::functionplotrulelabels{$xinitiallabel})) {
      &addlog("Using lower label $xinitiallabel");
      $li=$Apache::functionplotresponse::functionplotrulelabels{$xinitiallabel};
   } else {
      $li=&array_index($xmin,$xmax,$xinitial);
   }
   unless ($findupper) {
      if (defined($Apache::functionplotresponse::functionplotrulelabels{$xfinallabel})) {
         &addlog("Using upper label $xfinallabel");
         $lh=$Apache::functionplotresponse::functionplotrulelabels{$xfinallabel}-1;
      } else {
         $lh=&array_index($xmin,$xmax,$xfinal);
      }
   }
# Basic sanity checks
   if ($li<0) { $li=0; }
   if ($lh>400) { $lh=400; }
   if (($li>$lh) || (!defined($lh))) {
       $lh=$li;
   }

   &addlog("Boundaries: x=".&actualval($li,$xmin,$xmax)." (".$Apache::functionplotresponse::actualxval[$li]."; index $li)) to x=".
                            &actualval($lh,$xmin,$xmax)." (".$Apache::functionplotresponse::actualxval[$lh]."; index $lh))");
   if ($findupper) {
      &addlog("Looking for label $xfinallabel");
   }
   my $tol=$percent*($ymax-$ymin)/100;
   if ($xmax>$xmin) {
      if ($derivative==2) {
         $tol=4.*$tol/($xmax-$xmin);
      } elsif ($derivative==1) {
         $tol=2.*$tol/($xmax-$xmin);
      } elsif ($derivative==-1) {
         $tol=$tol*($xmax-$xmin)/2.;
      }
   }
   my $integral=0;
   my $binwidth=($xmax-$xmin)/400.;
   if (($derivative<0) && (!$findupper)) {
# definite integral, calculate over whole length
     &addlog("Calculating definite integral");
     for (my $i=$li; $i<=$lh; $i++) {
        $integral+=$Apache::functionplotresponse::func[$i]*$binwidth;
     }
     unless (&compare_rel($relationship,$value,$integral,$tol)) {
        &addlog("Actual integral ".(defined($integral)?$integral:'undef').", expected $value, tolerance $tol");
        &addlog("Rule $label failed.");
        &setfailed($label);
        return 0;
     } 
   } else {
     for (my $i=$li; $i<=$lh; $i++) {
        my $val;
        if ($derivative==2) {
           $val=$Apache::functionplotresponse::d2funcdx2[$i];
        } elsif ($derivative==1) {
           $val=$Apache::functionplotresponse::dfuncdx[$i];
        } elsif ($derivative==-1) {
           $integral+=$Apache::functionplotresponse::func[$i]*$binwidth;
           $val=$integral;      
        } else {
           $val=$Apache::functionplotresponse::func[$i];
        }
        unless (&compare_rel($relationship,$value,$val,$tol)) { 
           &addlog("Actual value ".(defined($val)?$val:'undef').", expected $value, tolerance $tol");
           &addlog("Condition not fulfilled at x=".&actualval($i,$xmin,$xmax)." (".$Apache::functionplotresponse::actualxval[$i]."; index $i)");
           if (($findupper) && ($i>$li)) {
# Check lengths
              unless (&checklength($i,$li,$minimumlength,$maximumlength,$xmin,$xmax,$label)) { return 0; }
# Successfully found a new label, set it
              $Apache::functionplotresponse::functionplotrulelabels{$xfinallabel}=$i;
              &addlog("Rule $label passed, setting label $xfinallabel");
              return 1;
           } else {
              &addlog("Rule $label failed.");
              &setfailed($label);
              return 0; 
           }
        }
     }
   }
# Corner case where this makes sense: using start or stop as defined labels
   unless (&checklength($lh,$li,$minimumlength,$maximumlength,$xmin,$xmax,$label)) { return 0; }
   &addlog("Rule $label passed.");
   return 1;
}

#
# check for minimum and maximum lengths
#

sub checklength {
    my ($i,$li,$minimumlength,$maximumlength,$xmin,$xmax,$label)=@_;
    unless (($minimumlength) || ($maximumlength)) { return 1; }
    my $length=&actualval($i,$xmin,$xmax)-&actualval($li,$xmin,$xmax);
    if ($minimumlength) {
       if ($length<$minimumlength) {
          &addlog("Rule $label failed, actual length $length, minimum length $minimumlength");
          &setfailed($label);
          return 0;
       }
    }
    if ($maximumlength) {
       if ($length>$maximumlength) {
          &addlog("Rule $label failed, actual length $length, maximum length $maximumlength");
          &setfailed($label);
          return 0;
       }
    }
    return 1;
}

sub setfailed {
   my ($hintlabel)=@_;
   $hintlabel=~s/^R//;
   push(@Apache::functionplotresponse::failedrules,$hintlabel);
   &addlog("Set hint condition $hintlabel");
}

sub start_functionplotruleset {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   if ($target eq 'edit') {
      return &Apache::edit::start_table($token).
        '<tr><td><span class="LC_nobreak">'.&Apache::loncommon::insert_folding_button().&mt('Function Plot Rule Set').'</span></td>'
       .'<td><span class="LC_nobreak">'.&mt('Delete?').' '
       .&Apache::edit::deletelist($target,$token).'&nbsp;&nbsp;&nbsp;'.
        &Apache::edit::insertlist($target,$token).'&nbsp;&nbsp;&nbsp;'
       .&Apache::loncommon::help_open_topic('Function_Plot_Response_Rule_Set','Function Plot Rules')
       .'</span></td>'
       ."<td>&nbsp;"
       .&Apache::edit::end_row()
       .&Apache::edit::start_spanning_row()
       ."\n";
   }
}

sub end_functionplotruleset {
    my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
    my $id=$Apache::inputtags::response[-1];
    my $partid=$Apache::inputtags::part;
    my $internalid = $partid.'_'.$id;

    if ($target eq 'edit' ) {
        return &Apache::edit::end_table();
    }  elsif ($target eq 'grade'
         && &Apache::response::submitted()
         && $Apache::lonhomework::type ne 'exam') {
#
# Actually grade
#
    my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-2);

        my $ad='';
        undef  %Apache::functionplotresponse::functionplotrulelabels;
        $Apache::functionplotresponse::ruleslog='';
        $Apache::functionplotresponse::functionplotrulelabels{'start'}=400;
        $Apache::functionplotresponse::functionplotrulelabels{'end'}=0;
        if (&populate_arrays($internalid,$xmin,$xmax,$ymin,$ymax) eq 'no_func') {
           $ad='NOT_FUNCTION';
        } else {
           &addlog("Start of function ".&actualval($Apache::functionplotresponse::functionplotrulelabels{'start'},$xmin,$xmax)." (index ".
                                        $Apache::functionplotresponse::functionplotrulelabels{'start'}.")");
           &addlog("End of function ".&actualval($Apache::functionplotresponse::functionplotrulelabels{'end'},$xmin,$xmax)." (index ".
                                        $Apache::functionplotresponse::functionplotrulelabels{'end'}.")");

# We have a function that we can actually grade, go through the spline rules.
           foreach my $rule (@Apache::functionplotresponse::functionplotrules) {
              unless (&functionplotrulecheck($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)) {
                 $ad='INCORRECT';
                 last;
              }
           }
# And now go through the vector rules
           foreach my $rule (@Apache::functionplotresponse::functionplotvectorrules) {
              unless (&functionplotvectorrulecheck($rule,$xmin,$xmax,$ymin,$ymax,$safeeval)) {
                 $ad='INCORRECT';
                 last;
              }
           }
# If it's not wrong, it's correct 
           unless ($ad) { $ad='EXACT_ANS' };
        }
        &addlog("Set hint conditions: ".join(",",@Apache::functionplotresponse::failedrules));
        &addlog("Assigned award detail: $ad");
# Store for later to be assigned at end_functionplotresponse
        $Apache::functionplotresponse::awarddetail=$ad;
     }
}


sub end_functionplotresponse {
  my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
  &Apache::response::end_response;

  my $result;
  my $id=$Apache::inputtags::response[-1];
  my $partid=$Apache::inputtags::part;
  my $internalid = $partid.'_'.$id;

    if ($target eq 'edit') { $result=&Apache::edit::end_table(); }
    if ($target eq 'grade'
         && &Apache::response::submitted()
         && $Apache::lonhomework::type eq 'exam') {

        &Apache::response::scored_response($partid,$id);

    } elsif ($target eq 'grade'
         && &Apache::response::submitted()
         && $Apache::lonhomework::type ne 'exam') {
        my ($response,%coords)=&get_answer_from_form_fields($internalid);
        $Apache::lonhomework::results{"resource.$partid.$id.submission"}=$response;
        my %previous=&Apache::response::check_for_previous($response,$partid,$id);
#
# Assign grade
#
        my $ad=$Apache::functionplotresponse::awarddetail;
#
# Store grading info
#
        $Apache::lonhomework::results{"resource.$partid.$id.awarddetail"}=$ad;
        &Apache::response::handle_previous(\%previous,$ad);
   } elsif ($target eq 'web') {
        undef @Apache::functionplotresponse::failedrules;
   }
   return $result;
}

sub end_functionplotelements {
  my ($target,$token,$tagstack,$parstack,$parser,$safeeval)=@_;
  my $result='';
  my $id=$Apache::inputtags::response[-1];
  my $partid=$Apache::inputtags::part;
  my $internalid = $partid.'_'.$id;
  if ($target eq 'edit' ) {
     $result=&Apache::edit::end_table();
  } elsif ($target eq 'web') {
     my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-2);

# Are we in show answer mode?
     my $showanswer=&Apache::response::show_answer();
     if ($showanswer) {
# Render answerdisplay
        my $answerdisplay=&Apache::lonxml::get_param('answerdisplay',$parstack,$safeeval,-2);
        if ($answerdisplay=~/\S/s) {
           foreach my $plot (split(/\s*\,\s*/,$answerdisplay)) {
              my @components=split(/\s*\:\s*/,$plot);
              if ($#components<3) {
# Just a simple plot
                 my ($func,$xl,$xh)=@components;
                 if ((!defined($xl)) || ($xl eq '')) { $xl=$xmin; }
                 if ((!defined($xh)) || ($xh eq '')) { $xh=$xmax; }
                 $result.=&plot_script($internalid,$func,1,'','00aa00',$xl,$xh,6);
              } else {
# This is a spline
                 $result.=&answer_spline_script($internalid,@components);
              }
           }
        }
     }
     my $fixed=0;
     if (($showanswer) || (&Apache::response::check_status()>=2)) { $fixed=1; }
# Now is the time to render all of the stored splines
     foreach my $label (keys(%Apache::functionplotresponse::splineorder)) {
        $result.=&generate_spline($internalid,$label,$xmin,$xmax,$ymin,$ymax,$fixed);
     }
# close the init script
     $result.=&end_init_script();
# register all splines in this response 
     $result.='<input type="hidden" name="HWVAL_AllSplines_'.$internalid.'" value="'.
                 join(',',keys(%Apache::functionplotresponse::splineorder)).'" />'."\n";
     foreach my $label (keys(%Apache::functionplotresponse::splineorder)) {
        $result.='<input type="hidden" name="HWVAL_SplineOrder_'.$internalid.'_'.$label.'" value="'.
                 $Apache::functionplotresponse::splineorder{$label}.'" />'."\n";
     }
# generate the input fields
     $result.=$Apache::functionplotresponse::inputfields;
# actually start the <applet>-tag
     $result.=&geogebra_startcode($internalid,
                                  &Apache::lonxml::get_param('width',$parstack,$safeeval,-2),
                                  &Apache::lonxml::get_param('height',$parstack,$safeeval,-2));
# set default parameters
     $result.=&geogebra_default_parameters($internalid);
# close the <applet>-tag
     $result.=&geogebra_endcode();
  }
  return $result;
}

sub boundaries {
   my ($parstack,$safeeval,$level)=@_;
   my $xmin=&Apache::lonxml::get_param('xmin',$parstack,$safeeval,$level);
   $xmin=(defined($xmin)?$xmin:-10);
   my $xmax=&Apache::lonxml::get_param('xmax',$parstack,$safeeval,$level);
   $xmax=(defined($xmax)?$xmax:10);
   my $ymin=&Apache::lonxml::get_param('ymin',$parstack,$safeeval,$level);
   $ymin=(defined($ymin)?$ymin:-10);
   my $ymax=&Apache::lonxml::get_param('ymax',$parstack,$safeeval,$level);
   $ymax=(defined($ymax)?$ymax:10);
   if ($xmax<=$xmin) {
      $xmax=$xmin+20;
   }
   if ($ymax<=$ymin) {
      $ymax=$ymin+20;
   }
   return ($xmin,$xmax,$ymin,$ymax);
}

sub start_functionplotelements {
   my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
   my $result='';
   my $id=$Apache::inputtags::response[-1];
   my $partid=$Apache::inputtags::part;
   my $internalid = $partid.'_'.$id;

   if ($target eq 'edit') {
      return &Apache::edit::start_table($token).
        '<tr><td><span class="LC_nobreak">'.&Apache::loncommon::insert_folding_button()
       .&mt('Function Plot Elements').'</span></td>'
       .'<td><span class="LC_nobreak">'.&mt('Delete?').' '
       .&Apache::edit::deletelist($target,$token).'&nbsp;&nbsp;&nbsp;'.
        &Apache::edit::insertlist($target,$token).'&nbsp;&nbsp;&nbsp;'
       .&Apache::loncommon::help_open_topic('Function_Plot_Response_Elements','Function Plot Elements')
       .'</span></td>'
       ."<td>&nbsp;"
       .&Apache::edit::end_row()
       .&Apache::edit::start_spanning_row()
       ."\n";
   } elsif ($target eq 'web') {
      my ($xmin,$xmax,$ymin,$ymax)=&boundaries($parstack,$safeeval,-2);
      my $xaxisvisible=(&Apache::lonxml::get_param('xaxisvisible',$parstack,$safeeval,-2)=~/on|true|yes|1/i?'true':'false');
      my $yaxisvisible=(&Apache::lonxml::get_param('yaxisvisible',$parstack,$safeeval,-2)=~/on|true|yes|1/i?'true':'false');
      my $gridvisible=(&Apache::lonxml::get_param('gridvisible',$parstack,$safeeval,-2)=~/on|true|yes|1/i?'true':'false');
      my $xlabel=&Apache::lonxml::get_param('xlabel',$parstack,$safeeval,-2);
      my $ylabel=&Apache::lonxml::get_param('ylabel',$parstack,$safeeval,-2);


# paste in the update routine to receive stuff back from the applet
     $result.=&update_script($internalid);
# start the initscript for this applet
     $result.=&start_init_script($internalid);
# put the axis commands inside
     $result.=&axes_script($internalid,$xmin,$xmax,$ymin,$ymax,$xaxisvisible,$yaxisvisible,$gridvisible);
     $result.=&axes_label($internalid,$xmin,$xmax,$ymin,$ymax,$xlabel,$ylabel);
# init script is left open
  }
  return $result;
}

1;

__END__
 
=head1 NAME

Apache::functionplotresponse.pm;

=head1 SYNOPSIS

Handles tags associated with accepting function plots.

This is part of the LearningOnline Network with CAPA project
described at http://www.lon-capa.org.

=head1 HANDLER SUBROUTINE

start_functionplotresponse()

=head1 OTHER SUBROUTINES

=over

=item end_functionplotresponse()

=back

=cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>