API Docs for: 1.0.0
Show:

File: src/gallery-mathcanvas/js/FunctionWithArgs.js

/**
 * @module gallery-mathcanvas
 */

/**********************************************************************
 * <p>Function that takes one or more arguments.</p>
 * 
 * @namespace MathFunction
 * @class FunctionWithArgs
 * @extends MathFunction
 * @constructor
 * @param name {String} the name of the function
 * @param args {MathFunction|Array} the arguments
 */

function MathFunctionWithArgs(
	/* string */		name,
	/* MathFunction */	args)
{
	MathFunctionWithArgs.superclass.constructor.call(this);
	this.name = name;

	if (Y.Lang.isArray(args) && Y.Lang.isArray(args[0]))
	{
		args = args[0];
	}

	this.args = [];
	if (Y.Lang.isArray(args))
	{
		for (var i=0; i<args.length; i++)
		{
			this.appendArg(args[i]);
		}
	}
	else
	{
		for (var i=1; i<arguments.length; i++)
		{
			this.appendArg(arguments[i]);
		}
	}
}

Y.extend(MathFunctionWithArgs, MathFunction,
{
	/**
	 * By default, we assume the number of arguments is fixed.  Derived
	 * classes can override.
	 *
	 * @method getMaxArgCount
	 * @return {int} maximum number of arguments
	 */
	getMaxArgCount: function()
	{
		return this.args.length;
	},

	/**
	 * @method getArgCount
	 * @return {int} number of arguments
	 */
	getArgCount: function()
	{
		return this.args.length;
	},

	/**
	 * @method getArg
	 * @return {MathFunction} requested argument, or undefined
	 */
	getArg: function(
		/* int */ index)
	{
		return this.args[index];
	},

	/**
	 * @method getArgs
	 * @return {Array} array of arguments
	 */
	getArgs: function()
	{
		return this.args;
	},

	/**
	 * @method insertArgBefore
	 * @param f {MathFunction}
	 * @param before {MathFunction}
	 */
	insertArgBefore: function(
		/* MathFunction */	f,
		/* MathFunction */	before)
	{
		var i = Y.Array.indexOf(this.args, before);
		if (i >= 0)
		{
			f.parent = this;
			this.args.splice(i, 0, f);
		}
		else
		{
			this.appendArg(f);
		}
	},

	/**
	 * @method insertArgAfter
	 * @param f {MathFunction}
	 * @param after {MathFunction}
	 */
	insertArgAfter: function(
		/* MathFunction */	f,
		/* MathFunction */	after)
	{
		var i = Y.Array.indexOf(this.args, after);
		if (i >= 0)
		{
			f.parent = this;
			this.args.splice(i+1, 0, f);
		}
		else
		{
			this.appendArg(f);
		}
	},

	/**
	 * @method appendArg
	 * @param f {MathFunction}
	 */
	appendArg: function(
		/* MathFunction */	f)
	{
		f.parent = this;
		this.args.push(f);
	},

	/**
	 * @method removeArg
	 * @param f {MathFunction}
	 */
	removeArg: function(
		/* MathFunction */	f)
	{
		var i = Y.Array.indexOf(this.args, f);
		if (i >= 0)
		{
			f.parent = null;
			this.args.splice(i,1);
		}
	},

	/**
	 * If origArg is an argument, replaces origArg with newArg.
	 * 
	 * @method replaceArg
	 * @param origArg {MathFunction} original argument
	 * @param newArg {MathFunction} new argument
	 */
	replaceArg: function(
		/* MathFunction */	origArg,
		/* MathFunction */	newArg)
	{
		var i = Y.Array.indexOf(this.args, origArg);
		if (i >= 0)
		{
			if (origArg.parent == this)		// might already have moved
				{
				origArg.parent = null;
				}
			newArg.parent = this;
			this.args[i]  = newArg;
		}
	},

	/**
	 * @method evaluateArgs
	 * @protected
	 * @param var_list {Object} map of variable names to values or MathFunctions
	 * @return list of argument values, from calling evaluate()
	 */
	evaluateArgs: function(
		/* map */	var_list)
	{
		return Y.Array.map(this.args, function(arg)
		{
			return arg.evaluate(var_list);
		});
	},

	/**
	 * @method layout
	 * @param context {object} the drawing context
	 * @param top_left {point} x,y coordinates of the top left of the bounding box
	 * @param font_size {float} percentage of the base font size
	 * @param rect_list {RectList} layout information
	 * @return {int} index of this items info in rect_list
	 */
	layout: function(
		/* Context2d */		context,
		/* point */			top_left,
		/* percentage */	font_size,
		/* RectList */		rect_list)
	{
		var r =
		{
			top:    top_left.y,
			left:   top_left.x,
			bottom: top_left.y + context.getLineHeight(font_size),
			right:  top_left.x + context.getStringWidth(font_size, this.name)
		};

		var midline = RectList.ycenter(r);

		// get rectangle for each argument

		var orig_midline = midline;

		var arg_top_left = { x: r.right, y: r.top };
		var sep_width    = context.getStringWidth(font_size, ', ');
		var arg_count    = this.args.length;

		var arg_i = [];
		for (var i=0; i<arg_count; i++)
		{
			var j     = this.args[i].layout(context, arg_top_left, font_size, rect_list);
			var info  = rect_list.get(j);
			var arg_r = info.rect;

			arg_top_left.x = arg_r.right + sep_width;
			r              = RectList.cover(r, arg_r);

			midline = Math.max(midline, info.midline);
			arg_i.push(j);
		}

		// adjust the argument rectangles so all the midlines are the same
		// (our midline is guaranteed to stay constant)

		if (arg_count > 1 && midline > orig_midline)
		{
			for (var i=0; i<arg_count; i++)
			{
				var j = arg_i[i];
				rect_list.setMidline(j, midline);
				r = RectList.cover(r, rect_list.get(j).rect);
			}
		}

		// Now that the midlines are the same, the height of our rectangle is
		// the height of the parentheses.  We have to shift all the arguments
		// to the right to make space for the left parenthesis.  By shifting
		// the rightmost one first, we avoid overlapping anything.

		var paren_w = context.getParenthesisWidth(r);

		for (var i=0; i<arg_count; i++)
		{
			rect_list.shift(arg_i[i], paren_w, 0);
		}

		// make space for 2 parentheses

		r.right += 2 * paren_w;

		return rect_list.add(r, midline, font_size, this);
	},

	/**
	 * @method render
	 * @param context {object} the drawing context
	 * @param rect_list {RectList} layout information
	 */
	render: function(
		/* Context2d */	context,
		/* RectList */	rect_list)
	{
		var info = rect_list.find(this);
		context.drawString(info.rect.left, info.midline, info.font_size, this.name);

		var r  =
		{
			top:    info.rect.top,
			bottom: info.rect.bottom
		};

		for (var i=0; i<this.args.length; i++)
		{
			this.args[i].render(context, rect_list);

			var info  = rect_list.find(this.args[i]);
			var arg_r = info.rect;
			if (i === 0)
			{
				r.left = arg_r.left;
			}

			if (i < this.args.length-1)
			{
				context.drawString(arg_r.right, info.midline, info.font_size, ',');
			}
			else
			{
				r.right = arg_r.right;
				context.drawParentheses(r);
			}
		}
	},

	/**
	 * @method toString
	 * @return text representation of the function
	 */
	toString: function()
	{
		return this.name + '(' + this.args.join(',') + ')';
	},

	/**
	 * Print an argument, with parentheses if necessary.
	 * 
	 * @method _printArg
	 * @protected
	 * @param index {number|MathFunction} argument index or MathFunction
	 * @return {string} the string representation of the argument
	 */
	_printArg: function(
		/* int */	index)
	{
		var arg = index instanceof MathFunction ? index : this.args[index];
		if (arg.parenthesizeForPrint(this))
		{
			return '(' + arg + ')';
		}
		else
		{
			return arg.toString();
		}
	}
});

MathFunction.FunctionWithArgs = MathFunctionWithArgs;