diff --git a/connect_points/Images/c_L.png b/connect_points/Images/c_L.png deleted file mode 100644 index bfa0bf5..0000000 Binary files a/connect_points/Images/c_L.png and /dev/null differ diff --git a/connect_points/Images/formulas.png b/connect_points/Images/formulas.png index 9eaddf7..68e3271 100644 Binary files a/connect_points/Images/formulas.png and b/connect_points/Images/formulas.png differ diff --git a/connect_points/index.html b/connect_points/index.html index c0ae32a..a34b78d 100644 --- a/connect_points/index.html +++ b/connect_points/index.html @@ -1,104 +1,136 @@ + - - - - - - - -
-

- Grid connector -

- - -
- -
-
-

- -
- - Grid size: 1 - - -
- - - -
- - - -

-
- -
-

- d(G): - 0 -
- - c(G): - 0 -
-

-
- -
-

- Vertices connected: - 0 - / - 0 -
- - Total connections: - 0 -

-
-
- -
-

- Controls -

-

- - Hold left mouse button: connect points with an edge. -
- - Hold right mouse button: disconnect points by removing edge between them (if there is one). -

-
- -
-

- Motivation -

-

- This page was made to aid solving graph connection related problems. Here are some of those problems. -

-

- Problem 1: Given Hamiltonian cycle whose edges do not self-intersect, find such cycle that maximises d(G). -
- Problem 2: Given Hamiltonian cycle whose edges do not self-intersect, find such cycle that minimises c(G). -
- Problem formulas -

-
- -

- Insights -

- -

- It was found that for grid sizes of nxn, where n < 6, If G maximises d(G), it also minimises c(G). It could be that this is true for all n. -

- -

-
-
- Problems author: Giedrius Alkauskas -

- + + + + + + + + +
+

+ Grid connector +

+ +
+
+ + + Grid size: 1 +
+ + + + Line width: 1 +
+ + + + Circle spacing: 1 +
+ + + + Circle radius: 1 +
+ + + Mark intersecting edges +
+ + + Mark connected vertices +
+ + + Auto connect circles +
+ + +
+ +
+ d(G): + 0 +
+
+ +
+ Vertices connected: + 0 + / + 0 +
+ + Total connections: + 0 +
+
+
+ +
+ +
+ +
+
+

+ Controls +

+

+ - Hold left mouse button: connect circles with an edge. +
+ - Hold right mouse button: disconnect circles by removing edge between them (if there is one). +

+
+ +
+

+ Motivation +

+

+ This page was made to aid solving graph connection related problems. +

+

+ Problem 1: Given Hamiltonian cycle whose edges do not self-intersect, find such cycle that maximises + d(G). +
+ Problem formulas +

+
+ +

+
+
+ Problems author: Giedrius Alkauskas +

