scripts/experiment-properties/graphing/d3-custom-plots/estimation_plot.js
export {create_estimation_plot,
create_estimation_interference_plot,
create_estimation_multi_interference_plot,
create_estimation_bisection_plot};
var exp;
/**
* Plots a regular estimation condition
*
* @param {object} experiment
* {object} attributes
*/
function create_estimation_plot(experiment, attributes) {
exp = experiment;
let chart = d3.select("#graph")
.append("svg")
.attr("width", attributes.chart.width)
.attr("height", attributes.chart.height)
.attr("style", "display: block");
let left = attributes.left_shape;
let right = attributes.right_shape;
plot_shape(left.shape, chart, left.size, left.y, left.x, left.is_ref, left.outline, left.fill, left.options);
plot_shape(right.shape, chart, right.size, right.y, right.x, right.is_ref, right.outline, right.fill, right.options);
if (experiment.condition_name === "absolute_area_ratio"){
plot_text(chart, "The target area ratio is " + attributes.chart.target_area_ratio + ".")
}
}
/**
* Plots a bisection estimation condition
* (e.g. conditions with "bisection" in their name)
*
* @param {object} experiment
* {object} attributes
*/
function create_estimation_bisection_plot(experiment, attributes) {
exp = experiment;
let chart = d3.select("#graph")
.append("svg")
.attr("width", attributes.chart.width)
.attr("height", attributes.chart.height)
.attr("style", "display: block");
let left = attributes.left_shape;
let right = attributes.right_shape;
let middle = attributes.middle_shape;
plot_shape(left.shape, chart, left.size, left.y, left.x, left.is_ref, left.outline, left.fill, left.options);
plot_shape(middle.shape, chart, middle.size, middle.y, middle.x, middle.is_ref, middle.outline, middle.fill, middle.options);
plot_shape(right.shape, chart, right.size, right.y, right.x, right.is_ref, right.outline, right.fill, right.options);
}
/**
* Plots an interference estimation condition
* (e.g. conditions with "interference" in their name but are not multi)
*
* @param {object} experiment
* {object} attributes
*/
function create_estimation_interference_plot(experiment, attributes) {
exp = experiment;
let chart = d3.select("#graph")
.append("svg")
.attr("width", attributes.chart.width)
.attr("height", attributes.chart.height)
.attr("style", "display: block");
let left = attributes.left_shape;
let right = attributes.right_shape;
let interf = attributes.interf_shape;
// Plot interference shape
if (interf) {
plot_shape(interf.shape, chart, interf.size, interf.y, interf.x, interf.is_ref, interf.outline, interf.fill, interf.options);
}
// Plot main shapes
plot_shape(left.shape, chart, left.size, left.y, left.x, left.is_ref, left.outline, left.fill, left.options);
plot_shape(right.shape, chart, right.size, right.y, right.x, right.is_ref, right.outline, right.fill, right.options);
}
/**
* Plots a multi interference estimation condition
* (e.g. conditions with "interference" and "multi" in their name)
*
* @param {object} experiment
* {object} attributes
*/
function create_estimation_multi_interference_plot(experiment, attributes) {
exp = experiment;
let chart = d3.select("#graph")
.append("svg")
.attr("width", attributes.chart.width)
.attr("height", attributes.chart.height)
.attr("style", "display: block");
let left = attributes.left_shape;
let right = attributes.right_shape;
let ref_sub = attributes.ref_sub_shape;
let mod = attributes.mod_shape;
// These conditions need main to go first as the sub+mod are on top
if (experiment.condition_name.split("_").includes("fan") ||
experiment.condition_name === "multi_square_cutout_interference" ||
experiment.condition_name === "multi_square_cutout_interference_flicker") {
// Plot main shapes
plot_shape(left.shape, chart, left.size, left.y, left.x, left.is_ref, left.outline, left.fill, left.options);
plot_shape(right.shape, chart, right.size, right.y, right.x, right.is_ref, right.outline, right.fill, right.options);
// Plot ref sub shape
plot_shape(ref_sub.shape, chart, ref_sub.size, ref_sub.y, ref_sub.x, ref_sub.is_ref, ref_sub.outline, ref_sub.fill, ref_sub.options);
// Plot mod shape
plot_shape(mod.shape, chart, mod.size, mod.y, mod.x, mod.is_ref, mod.outline, mod.fill, mod.options);
} else {
// Plot ref sub shape
plot_shape(ref_sub.shape, chart, ref_sub.size, ref_sub.y, ref_sub.x, ref_sub.is_ref, ref_sub.outline, ref_sub.fill, ref_sub.options);
// Plot mod shape
plot_shape(mod.shape, chart, mod.size, mod.y, mod.x, mod.is_ref, mod.outline, mod.fill, mod.options);
// Plot main shapes
plot_shape(left.shape, chart, left.size, left.y, left.x, left.is_ref, left.outline, left.fill, left.options);
plot_shape(right.shape, chart, right.size, right.y, right.x, right.is_ref, right.outline, right.fill, right.options);
}
}
/**
* Routes to correct plotting code depending on shape type.
*
* @param shape {string}
* @param chart {object}
* @param length {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean} if the shape is a reference shape or a modifiable shape
* @param outline {string} outline color
* @param fill {string} fill color
* @param options {string} can be: {"scaling": "scales_with_mod" This shape serves as bg to the modifiable shape
OR
"scales_indep" This shape is a bg shape, but is the one being modified
},
{"fan-attributes": {"slice-alignment" : "top" or "bottom", Position of slice in pie
"angle-size" : some_value, Angle in degrees of the fan
},
{"flicker": {"on": on_duration, The duration in ms for the shape to appear
"off": off_duration}} The duration in ms for the shape to disappear
*/
function plot_shape(shape, chart, length, y_pos, x_pos, is_ref, outline, fill, options) {
switch (shape) {
case "circle":
plot_circle(chart, length, y_pos, x_pos, is_ref, outline, fill, options);
break;
case "fan":
// When mod_side_alignment = slice-bottom, reference circle must have a sliced out
// section equivalent to modifiable fan. Here we just plot another fan over the
// reference circle.
if (options["fan-attributes"]["ref_shape_adjusts"]) {
plot_fan(chart, length, y_pos-length/4, x_pos, is_ref, fill, fill, options);
}
plot_fan(chart, length, y_pos, x_pos, is_ref, outline, fill, options);
break;
case "triangle":
plot_triangle(chart, length, y_pos, x_pos, is_ref, outline, fill, options);
break;
case "square":
plot_square(chart, length, y_pos, x_pos, is_ref, outline, fill, options);
break;
case "line":
plot_line(chart, length, y_pos, x_pos, is_ref, outline, options);
break;
case "rectangle":
plot_rectangle(chart, length, y_pos, x_pos, is_ref, outline, fill, options);
break;
default:
throw Error(shape + " shape is not supported.");
}
}
/**
* Plots a circle.
*
* @param chart {object}
* @param diameter {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean} if the shape is a reference shape or a modifiable shape
* @param outline {string}
* @param fill {string}
* @param options {string}
*/
function plot_circle(chart, diameter, y_pos, x_pos, is_ref, outline, fill, options) {
let radius = diameter / 2;
let shape = chart.append("circle")
.attr("cx", x_pos)
.attr("cy", y_pos)
.attr("r", diameter / 2)
.attr("id", function(){
if (options && options.scaling && options.scaling === "scales_with_mod") {
exp.interf_shape_variables = {x_pos: x_pos, y_pos: y_pos, diameter: diameter};
return "bound_circle_shape"
}
return "circle_shape"}
)
.attr("is_ref", is_ref)
.attr("fill", fill)
.attr("stroke", outline);
if (is_ref === false) {
d3.select("body")
.on("keydown", function () {
let event = d3.event;
// console.log(event);
if (event.key === "m" || event.key === "z") {
diameter = calculate_size_change(event.key, diameter, "circle");
radius = diameter / 2;
d3.select("#circle_shape")
.attr("r", radius);
}
});
}
if (options && options.flicker) {
shape.call(flicker_shape, fill, outline, options.flicker.on, options.flicker.off);
}
}
/**
* Plots a fan.
*
* @param chart {object}
* @param width {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean} if the shape is a reference shape or a modifiable shape,
* is_ref === true if the shape is a reference shape
* @param outline {string}
* @param fill {string}
* @param options {object}
*/
function plot_fan(chart, diameter, y_pos, x_pos, is_ref, outline, fill, options) {
if (!options["fan-attributes"]) {throw Error("Fan attributes are needed in options to plot a fan.")};
let radius = diameter/2;
let angle_size = options["fan-attributes"]["angle_size"];
let arc = d3.arc()
.innerRadius(0)
.outerRadius(radius)
.startAngle(0 * Math.PI/180)
.endAngle(function(){
return angle_size * Math.PI/180;
});
let shape = chart.append("g")
.attr("transform", function() {
return "translate(" + x_pos + "," + y_pos + ")";
})
.append("path")
.attr("id", function(){
if (is_ref) {
return "fan_shape_ref";
} else {
return "fan_shape_mod";
}
})
.attr("d", arc)
.attr("fill", fill)
.attr("stroke", outline)
.attr("stroke-width", 1)
.attr("transform", "rotate(" + compute_angle_shift(angle_size, options["fan-attributes"]["slice-alignment"]) + ")");
if (is_ref === false) {
d3.select("body")
.on("keydown", function () {
let event = d3.event;
if (event.key === "m" || event.key === "z") {
// Estimated size = angle units
angle_size = calculate_angle_change(event.key, angle_size, radius);
let changed_arc = d3.arc()
.innerRadius(0)
.outerRadius(radius)
.startAngle(0 * Math.PI/180)
.endAngle(function(){
return angle_size * Math.PI/180;
});
d3.selectAll("#fan_shape_mod")
.attr("d", changed_arc)
.attr("transform", "rotate(" + compute_angle_shift(angle_size, options["fan-attributes"]["slice-alignment"]) + ")");
}
});
}
if (options && options.flicker) {
shape.call(flicker_shape, fill, outline, options.flicker.on, options.flicker.off);
}
}
/**
* Returns the amount of rotational shift in degrees to align the angle
* at the desired alignment.
*
* @param {double} angle - size of angle in degrees
* {string} alignment - e.g. bottom or top of circle
*/
function compute_angle_shift(angle, alignment) {
let shift = (-1)*angle/2; // Shifts it so slice is perfectly centered and at top of pie
if (alignment === "bottom") {
shift += 180;
} else if (alignment !== "top") {
throw Error(alignment + " alignment not supported in computing angle shift for fan shapes.");
}
return shift;
}
/**
* On conditions square,circle interference and circle interference, will trigger
* the underlying bound bg shape to scale with the mod shape
*
* @param {double} size_change
*/
function adjust_interference_shape(size_change) {
let subcond = exp.curr_conditions_constants[exp.curr_condition_index];
let ratio = subcond.interf_ratio;
let y_translation;
let x_translation;
if (subcond.interf_shape) {
switch (subcond.interf_shape) {
case "circle" :
d3.select("#bound_circle_shape")
.attr("r", size_change * ratio)
break;
case "square" :
x_translation = exp.interf_shape_variables.x_pos - ((size_change * ratio) / 2);
y_translation = exp.interf_shape_variables.y_pos - ((size_change * ratio) / 2);
d3.select("#bound_square_shape")
.attr("width", (size_change * ratio))
.attr("height", (size_change * ratio))
.attr("x", x_translation)
.attr("y", y_translation);
break;
}
}
}
/**
* Plots a square.
*
* @param chart {object}
* @param width {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean} if the shape is a reference shape or a modifiable shape,
* is_ref === true if the shape is a reference shape
* @param outline {string}
* @param fill {string}
* @param options {object}
*/
function plot_square(chart, width, y_pos, x_pos, is_ref, outline, fill, options) {
let subcond = exp.curr_conditions_constants[exp.curr_condition_index];
let shape;
if (options && options.cutout_radius) {
let cutout = options.cutout_radius;
// Need to shift by half length to match centering done by regular rect svgs
x_pos -= width/2;
y_pos -= width/2;
let poly = [{"x":(x_pos+cutout), "y":(y_pos)}, //bottom left of cutout
{"x":(x_pos+cutout), "y":(y_pos+cutout)}, //bottom right of cutout
{"x":(x_pos), "y":(y_pos+cutout)}, //top right of cutout
{"x":(x_pos), "y":(y_pos+width)}, //bottom left corner
{"x":(x_pos + width), "y":(y_pos + width)}, //bottom right corner
{"x":(x_pos + width), "y":(y_pos)}, //top right corner
];
shape = chart.append("polygon")
.attr("points",function() {
return poly.map(function(d) { return [d.x, d.y].join(","); }).join(" ");})
.attr("fill", fill)
.attr("stroke", outline)
.attr("id", is_ref? "square_cutout_shape_ref" : "square_cutout_shape_mod");
} else {
shape = chart.append("rect")
.attr("id", function(){
if (options && options.scaling && options.scaling === "scales_with_mod") {
exp.interf_shape_variables = {x_pos: x_pos, y_pos: y_pos, width: width};
return "bound_square_shape";
}
if (is_ref) {
return "square_shape_ref";
} else {
return "square_shape_mod"; // if is an interf and is not ref, becomes a mod
}
})
.attr("x", x_pos - width / 2)
.attr("y", y_pos - width / 2) // the x and y core_attributes for square
// refers to the position of the upper left corner
// however x_pos and y_pos specifies the center of the shape
.attr("width", width)
.attr("height", width)
.attr("fill", fill)
.attr("stroke", outline);
if (is_ref === true && exp.curr_trial_data.ref_rotate_by) {
let transform = "rotate(";
transform = transform + exp.curr_trial_data.ref_rotate_by.toString();
transform = transform + " " + (x_pos - width).toString();
transform = transform + " " + (y_pos - width).toString();
transform = transform + ")";
d3.select("#square_shape_ref").attr("transform", transform);
}
if (is_ref === false) {
d3.select("body")
.on("keydown", () => {
let event = d3.event;
if (event.key === "m" || event.key === "z") {
width = calculate_size_change(event.key, width, "square");
d3.select("#square_shape_mod")
.attr("width", width)
.attr("height", width);
// Need to adjust translation for when adjustable
// interf is overlapping.
if (options && options.scaling && options.scaling === "scales_indep"){
let x_translation;
let y_translation;
// If interf is larger than ref
if (subcond.mod_ratio > 1) {
x_translation = x_pos - ((width * subcond.mod_ratio) / 4);
y_translation = y_pos - ((width * subcond.mod_ratio) / 4);
} else { // If interf is smaller than ref (so behind + center of ref)
x_translation = x_pos - ((width * subcond.mod_ratio));
y_translation = y_pos - ((width * subcond.mod_ratio));
}
d3.select("#square_shape_mod")
.attr("x", x_translation)
.attr("y", y_translation);
}
}
});
}
}
if (options && options.flicker) {
shape.call(flicker_shape, fill, outline, options.flicker.on, options.flicker.off);
}
}
/**
* Plots a triangle.
*
* @param chart {object}
* @param radius {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean} if the shape is a reference shape or a modifiable shape,
* is_ref === true if the shape is a reference shape
* @param outline {string}
* @param fill {string}
* @param options {object}
*/
function plot_triangle(chart, radius, y_pos, x_pos, is_ref, outline, fill, options) {
// for equilateral triangles, height = side * sqrt(3) / 2;
let short_side = radius;
let long_side = radius;
let height = 0, width = 0;
let poly = [];
if (!is_ref) {
if (exp.curr_trial_data.width_height_ratio) {
long_side = short_side * exp.curr_trial_data.width_height_ratio;
height = Math.sqrt(Math.pow(long_side, 2) - Math.pow(short_side / 2, 2));
width = short_side;
if (exp.curr_trial_data.mod_rotate_by) {
poly = [
{"x":(0.5 * height + x_pos), "y":(y_pos)},
{"x":(-0.5 * height + x_pos), "y":(-0.5 * width + y_pos)},
{"x":(-0.5 * height + x_pos), "y":(0.5 * width + y_pos)}];
} else {
poly = [
{"x":(x_pos), "y":(-0.5 * height + y_pos)},
{"x":(-0.5 * width + x_pos), "y":(0.5 * height + y_pos)},
{"x":(0.5 * width + x_pos), "y":(0.5 * height + y_pos)}];
}
} else {
height = radius * Math.sqrt(3)/2;
poly = [
{"x":x_pos, "y":(-0.5 * height + y_pos)},
{"x":(-0.5 * radius + x_pos), "y":(0.5 * height + y_pos)},
{"x":(0.5 * radius + x_pos), "y":(0.5 * height + y_pos)}];
}
} else {
height = radius * Math.sqrt(3)/2;
poly = [
{"x":x_pos, "y":(-0.5 * height + y_pos)},
{"x":(-0.5 * radius + x_pos), "y":(0.5 * height + y_pos)},
{"x":(0.5 * radius + x_pos), "y":(0.5 * height + y_pos)}];
}
let shape = chart.append("polygon")
.attr("points",function() {
return poly.map(function(d) { return [d.x, d.y].join(","); }).join(" ");})
.attr("fill", fill)
.attr("stroke", outline)
.attr("id", is_ref? "triangle_shape_ref" : "triangle_shape_mod");
if (is_ref === false) {
d3.select("body")
.on("keydown", function () {
let event = d3.event;
if (event.key === "m" || event.key === "z") {
// decide the amount of change;
radius = calculate_size_change(event.key, radius, "triangle");
// plot the changed shape
short_side = radius;
if (exp.curr_trial_data.width_height_ratio) {
long_side = short_side * exp.curr_trial_data.width_height_ratio;
height = Math.sqrt(Math.pow(long_side, 2) - Math.pow(short_side / 2, 2));
width = short_side;
if (exp.curr_trial_data.mod_rotate_by) {
poly = [
{"x":(0.5 * height + x_pos), "y":(y_pos)},
{"x":(-0.5 * height + x_pos), "y":(-0.5 * width + y_pos)},
{"x":(-0.5 * height + x_pos), "y":(0.5 * width + y_pos)}];
} else {
poly = [
{"x":(x_pos), "y":(-0.5 * height + y_pos)},
{"x":(-0.5 * width + x_pos), "y":(0.5 * height + y_pos)},
{"x":(0.5 * width + x_pos), "y":(0.5 * height + y_pos)}];
}
} else {
height = short_side * Math.sqrt(3)/2;
poly = [
{"x":x_pos, "y":(-0.5 * height + y_pos)},
{"x":(-0.5 * short_side + x_pos), "y":(0.5 * height + y_pos)},
{"x":(0.5 * short_side + x_pos), "y":(0.5 * height + y_pos)}];
}
chart.select("#triangle_shape_mod")
.attr("points",function() {
return poly.map(function(d) { return [d.x, d.y].join(","); }).join(" ");});
adjust_interference_shape(radius);
}
});
}
if (options && options.flicker) {
shape.call(flicker_shape, fill, outline, options.flicker.on, options.flicker.off);
}
}
/**
* Plots a rectangle.
*
* @param chart {object}
* @param size {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean}
* @param outline {string}
* @param fill {string}
* @param options {object}
*/
function plot_rectangle(chart, size, y_pos, x_pos, is_ref, outline, fill, options) {
let short_side = size;
let long_side = size;
let height = 0, width = 0;
if (exp.curr_trial_data.width_height_ratio) {
long_side = short_side * exp.curr_trial_data.width_height_ratio;
}
width = short_side;
height = long_side;
let shape = chart.append("rect")
.attr("id", is_ref? "rect_shape_ref": "rect_shape_mod")
.attr("x", x_pos - width / 2)
.attr("y", y_pos - height / 2) // the x and y core_attributes for square
// refers to the position of the upper left corner
// however x_pos and y_pos specifies the center of the shape
.attr("width", width)
.attr("height", height)
.attr("fill", fill)
.attr("stroke", outline);
if (is_ref === false && exp.curr_trial_data.mod_rotate_by) {
let transform = "rotate(";
transform = transform + exp.curr_trial_data.mod_rotate_by.toString();
transform = transform + " " + (x_pos).toString();
transform = transform + " " + (y_pos).toString();
transform = transform + ")";
console.log(transform);
d3.select("#rect_shape_mod").attr("transform", transform);
}
if (is_ref === false) {
d3.select("body")
.on("keydown", function () {
let event = d3.event;
if (event.key === "m" || event.key === "z") {
size = calculate_size_change(event.key, size, "rectangle");
let short_side = size;
let long_side = exp.curr_trial_data.width_height_ratio * short_side;
let new_width = 0, new_height = 0;
new_width = short_side;
new_height = long_side;
d3.select("#rect_shape_mod")
.attr("width", new_width)
.attr("height", new_height);
}
});
}
if (options && options.flicker) {
shape.call(flicker_shape, fill, outline, options.flicker.on, options.flicker.off);
}
}
/**
* Plots a line.
*
* @param chart {object}
* @param width {number}
* @param y_pos {number}
* @param x_pos {number}
* @param is_ref {boolean}
* @param outline
* @param options {object}
*/
function plot_line(chart, width, y_pos, x_pos, is_ref, outline, options) {
let x1, x2, y1, y2;
if (!is_ref) {
x1 = x_pos - (width / 2) * Math.sin(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
x2 = x_pos + (width / 2) * Math.sin(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
y1 = y_pos - (width / 2) * Math.cos(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
y2 = y_pos + (width / 2) * Math.cos(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
} else {
x1 = x_pos - width / 2;
x2 = x_pos + width / 2;
y1 = y_pos;
y2 = y_pos;
}
let shape = chart.append("line")
.style("stroke", outline)
.style("stroke-width", exp.curr_trial_data.stroke_width)
.attr("id", is_ref? "line_shape_ref": "line_shape_mod")
.attr("x1", x1)
.attr("x2", x2)
.attr("y1", y1)
.attr("y2", y2);
if (is_ref === false) {
d3.select("body")
.on("keydown", function () {
let event = d3.event;
if (event.key === "m" || event.key === "z") {
width = calculate_size_change(event.key, width, "line");
if (!is_ref) {
x1 = x_pos - (width / 2) * Math.sin(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
x2 = x_pos + (width / 2) * Math.sin(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
y1 = y_pos - (width / 2) * Math.cos(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
y2 = y_pos + (width / 2) * Math.cos(exp.curr_trial_data.mod_rotate_by * Math.PI / 180);
} else {
x1 = x_pos - width / 2;
x2 = x_pos + width / 2;
y1 = y_pos;
y2 = y_pos;
}
d3.select("#line_shape_mod")
.attr("x1", x1)
.attr("x2", x2)
.attr("y1", y1)
.attr("y2", y2);
}
});
}
if (options && options.flicker) {
shape.call(flicker_shape, "none", outline, options.flicker.on, options.flicker.off);
}
}
/**
* Plots text centered at the bottom of the page.
*
* @param chart {object}
* text {string}
*/
function plot_text(chart, text){
chart.append("text")
.attr("x", "50%")
.attr("y", "95%")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", "28px")
.attr("fill", "black")
.text(()=>{
return text;
});
}
/**
* Flickers on and off the D3 selection.
*
* @param {object} selection
* {string} fill
* {string} stroke
* {double} on_duration - duration selection displays for in ms
* {double} off_duration - duration selection becomes invisible for in ms
*/
function flicker_shape(selection, fill, stroke, on_duration, off_duration) {
setInterval(display_off, on_duration);
setInterval(display_on, on_duration + off_duration);
function display_off() {
selection.attr("fill", "none")
.attr("stroke", "WHITE");
}
function display_on() {
selection.attr("fill", fill)
.attr("stroke", stroke);
}
}
/**
* Calculates the next size and saves the data.
*
* @param {object} event_key m to increase the size and z to decrease the size
* @param {double} size the previous size of the shape
* @param {string} the type of shape
*
* @returns {number} the new size in pixels
*/
function calculate_size_change(event_key, size, shape_type) {
let sign = event_key === "m" ? 1 : -1;
let change = Math.random() * exp.PIXEL_TO_CM * exp.MAX_STEP_SIZE;
let new_radius = size + sign * change;
let size_in_px = new_radius;
let size_in_cm = new_radius / exp.PIXEL_TO_CM;
exp.save_adjustment(change * sign / exp.PIXEL_TO_CM);
exp.save_estimated_size(size_in_cm, "cm");
let area = exp.compute_shape_area(shape_type, size_in_px);
exp.save_estimated_area(area);
return size_in_px;
}
/**
* Calculates the next angle and saves the data.
*
* @param {object} event_key m to increase the size and z to decrease the size
* @param {double} previous angle
* @param {double} radius of the fan
*
* @returns {number} the new angle in degrees
*/
function calculate_angle_change(event_key, angle, radius) {
let current_constants = exp.curr_conditions_constants[exp.curr_condition_index];
let max_step_size = current_constants.max_step_size;
let sign = event_key === "m" ? 1 : -1;
let change = Math.random() * max_step_size;
let diff_angle = sign * change;
let new_angle = angle + diff_angle;
exp.save_adjustment(diff_angle);
exp.save_estimated_size(new_angle, "angle");
let area = exp.compute_fan_area(new_angle, radius);
exp.save_estimated_area(area);
return new_angle;
}