shooflenet/articles/circle_script/circle_setter.js

290 lines
10 KiB
JavaScript
Raw Normal View History

var currently_dragging = null; // if this is null, we have nothing being dragged. if it's not null, then it will represent the glyph object (as goes in the glyphs array) which is currently being dragged
var major_radius;
const glyphs = [];
2023-09-12 23:00:04 -04:00
const vowels = [];
const consonants = [];
2024-09-10 13:19:40 -04:00
var svg;
var connections_container;
$(document).ready(function() {
svg = $('svg#arena')[0];
2024-09-10 13:19:40 -04:00
connections_container = $('svg#arena #connections');
major_radius = parseFloat($('circle').attr('r'));
$(document).on("mouseup", on_mouse_up);
2023-09-12 23:00:04 -04:00
$('td > svg').on("mousedown", function(event) {
let receptacle = $("#consonant_put_it_in_here");
let selected = $(this);
receptacle.html(selected.html());
2023-09-12 23:00:04 -04:00
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
2024-09-10 13:19:40 -04:00
var g = make_new_consonant();
set_location(g, mouse_position);
2023-09-12 23:00:04 -04:00
currently_dragging = g;
})
$('.vowel-example > svg').on("mousedown", function(event) {
let receptacle = $('#vowel_put_it_in_here');
let selected = $(this);
receptacle.html(selected.html());
2023-09-12 23:00:04 -04:00
2024-09-10 13:19:40 -04:00
var g = make_new_vowel();
2023-09-12 23:00:04 -04:00
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
2024-09-10 13:19:40 -04:00
set_location(g, mouse_position);
2023-09-12 23:00:04 -04:00
currently_dragging = g;
});
2023-09-12 23:00:04 -04:00
$('td > svg').each(function() { this.setAttribute('viewBox',"0 30 40 100");});
populate_consonant_grid();
2023-09-12 23:00:04 -04:00
populate_vowels();
});
function populate_consonant_grid() {
// this function expects that there's a #consonant-grid table, where each row corresponds to a glyph set
// the cells of the row need to be filled with the various possible configurations of that glyph
// so like, the <th> in a given row should contain a .megaglyph svg group, which contains the base glyph plus all possible radicals.
// then we use the split_megaglyph function to generate the five possible configurations out of that megaglyph
// and insert the five configured glyphs into their corresponding (td) cells of the table
$("#consonant-grid tbody tr").each(function (asdf, row) { // iterate over the rows in the table
// row will now be something like <tr><th/><td/><td/><td/><td/><td/><th/></tr>
// that is, a tr containing one th on either end plus a td for each consonant in this row
var head = $(row).find("th:first-child svg"); // this is the header for the row, which should contain the .megaglyph object
var megaglyph = head.find(".megaglyph");
var glyphs = split_megaglyph(megaglyph); // now this is a list of the configured consonant symbols
$(row).find("td").each(function(idx, cell) { // iterate over the cells in the row
// cell will now be a <td> cell which contains the IPA for that cell's consonants, plus the <svg> canvas into which to insert the configured consonant symbol
$(cell).find("svg").append(glyphs[idx]);
});
});
}
function split_megaglyph(megaglyph) {
// megaglyph is an svg object which contains all the possible radicals at once.
// this should return a list of new glyphs, cloned frmo megaglyph, each of which contains only the radicals it wants.
var one = megaglyph.clone();
one.find('.line_1,.line_2,.line_3,.dot_2,.dot_3').remove(); // only .dot_1
remove_class(one, "megaglyph");
var two = megaglyph.clone();
two.find('.line_2,.line_3,.dot_1,.dot_2,.dot_3').remove(); // line 1 only
remove_class(two, "megaglyph");
var three = megaglyph.clone();
three.find('.line_1,.line_2,.line_3,.dot_1').remove(); // only dots 2 and 3
remove_class(three, "megaglyph");
var four = megaglyph.clone();
four.find('.line_1,.dot_1,.dot_2,.dot_3').remove(); //lines 2 and 3
remove_class(four, "megaglyph");
var five = megaglyph.clone();
five.find('.line_1,.line_2,.line_3').remove(); // all three dots
remove_class(five, "megaglyph");
return [one, two, three, four, five];
}
2023-09-12 23:00:04 -04:00
function populate_vowels() {
megaglyph = $(".vowel.megaglyph");
$(".vowel-example svg").each((i, ex) => {
let glyphs = $(ex).text().replace(/(\s|\n)+/g,",").split(",").filter((a) => a!="")
$(ex).text("");
$.each(glyphs, (i, e) => {
let wanted_class = "." + e;
let glyph_original = megaglyph.find(wanted_class);
let new_glyph = glyph_original.clone();
$(ex).append(new_glyph);
});
});
}
2023-09-12 23:00:04 -04:00
function make_new_consonant(source) {
// create a new glyph object by copying the elements pointed to by source
// this will add it to the svg and to the glyphs list
var x = {};
x.angle = 0;
2024-09-10 13:19:40 -04:00
x.element = $("#consonant_copy_from_here").clone().attr("id","").attr("onclick", "").attr("onmousedown", "");
2023-09-12 23:00:04 -04:00
x.element.find("#consonant_put_it_in_here").attr("id","");
x.position = {x:0, y:0};
2024-09-10 13:19:40 -04:00
x.element.on("mousedown", x, (event) => { currently_dragging = event.data });
2023-09-12 23:00:04 -04:00
$(svg).append(x.element);
2023-09-12 23:00:04 -04:00
x.handles = [];
2024-09-10 13:19:40 -04:00
x.handles.push($(ns_elem("circle", svg_ns)))
x.handles.push($(ns_elem("circle", svg_ns)))
$.each(x.handles, function() {
$(svg).append(this)
this.attr("r", 2);
add_class(this, "handle");
});
2023-09-12 23:00:04 -04:00
update_handles(x);
glyphs.push(x);
2024-09-10 13:19:40 -04:00
x.vowel = false;
2023-09-12 23:00:04 -04:00
return x;
}
2024-09-10 13:19:40 -04:00
function make_new_vowel(source) {
// create a new glyph object by copying the elements pointed to by source
// this will add it to the svg and to the glyphs list
var x = {};
x.angle = 0;
2024-09-10 13:19:40 -04:00
x.element = $("#vowel_copy_from_here").clone().attr("id","").attr("onclick", "").attr("onmousedown", "");
2023-09-12 23:00:04 -04:00
x.element.find("#vowel_put_it_in_here").attr("id","");
x.position = {x:0, y:0};
2024-09-10 13:19:40 -04:00
x.element.on("mousedown", x, (event) => { currently_dragging = event.data });
$(svg).append(x.element);
2023-09-12 23:00:04 -04:00
x.handles = [];
2024-09-10 13:19:40 -04:00
x.handles.push($(ns_elem("circle", svg_ns)));
x.handles.push($(ns_elem("circle", svg_ns)));
$.each(x.handles, function() {
$(svg).append(this)
this.attr("r", 2);
add_class(this, "handle");
});
2023-09-12 23:00:04 -04:00
update_handles(x);
glyphs.push(x);
2024-09-10 13:19:40 -04:00
x.vowel = true;
return x;
}
function on_mouse_up(event) {
// this is called on the whole document when the mouse is released on it. event.data is meaningless. this should handle releasing the current dragged element, if it exists.
if (currently_dragging != null && is_in_delete_region(currently_dragging.position)) {
remove(glyphs, currently_dragging);
2023-09-12 23:00:04 -04:00
$.each(currently_dragging.handles, function() { $(this).remove(); });
currently_dragging.element.remove();
currently_dragging = null;
2024-09-10 13:19:40 -04:00
connect_vowels();
}
if (currently_dragging != null) {
2024-09-10 13:19:40 -04:00
set_location(currently_dragging, mul(norm(currently_dragging.position), major_radius));
}
currently_dragging = null;
}
function is_in_delete_region(p) {
r = $("#delete");
first = {x:parseFloat(r.attr("x")), y:parseFloat(r.attr("y"))};
second = {x:parseFloat(r.attr("width"))+first.x, y:parseFloat(r.attr("height"))+first.y};
if (p.x > first.x && p.x < second.x && p.y > first.y && p.y < second.y) {
return true;
}
return false;
}
$(document).mousemove(function(event) {
const mouse_position = svg_from_dom({x:event.clientX, y:event.clientY}, svg);
const mouse_move = sub(mouse_position, previous_mouse_position);
previous_mouse_position = mouse_position;
var s_position = mouse_position;
// get the position inside the svg tag for the mouse
if (currently_dragging != null) {
2024-09-10 13:19:40 -04:00
set_location(currently_dragging, add(currently_dragging.position, mouse_move));
2023-09-12 23:00:04 -04:00
update_handles(currently_dragging);
}
2023-09-12 23:00:04 -04:00
});
function update_handles(g) {
2024-09-10 13:19:40 -04:00
if (g.element.find('path.glyph,path.vowel-base').length != 0) {
set_loc(g.handles[0], get_global_point_at_length(svg, g.element.find('path.glyph,path.vowel-base')[0], 0));
set_loc(g.handles[1], get_global_point_at_length(svg, g.element.find('path.glyph,path.vowel-base')[0], 1000));
}
connect_vowels();
}
function set_location(glyph, point) {
glyph.position = point;
glyph.angle = Math.atan2(point.y, point.x);
set_transform(glyph.element, glyph.position, appropriate_angle(point));
update_handles(glyph);
}
function connect_vowels() {
let vowels = $.grep(glyphs, (v) => v.vowel);
vowels.sort((a, b) => b.angle - a.angle);
console.log($.map(vowels, (a)=>a.angle));
// clear oout the old connections
connections_container.html("");
if (vowels.length == 0) {
// if there's no vowels, we should have just a bare circle
/*} else if (vowels.length == 1) {
// make a fake point on the other side to connect
let connection = ns_elem("path", svg_ns);
add_class(connection, "intervowel");
let path = `M ${start_x} ${start_y} `;
path += `A ${}`
$(connection).attr("d", `M ${start_x} ${start_y} A ${major_radius} ${major_radius} 0 ${sweep} ${large} ${end_x} ${end_y}`);
connections_container.append(connection);*/
} else {
// otherwise iterate over them and connect one to the next
for (let i=0; i < vowels.length; i++) {
//connections_container.append($(ns_elem("circle", svg_ns)).attr("r",1).attr("cx",Math.cos(vowels[i].angle)*major_radius).attr("cy",major_radius*Math.sin(vowels[i].angle)));
let connection = ns_elem("path", svg_ns);
add_class(connection, "intervowel");
let first = vowels[i];
let second = vowels[(i+1)%vowels.length];
let start_x = parseFloat(first.handles[1].attr("cx"));
let start_y = parseFloat(first.handles[1].attr("cy"));
/* abandoned quadratic approach
let roundness = 0.2*Math.abs(first.angle - second.angle);
roundness = first.angle > second.angle ? 1 : 0;
let s_dx = start_x + (Math.sin(first.angle) * major_radius * roundness);
let s_dy = start_y - (Math.cos(first.angle) * major_radius * roundness);
*/
let end_x = parseFloat(second.handles[0].attr("cx"));
let end_y = parseFloat(second.handles[0].attr("cy"));
/*
let e_dx = end_x - (Math.sin(second.angle) * major_radius * roundness);
let e_dy = end_y + (Math.cos(second.angle) * major_radius * roundness);
// $(connection).attr("d", `M ${start_x} ${start_y} C ${s_dx} ${s_dy} ${e_dx} ${e_dy} ${end_x} ${end_y}`);
*/
let distance = -(second.angle - first.angle);
if (vowels.length == 1) {
distance = 1.5*Math.PI;
}
let midpoint;
let sweep = "0";
let large = "0";
if (distance < 0) {
distance += 2*Math.PI;
}
if (distance < Math.PI) {
sweep = "0"
large = "0"
} else if (distance < 2*Math.PI) {
sweep = "1"
large = "0"
}
$(connection).attr("d", `M ${start_x} ${start_y} A ${major_radius} ${major_radius} 0 ${sweep} ${large} ${end_x} ${end_y}`);
connections_container.append(connection);
}
}
2023-09-12 23:00:04 -04:00
}