+
+ + \ No newline at end of file diff --git a/connect_points/index.js b/connect_points/index.js index b1f1b32..ca37788 100644 --- a/connect_points/index.js +++ b/connect_points/index.js @@ -1,16 +1,30 @@ let canvas; let canvas_ctx; -let circle_radius = 16; -let circle_grid_size = 4; -let min_circle_grid_size = 1; -let max_circle_grid_size = 8; +let grid_size = 4; +let min_grid_size = 1; +let max_grid_size = 50; + +let circle_line_width_increment = 2; +let line_width = 6; +let min_line_width = 2; +let max_line_width = 20; + +let circle_spacing_increment = 5; +let circle_spacing = 80; +let min_circle_spacing = 20; +let max_circle_spacing = 100; + +let circle_radius_increment = 5; +let circle_radius = 15; +let min_circle_radius = 5; +let max_circle_radius = 50; + +// distance between circle edge and canvas edge +let canvas_padding = 20; + let circle_grid_start_pos_x = 56; let circle_grid_start_pos_y = 56; -let circle_grid_spacing = 84; - -let start_drag_pos_x = 0; -let start_drag_pos_y = 0; // Last circle row/column where mouse was pressed on let last_grid_circle_x = -1; @@ -40,34 +54,38 @@ const colour_green = "#35c13d"; const colour_red = "#ff0000"; const colour_dark_red = "#a10000"; -window.onload = function(){ - canvas = document.getElementById('main_canvas'); - canvas_ctx = canvas.getContext('2d'); - - canvas.addEventListener("mousedown", mouse_down); - canvas.addEventListener("mouseup", mouse_up); - canvas.addEventListener("mousemove", mouse_move); - - window.addEventListener('contextmenu', (event) => { - event.preventDefault() - }) - - set_grid_size(circle_grid_size); - - set_vertices_connected(0); - set_total_vertices(circle_grid_size * circle_grid_size); - set_total_connections(0); - - setInterval(draw_everything, 1000/60); -} - -function draw_everything(){ - canvas_ctx.fillStyle = 'white'; - canvas_ctx.fillRect(0,0,canvas.width,canvas.height); - - draw_grid_circles(); - draw_grid_lines(); - draw_currently_held_line(); +window.onload = function () { + canvas = document.getElementById('main_canvas'); + canvas_ctx = canvas.getContext('2d'); + + canvas.addEventListener("mousedown", mouse_down); + canvas.addEventListener("mouseup", mouse_up); + canvas.addEventListener("mousemove", mouse_move); + + window.addEventListener('contextmenu', (event) => { + event.preventDefault() + }) + + set_grid_size(grid_size); + set_line_width(line_width); + set_circle_spacing(circle_spacing); + set_circle_radius(circle_radius); + auto_resize_canvas(); + + set_vertices_connected(0); + set_total_vertices(grid_size * grid_size); + set_total_connections(0); + + setInterval(draw_everything, 1000 / 60); +} + +function draw_everything() { + canvas_ctx.fillStyle = 'white'; + canvas_ctx.fillRect(0, 0, canvas.width, canvas.height); + + draw_grid_circles(); + draw_grid_lines(); + draw_currently_held_line(); } // Source: https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ @@ -89,20 +107,20 @@ function orientation(p, q, r) { // Returns true if line segment 'p1q1' and 'p2q2' intersect // assuming they're not collinear and they don't share vertexes. function segments_intersect(p1, q1, p2, q2) { - // Discard cases where segments share the same vertex - if (p1.x === p2.x && p1.y === p2.y) - return false; - - if (p1.x === q2.x && p1.y === q2.y) - return false; - - if (q1.x === p2.x && q1.y === p2.y) - return false; - - if (q1.x === q2.x && q1.y === q2.y) - return false; - - + // Discard cases where segments share the same vertex + if (p1.x === p2.x && p1.y === p2.y) + return false; + + if (p1.x === q2.x && p1.y === q2.y) + return false; + + if (q1.x === p2.x && q1.y === p2.y) + return false; + + if (q1.x === q2.x && q1.y === q2.y) + return false; + + let o1 = orientation(p1, q1, p2); let o2 = orientation(p1, q1, q2); let o3 = orientation(p2, q2, p1); @@ -114,462 +132,515 @@ function segments_intersect(p1, q1, p2, q2) { return false; } -function make_1D_index(x, y, arr_width){ - return y * arr_width + x; -} - -function draw_grid_circles(){ - let mark_connected_vertices_enabled = is_mark_connected_vertices_enabled(); - - for(let y = 0; y < circle_grid_size; y++){ - for(let x = 0; x < circle_grid_size; x++){ - let pos_x = circle_grid_start_pos_x + x * circle_grid_spacing; - let pos_y = circle_grid_start_pos_y + y * circle_grid_spacing; - - let diff_x = pos_x - last_mouse_pos_x; - let diff_y = pos_y - last_mouse_pos_y; - - let vertex_idx = make_1D_index(x, y, circle_grid_size); - let vertex_is_taken = false; - if(taken_vertices[vertex_idx] === 1){ - vertex_is_taken = true; - } - - let distance = Math.sqrt(diff_x * diff_x + diff_y * diff_y); - if(distance <= circle_radius){ - if(mark_connected_vertices_enabled && vertex_is_taken){ - draw_circle(pos_x, pos_y, circle_radius, colour_light_orange); - } - else{ - draw_circle(pos_x, pos_y, circle_radius, colour_light_blue); - } - } - else{ - if(mark_connected_vertices_enabled && vertex_is_taken){ - draw_circle(pos_x, pos_y, circle_radius, colour_orange); - } - else{ - draw_circle(pos_x, pos_y, circle_radius, colour_blue); - } - // draw_circle(pos_x, pos_y, circle_radius, colour_blue); - } - } - } -} - -function draw_grid_lines(){ - let mark_intersecting_edges_enabled = is_mark_intersecting_edges_enabled(); - - if(mark_intersecting_edges_enabled){ - // Split up drawing iterations, to avoid random order of line draws on top of another - // First draw green lines, then red lines - - for(let i = 0; i < grid_connection_data.length; i++){ - if(intersecting_edges[i] !== 0){ - continue; - } - - from_x = grid_connection_data[i][0]; - from_y = grid_connection_data[i][1]; - to_x = grid_connection_data[i][2]; - to_y = grid_connection_data[i][3]; - - from_x = from_x * circle_grid_spacing + circle_grid_start_pos_x; - from_y = from_y * circle_grid_spacing + circle_grid_start_pos_y; - to_x = to_x * circle_grid_spacing + circle_grid_start_pos_x; - to_y = to_y * circle_grid_spacing + circle_grid_start_pos_y; - - draw_line(from_x, from_y, to_x, to_y, colour_green); - } - - for(let i = 0; i < grid_connection_data.length; i++){ - if(intersecting_edges[i] !== 1){ - continue; - } - - from_x = grid_connection_data[i][0]; - from_y = grid_connection_data[i][1]; - to_x = grid_connection_data[i][2]; - to_y = grid_connection_data[i][3]; - - from_x = from_x * circle_grid_spacing + circle_grid_start_pos_x; - from_y = from_y * circle_grid_spacing + circle_grid_start_pos_y; - to_x = to_x * circle_grid_spacing + circle_grid_start_pos_x; - to_y = to_y * circle_grid_spacing + circle_grid_start_pos_y; - - draw_line(from_x, from_y, to_x, to_y, colour_red); - } - } - else{ - for(let i = 0; i < grid_connection_data.length; i++){ - from_x = grid_connection_data[i][0]; - from_y = grid_connection_data[i][1]; - to_x = grid_connection_data[i][2]; - to_y = grid_connection_data[i][3]; - - from_x = from_x * circle_grid_spacing + circle_grid_start_pos_x; - from_y = from_y * circle_grid_spacing + circle_grid_start_pos_y; - to_x = to_x * circle_grid_spacing + circle_grid_start_pos_x; - to_y = to_y * circle_grid_spacing + circle_grid_start_pos_y; - - draw_line(from_x, from_y, to_x, to_y, colour_green); - } - } -} - -function draw_currently_held_line(){ - if(last_grid_circle_x == -1 && last_grid_circle_y == -1){ - return; - } - - from_x = last_grid_circle_x * circle_grid_spacing + circle_grid_start_pos_x; - from_y = last_grid_circle_y * circle_grid_spacing + circle_grid_start_pos_y; - to_x = last_mouse_pos_x; - to_y = last_mouse_pos_y; - - if(left_mouse_button_is_down){ - draw_line(from_x, from_y, to_x, to_y, colour_green); - } - else{ - draw_line(from_x, from_y, to_x, to_y, colour_dark_red); - } -} - -function draw_circle(x, y, radius, style){ - let ctx = canvas_ctx; - ctx.save(); - - ctx.beginPath(); - ctx.arc(x, y, radius, 0, 2 * Math.PI, false); - ctx.fillStyle = style; - ctx.fill(); - - ctx.restore(); -} - -function draw_line(from_x, from_y, to_x, to_y, style){ - let ctx = canvas_ctx; - ctx.save(); - - ctx.lineWidth = 5; - ctx.lineCap = "round"; - ctx.beginPath(); - ctx.moveTo(from_x, from_y); - ctx.lineTo(to_x, to_y); - ctx.strokeStyle = style; - ctx.stroke(); - - ctx.restore(); -} - -function reset_grid(){ - grid_connection_data.length = 0; - update_connection_info(); -} - -function set_grid_size(new_value){ - circle_grid_size = clamp(new_value, min_circle_grid_size, max_circle_grid_size); - document.getElementById("grid_size").textContent = circle_grid_size; -} - -function increase_grid_size(){ - set_grid_size(circle_grid_size + 1); - update_connection_info(); -} - -function decrease_grid_size(){ - set_grid_size(circle_grid_size - 1); - - // remove out of bounds grid connections - for(let i = 0; i < grid_connection_data.length; i++){ - for(let j = 0; j < 4; j++){ - if(grid_connection_data[i][j] >= circle_grid_size){ - grid_connection_data.splice(i, 1); - i--; - break; - } - } - } - - update_connection_info(); -} - -function calculate_intersection_location(mouse_pos_x, mouse_pos_y){ - for(let y = 0; y < circle_grid_size; y++){ - for(let x = 0; x < circle_grid_size; x++){ - let pos_x = circle_grid_start_pos_x + x * circle_grid_spacing; - let pos_y = circle_grid_start_pos_y + y * circle_grid_spacing; - let diff_x = pos_x - mouse_pos_x; - let diff_y = pos_y - mouse_pos_y; - - let distance = Math.sqrt(diff_x * diff_x + diff_y * diff_y); - if(distance <= circle_radius){ - return [x, y] - } - } - } - - return [] -} - -function add_grid_entry_if_needed(mouse_pos_x, mouse_pos_y){ - let intersection = calculate_intersection_location(mouse_pos_x, mouse_pos_y); - if(intersection.length == 0){ - return; - } - - let old_last_grid_circle_x = last_grid_circle_x; - let old_last_grid_circle_y = last_grid_circle_y; - - last_grid_circle_x = intersection[0]; - last_grid_circle_y = intersection[1]; - - if(old_last_grid_circle_x == -1 && old_last_grid_circle_y == -1){ - return; - } - - let a_x = old_last_grid_circle_x; - let a_y = old_last_grid_circle_y; - let b_x = last_grid_circle_x; - let b_y = last_grid_circle_y; - let from_idx = make_1D_index(a_x, a_y, max_circle_grid_size - 1); - let to_idx = make_1D_index(b_x, b_y, max_circle_grid_size - 1); - - // we don't want want connections going from same to same element - if (from_idx == to_idx){ - return; - } - - let vec_x = Math.abs(a_x - b_x); - let vec_y = Math.abs(a_y - b_y); - - let vector_count = gcd(vec_x, vec_y); - let simplified_vector = {x: vec_x / vector_count, y: vec_y / vector_count}; - - if(a_x > b_x){ - simplified_vector.x *= -1; - } - - if(a_y > b_y){ - simplified_vector.y *= -1; - } - - // hack to avoid adding duplicate data such as: - // (0, 1) -> (2, 3) - // (2, 3) -> (0, 1) - if (from_idx > to_idx){ - [a_x, b_x] = [b_x, a_x]; - [a_y, b_y] = [b_y, a_y]; - simplified_vector.x *= -1; - simplified_vector.y *= -1; - } - - for(let i = 0; i < vector_count; i++){ - grid_connection_data.push([ - a_x + simplified_vector.x * i, - a_y + simplified_vector.y * i, - a_x + simplified_vector.x * (i + 1), - a_y + simplified_vector.y * (i + 1) - ]); - } - - // remove duplicate entries - // https://stackoverflow.com/a/44014849 - grid_connection_data = Array.from(new Set(grid_connection_data.map(JSON.stringify)), JSON.parse) - - update_connection_info(); -} - -function remove_grid_entry_if_needed(mouse_pos_x, mouse_pos_y){ - let intersection = calculate_intersection_location(mouse_pos_x, mouse_pos_y); - if(intersection.length == 0){ - return; - } - - let old_last_grid_circle_x = last_grid_circle_x; - let old_last_grid_circle_y = last_grid_circle_y; - - last_grid_circle_x = intersection[0]; - last_grid_circle_y = intersection[1]; - - if(old_last_grid_circle_x == -1 && old_last_grid_circle_y == -1){ - return; - } - - let arr_to_remove1 = [old_last_grid_circle_x, old_last_grid_circle_y, last_grid_circle_x, last_grid_circle_y]; - let arr_to_remove2 = [last_grid_circle_x, last_grid_circle_y, old_last_grid_circle_x, old_last_grid_circle_y]; - - for(let i = 0; i < grid_connection_data.length; i++){ - let is_equal1 = true; - let is_equal2 = true; - for(let j = 0; j < 4; j++){ - if(grid_connection_data[i][j] != arr_to_remove1[j]){ - is_equal1 = false; - } - if(grid_connection_data[i][j] != arr_to_remove2[j]){ - is_equal2 = false; - } - } - - if(is_equal1 || is_equal2){ - grid_connection_data.splice(i, 1); - } - } - - update_connection_info(); -} - -function set_d_g(new_value){ - document.getElementById("d_g").textContent = new_value; -} - -function set_c_g(new_value){ - document.getElementById("c_g").textContent = new_value; -} - -function set_vertices_connected(new_value){ - document.getElementById("vertices_connected").textContent = new_value; -} - -function set_total_vertices(new_value){ - document.getElementById("total_vertices").textContent = new_value; -} - -function set_total_connections(new_value){ - document.getElementById("total_connections").textContent = new_value; -} - -function is_mark_intersecting_edges_enabled(){ - return document.querySelector('.mark_intersecting_edges').checked; -} - -function is_mark_connected_vertices_enabled(){ - return document.querySelector('.mark_connected_vertices').checked; -} - -function update_connection_info(){ - let d_g = 0; - for(let i = 0; i < grid_connection_data.length; i++){ - let diff_x = Math.abs(grid_connection_data[i][0] - grid_connection_data[i][2]); - let diff_y = Math.abs(grid_connection_data[i][1] - grid_connection_data[i][3]); - d_g += diff_x * diff_x + diff_y * diff_y; - } - set_d_g(d_g); - - let c_g = 0; - for(let i = 0; i < grid_connection_data.length; i++){ - let x_i0 = grid_connection_data[i][0] + 1; - let y_i0 = grid_connection_data[i][1] + 1; - let x_i1 = grid_connection_data[i][2] + 1; - let y_i1 = grid_connection_data[i][3] + 1; - c_g += x_i0 * x_i1 + y_i0 * y_i1; - } - - set_c_g(c_g); - - // initialise array with 0 values - taken_vertices = Array.apply(null, Array(circle_grid_size * circle_grid_size)).map(function (x, i) { return 0; }); - let vertices_connected = 0; - for(let i = 0; i < grid_connection_data.length; i++){ - let from_idx = make_1D_index(grid_connection_data[i][0], grid_connection_data[i][1], circle_grid_size); - let to_idx = make_1D_index(grid_connection_data[i][2], grid_connection_data[i][3], circle_grid_size); - if(taken_vertices[from_idx] === 0){ - taken_vertices[from_idx] = 1; - vertices_connected += 1; - } - if(taken_vertices[to_idx] === 0){ - taken_vertices[to_idx] = 1; - vertices_connected += 1; - } - } - - set_vertices_connected(vertices_connected); - - set_total_vertices(circle_grid_size * circle_grid_size); - - set_total_connections(grid_connection_data.length); - - // initialise array with 0 values - intersecting_edges = Array.apply(null, Array(grid_connection_data.length)).map(function (x, i) { return 0; }); - for(let i = 0; i < grid_connection_data.length; i++){ - for(let j = 0; j < grid_connection_data.length; j++){ - if(i === j){ - continue; - } - - p1 = {x: grid_connection_data[i][0], y: grid_connection_data[i][1]}; - q1 = {x: grid_connection_data[i][2], y: grid_connection_data[i][3]}; - p2 = {x: grid_connection_data[j][0], y: grid_connection_data[j][1]}; - q2 = {x: grid_connection_data[j][2], y: grid_connection_data[j][3]}; - - if(segments_intersect(p1, q1, p2, q2)){ - intersecting_edges[i] = 1; - break; - } - } - } -} - -function get_canvas_mouse_pos(event){ - let rect = canvas.getBoundingClientRect(); - return { - x: event.clientX - rect.left, - y:event.clientY - rect.top - } +function make_1D_index(x, y, arr_width) { + return y * arr_width + x; +} + +function draw_grid_circles() { + let mark_connected_vertices_enabled = is_mark_connected_vertices_enabled(); + + for (let y = 0; y < grid_size; y++) { + for (let x = 0; x < grid_size; x++) { + let pos_x = circle_grid_start_pos_x + x * circle_spacing; + let pos_y = circle_grid_start_pos_y + y * circle_spacing; + + let diff_x = pos_x - last_mouse_pos_x; + let diff_y = pos_y - last_mouse_pos_y; + + let vertex_idx = make_1D_index(x, y, grid_size); + let vertex_is_taken = false; + if (taken_vertices[vertex_idx] === 1) { + vertex_is_taken = true; + } + + let distance = Math.sqrt(diff_x * diff_x + diff_y * diff_y); + if (distance <= circle_radius) { + if (mark_connected_vertices_enabled && vertex_is_taken) { + draw_circle(pos_x, pos_y, circle_radius, colour_light_orange); + } + else { + draw_circle(pos_x, pos_y, circle_radius, colour_light_blue); + } + } + else { + if (mark_connected_vertices_enabled && vertex_is_taken) { + draw_circle(pos_x, pos_y, circle_radius, colour_orange); + } + else { + draw_circle(pos_x, pos_y, circle_radius, colour_blue); + } + // draw_circle(pos_x, pos_y, circle_radius, colour_blue); + } + } + } +} + +function draw_grid_lines() { + let mark_intersecting_edges_enabled = is_mark_intersecting_edges_enabled(); + + if (mark_intersecting_edges_enabled) { + // Split up drawing iterations, to avoid random order of line draws on top of another + // First draw green lines, then red lines + + for (let i = 0; i < grid_connection_data.length; i++) { + if (intersecting_edges[i] !== 0) { + continue; + } + + from_x = grid_connection_data[i][0]; + from_y = grid_connection_data[i][1]; + to_x = grid_connection_data[i][2]; + to_y = grid_connection_data[i][3]; + + from_x = from_x * circle_spacing + circle_grid_start_pos_x; + from_y = from_y * circle_spacing + circle_grid_start_pos_y; + to_x = to_x * circle_spacing + circle_grid_start_pos_x; + to_y = to_y * circle_spacing + circle_grid_start_pos_y; + + draw_line(from_x, from_y, to_x, to_y, colour_green); + } + + for (let i = 0; i < grid_connection_data.length; i++) { + if (intersecting_edges[i] !== 1) { + continue; + } + + from_x = grid_connection_data[i][0]; + from_y = grid_connection_data[i][1]; + to_x = grid_connection_data[i][2]; + to_y = grid_connection_data[i][3]; + + from_x = from_x * circle_spacing + circle_grid_start_pos_x; + from_y = from_y * circle_spacing + circle_grid_start_pos_y; + to_x = to_x * circle_spacing + circle_grid_start_pos_x; + to_y = to_y * circle_spacing + circle_grid_start_pos_y; + + draw_line(from_x, from_y, to_x, to_y, colour_red); + } + } + else { + for (let i = 0; i < grid_connection_data.length; i++) { + from_x = grid_connection_data[i][0]; + from_y = grid_connection_data[i][1]; + to_x = grid_connection_data[i][2]; + to_y = grid_connection_data[i][3]; + + from_x = from_x * circle_spacing + circle_grid_start_pos_x; + from_y = from_y * circle_spacing + circle_grid_start_pos_y; + to_x = to_x * circle_spacing + circle_grid_start_pos_x; + to_y = to_y * circle_spacing + circle_grid_start_pos_y; + + draw_line(from_x, from_y, to_x, to_y, colour_green); + } + } +} + +function draw_currently_held_line() { + if (last_grid_circle_x == -1 && last_grid_circle_y == -1) { + return; + } + + from_x = last_grid_circle_x * circle_spacing + circle_grid_start_pos_x; + from_y = last_grid_circle_y * circle_spacing + circle_grid_start_pos_y; + to_x = last_mouse_pos_x; + to_y = last_mouse_pos_y; + + if (left_mouse_button_is_down) { + draw_line(from_x, from_y, to_x, to_y, colour_green); + } + else { + draw_line(from_x, from_y, to_x, to_y, colour_dark_red); + } +} + +function draw_circle(x, y, radius, style) { + let ctx = canvas_ctx; + ctx.save(); + + ctx.beginPath(); + ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + ctx.fillStyle = style; + ctx.fill(); + + ctx.restore(); +} + +function draw_line(from_x, from_y, to_x, to_y, style) { + let ctx = canvas_ctx; + ctx.save(); + + ctx.lineWidth = line_width; + ctx.lineCap = "round"; + ctx.beginPath(); + ctx.moveTo(from_x, from_y); + ctx.lineTo(to_x, to_y); + ctx.strokeStyle = style; + ctx.stroke(); + + ctx.restore(); +} + +function reset_grid() { + grid_connection_data.length = 0; + update_connection_info(); +} + +function set_grid_size(new_value) { + grid_size = clamp(new_value, min_grid_size, max_grid_size); + document.getElementById("grid_size").textContent = grid_size; + auto_resize_canvas(); +} + +function increase_grid_size() { + set_grid_size(grid_size + 1); + update_connection_info(); +} + +function decrease_grid_size() { + set_grid_size(grid_size - 1); + + // remove out of bounds grid connections + for (let i = 0; i < grid_connection_data.length; i++) { + for (let j = 0; j < 4; j++) { + if (grid_connection_data[i][j] >= grid_size) { + grid_connection_data.splice(i, 1); + i--; + break; + } + } + } + + update_connection_info(); +} + +function set_line_width(new_value) { + line_width = clamp(new_value, min_line_width, max_line_width); + document.getElementById("line_width").textContent = line_width; +} + +function increase_line_width() { + set_line_width(line_width + circle_line_width_increment); +} + +function decrease_line_width() { + set_line_width(line_width - circle_line_width_increment); +} + +function set_circle_spacing(new_value) { + circle_spacing = clamp(new_value, min_circle_spacing, max_circle_spacing); + document.getElementById("circle_spacing").textContent = circle_spacing; + auto_resize_canvas(); +} + +function increase_circle_spacing() { + set_circle_spacing(circle_spacing + circle_spacing_increment); +} + +function decrease_circle_spacing() { + set_circle_spacing(circle_spacing - circle_spacing_increment); +} + +function set_circle_radius(new_value) { + circle_radius = clamp(new_value, min_circle_radius, max_circle_radius); + document.getElementById("circle_radius").textContent = circle_radius; + recalculate_grid_start_pos(); + auto_resize_canvas(); +} + +function increase_circle_radius() { + set_circle_radius(circle_radius + circle_radius_increment); +} + +function decrease_circle_radius() { + set_circle_radius(circle_radius - circle_radius_increment); +} + +function auto_resize_canvas() { + let canvas_size = 2 * canvas_padding + 2 * circle_radius + circle_spacing * (grid_size - 1); + document.getElementById("main_canvas").width = canvas_size; + document.getElementById("main_canvas").height = canvas_size; + console.log(document.getElementById("main_canvas").width); + console.log(document.getElementById("main_canvas").height); + + draw_everything(); +} + +function recalculate_grid_start_pos() { + let start_pos = canvas_padding + circle_radius; + circle_grid_start_pos_x = start_pos; + circle_grid_start_pos_y = start_pos; +} + +function calculate_intersection_location(mouse_pos_x, mouse_pos_y) { + for (let y = 0; y < grid_size; y++) { + for (let x = 0; x < grid_size; x++) { + let pos_x = circle_grid_start_pos_x + x * circle_spacing; + let pos_y = circle_grid_start_pos_y + y * circle_spacing; + let diff_x = pos_x - mouse_pos_x; + let diff_y = pos_y - mouse_pos_y; + + let distance = Math.sqrt(diff_x * diff_x + diff_y * diff_y); + if (distance <= circle_radius) { + return [x, y] + } + } + } + + return [] +} + +function add_grid_entry_if_needed(mouse_pos_x, mouse_pos_y) { + let intersection = calculate_intersection_location(mouse_pos_x, mouse_pos_y); + if (intersection.length == 0) { + return; + } + + let old_last_grid_circle_x = last_grid_circle_x; + let old_last_grid_circle_y = last_grid_circle_y; + + last_grid_circle_x = intersection[0]; + last_grid_circle_y = intersection[1]; + + if (old_last_grid_circle_x == -1 && old_last_grid_circle_y == -1) { + return; + } + + let a_x = old_last_grid_circle_x; + let a_y = old_last_grid_circle_y; + let b_x = last_grid_circle_x; + let b_y = last_grid_circle_y; + let from_idx = make_1D_index(a_x, a_y, max_grid_size - 1); + let to_idx = make_1D_index(b_x, b_y, max_grid_size - 1); + + // we don't want want connections going from same to same element + if (from_idx == to_idx) { + return; + } + + let vec_x = Math.abs(a_x - b_x); + let vec_y = Math.abs(a_y - b_y); + + let vector_count = gcd(vec_x, vec_y); + let simplified_vector = { x: vec_x / vector_count, y: vec_y / vector_count }; + + if (a_x > b_x) { + simplified_vector.x *= -1; + } + + if (a_y > b_y) { + simplified_vector.y *= -1; + } + + // hack to avoid adding duplicate data such as: + // (0, 1) -> (2, 3) + // (2, 3) -> (0, 1) + if (from_idx > to_idx) { + [a_x, b_x] = [b_x, a_x]; + [a_y, b_y] = [b_y, a_y]; + simplified_vector.x *= -1; + simplified_vector.y *= -1; + } + + for (let i = 0; i < vector_count; i++) { + grid_connection_data.push([ + a_x + simplified_vector.x * i, + a_y + simplified_vector.y * i, + a_x + simplified_vector.x * (i + 1), + a_y + simplified_vector.y * (i + 1) + ]); + } + + // remove duplicate entries + // https://stackoverflow.com/a/44014849 + grid_connection_data = Array.from(new Set(grid_connection_data.map(JSON.stringify)), JSON.parse) + + update_connection_info(); +} + +function remove_grid_entry_if_needed(mouse_pos_x, mouse_pos_y) { + let intersection = calculate_intersection_location(mouse_pos_x, mouse_pos_y); + if (intersection.length == 0) { + return; + } + + let old_last_grid_circle_x = last_grid_circle_x; + let old_last_grid_circle_y = last_grid_circle_y; + + last_grid_circle_x = intersection[0]; + last_grid_circle_y = intersection[1]; + + if (old_last_grid_circle_x == -1 && old_last_grid_circle_y == -1) { + return; + } + + let arr_to_remove1 = [old_last_grid_circle_x, old_last_grid_circle_y, last_grid_circle_x, last_grid_circle_y]; + let arr_to_remove2 = [last_grid_circle_x, last_grid_circle_y, old_last_grid_circle_x, old_last_grid_circle_y]; + + for (let i = 0; i < grid_connection_data.length; i++) { + let is_equal1 = true; + let is_equal2 = true; + for (let j = 0; j < 4; j++) { + if (grid_connection_data[i][j] != arr_to_remove1[j]) { + is_equal1 = false; + } + if (grid_connection_data[i][j] != arr_to_remove2[j]) { + is_equal2 = false; + } + } + + if (is_equal1 || is_equal2) { + grid_connection_data.splice(i, 1); + } + } + + update_connection_info(); +} + +function set_d_g(new_value) { + document.getElementById("d_g").textContent = new_value; +} + +function set_vertices_connected(new_value) { + document.getElementById("vertices_connected").textContent = new_value; +} + +function set_total_vertices(new_value) { + document.getElementById("total_vertices").textContent = new_value; +} + +function set_total_connections(new_value) { + document.getElementById("total_connections").textContent = new_value; +} + +function is_mark_intersecting_edges_enabled() { + return document.getElementById("mark_intersecting_edges").checked; +} + +function is_mark_connected_vertices_enabled() { + return document.getElementById("mark_connected_vertices").checked; +} + +function is_auto_connect_circles_enabled() { + return document.getElementById("auto_connect_circles").checked; +} + +function update_connection_info() { + let d_g = 0; + for (let i = 0; i < grid_connection_data.length; i++) { + let diff_x = Math.abs(grid_connection_data[i][0] - grid_connection_data[i][2]); + let diff_y = Math.abs(grid_connection_data[i][1] - grid_connection_data[i][3]); + d_g += diff_x * diff_x + diff_y * diff_y; + } + set_d_g(d_g); + + // initialise array with 0 values + taken_vertices = Array.apply(null, Array(grid_size * grid_size)).map(function (x, i) { return 0; }); + let vertices_connected = 0; + for (let i = 0; i < grid_connection_data.length; i++) { + let from_idx = make_1D_index(grid_connection_data[i][0], grid_connection_data[i][1], grid_size); + let to_idx = make_1D_index(grid_connection_data[i][2], grid_connection_data[i][3], grid_size); + if (taken_vertices[from_idx] === 0) { + taken_vertices[from_idx] = 1; + vertices_connected += 1; + } + if (taken_vertices[to_idx] === 0) { + taken_vertices[to_idx] = 1; + vertices_connected += 1; + } + } + + set_vertices_connected(vertices_connected); + + set_total_vertices(grid_size * grid_size); + + set_total_connections(grid_connection_data.length); + + // initialise array with 0 values + intersecting_edges = Array.apply(null, Array(grid_connection_data.length)).map(function (x, i) { return 0; }); + for (let i = 0; i < grid_connection_data.length; i++) { + for (let j = 0; j < grid_connection_data.length; j++) { + if (i === j) { + continue; + } + + p1 = { x: grid_connection_data[i][0], y: grid_connection_data[i][1] }; + q1 = { x: grid_connection_data[i][2], y: grid_connection_data[i][3] }; + p2 = { x: grid_connection_data[j][0], y: grid_connection_data[j][1] }; + q2 = { x: grid_connection_data[j][2], y: grid_connection_data[j][3] }; + + if (segments_intersect(p1, q1, p2, q2)) { + intersecting_edges[i] = 1; + break; + } + } + } +} + +function get_canvas_mouse_pos(event) { + let rect = canvas.getBoundingClientRect(); + return { + x: event.clientX - rect.left, + y: event.clientY - rect.top + } } function mouse_down(event) { - mouse_is_down = true; - if(event.button == 0){ - left_mouse_button_is_down = true; - } - - if (event.button == 2){ - right_mouse_button_is_down = true; - } - - last_grid_circle_x = -1; - last_grid_circle_y = -1; - - let mouse_pos = get_canvas_mouse_pos(event); - - let intersection = calculate_intersection_location(mouse_pos.x, mouse_pos.y); - if(intersection.length == 0){ - return; - } - - last_grid_circle_x = intersection[0]; - last_grid_circle_y = intersection[1]; - - mouse_down_pos_x = mouse_pos.x; - mouse_down_pos_y = mouse_pos.y; + mouse_is_down = true; + if (event.button == 0) { + left_mouse_button_is_down = true; + } + + if (event.button == 2) { + right_mouse_button_is_down = true; + } + + last_grid_circle_x = -1; + last_grid_circle_y = -1; + + let mouse_pos = get_canvas_mouse_pos(event); + + let intersection = calculate_intersection_location(mouse_pos.x, mouse_pos.y); + if (intersection.length == 0) { + return; + } + + last_grid_circle_x = intersection[0]; + last_grid_circle_y = intersection[1]; } function mouse_up(event) { - if(event.button == 0){ - left_mouse_button_is_down = false; - } - - if (event.button == 2){ - right_mouse_button_is_down = false; - } - - last_grid_circle_x = -1; - last_grid_circle_y = -1; + let mouse_pos = get_canvas_mouse_pos(event); + + if (event.button == 0) { + add_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); + left_mouse_button_is_down = false; + } + + if (event.button == 2) { + remove_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); + right_mouse_button_is_down = false; + } + + last_grid_circle_x = -1; + last_grid_circle_y = -1; } function mouse_move(event) { - let mouse_pos = get_canvas_mouse_pos(event); - - last_mouse_pos_x = mouse_pos.x - last_mouse_pos_y = mouse_pos.y - - if (left_mouse_button_is_down == true){ - add_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); - } - else if (right_mouse_button_is_down == true){ - remove_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); - } + let mouse_pos = get_canvas_mouse_pos(event); + + last_mouse_pos_x = mouse_pos.x + last_mouse_pos_y = mouse_pos.y + + if (is_auto_connect_circles_enabled() == false) { + return; + } + + if (left_mouse_button_is_down == true) { + add_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); + } + else if (right_mouse_button_is_down == true) { + remove_grid_entry_if_needed(mouse_pos.x, mouse_pos.y); + } } function gcd(a, b) { diff --git a/connect_points/style.css b/connect_points/style.css index 66ac453..b88bdbb 100644 --- a/connect_points/style.css +++ b/connect_points/style.css @@ -1,53 +1,57 @@ body { - font: 18px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - background-color:lightgrey; - margin: 40px auto; - padding: 0 10px; - max-width: 700px; + font: 18px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + background-color: lightgrey; + margin: 1em auto; + padding: 0 0.5em; } -div.canvas { - text-align: center; +.maxwidth{ + max-width: 700px; + margin: 1em auto; +} + +.centered{ + text-align: center; } div.option_column { - float: left; - width: 40%; + float: left; + width: 40%; } div.function_column { - float: left; - width: 20%; + float: left; + width: 20%; } div.info_column { - float: left; - width: 40%; + float: left; + width: 40%; } /* Clear floats after the columns */ div.canvas_info:after { - content: ""; - display: table; - clear: both; + content: ""; + display: table; + clear: both; } img { - width: auto; - height: 250px; + width: auto; + height: 250px; } @media (prefers-color-scheme: dark) { - body { - color: #c9d1d9; - background: #0d1117; - } - - a:link { - color: #58a6ff; - } - - a:visited { - color: #8e96f0; - } + body { + color: #c9d1d9; + background: #0d1117; + } + + a:link { + color: #58a6ff; + } + + a:visited { + color: #8e96f0; + } } \ No newline at end of file