Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beta build #62

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
582efe5
latex setup done
MerujSargsyan Nov 21, 2024
208ad43
added base string
MerujSargsyan Nov 21, 2024
0666708
need to fix up closest function
MerujSargsyan Nov 21, 2024
6c7b44a
simplified distance calculator
MerujSargsyan Nov 21, 2024
d1daddb
corrected relativePos logic
MerujSargsyan Nov 21, 2024
96511ea
applied linter
MerujSargsyan Nov 22, 2024
11da732
did some standardizing with project
MerujSargsyan Nov 22, 2024
9c3e2aa
faulty but have to plan it out more
MerujSargsyan Nov 23, 2024
99973bd
state conversion works
MerujSargsyan Nov 23, 2024
9d1fe88
edges being drawn
MerujSargsyan Nov 24, 2024
a68e0c6
angle calculation works
MerujSargsyan Nov 24, 2024
ceeb4f4
label no longer on vertical line
MerujSargsyan Nov 24, 2024
3d8624d
converting labels based on machine type
MerujSargsyan Nov 24, 2024
b245a7d
format 1 failed
MerujSargsyan Nov 25, 2024
ff8c30f
trying out absolute positioning
MerujSargsyan Nov 26, 2024
8f4cf0b
trying normal vector positioning
MerujSargsyan Nov 26, 2024
a8a0afd
trying logarithmic compression
MerujSargsyan Nov 26, 2024
8f3b004
planar compression done, needs testing
MerujSargsyan Nov 26, 2024
fcb64a4
cleanup done
MerujSargsyan Nov 26, 2024
346827a
angled edges between labels done
MerujSargsyan Nov 26, 2024
5548a1b
todo self loops
MerujSargsyan Nov 27, 2024
3a1c743
added tests and TODO
MerujSargsyan Nov 27, 2024
afc5e83
cleanup
MerujSargsyan Nov 27, 2024
12b9152
Merge pull request #1 from MerujSargsyan/exactPositioning
MerujSargsyan Nov 28, 2024
390abff
ran linter
MerujSargsyan Dec 2, 2024
e4dd63d
removed no ussless escape rule
MerujSargsyan Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"error",
"always"
],
"no-useless-escape": [
"off"
],
"no-unused-vars": [
"warn",
{ "vars": "all", "args": "after-used", "ignoreRestSiblings": false }
Expand Down
7 changes: 4 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<link rel="manifest" href="assets/site.webmanifest">
<meta name="theme-color" content="#ffffff">

<script src="scripts/index.js" type="module"></script>
<script defer src="scripts/index.js" type="module"></script>
</head>
<body>

Expand Down Expand Up @@ -86,8 +86,9 @@ <h1>Header</h1>
</div>

<div hidden class="dropdown save">
<button id="save_machine">Save as png</button>
<button id="permalink">Permalink</button>
<button id="save_machine">Save as png</button> <br>
<button id="permalink">Permalink</button> <br>
<button id="latex">Latex</button> <br>
</div>
</section>

Expand Down
5 changes: 4 additions & 1 deletion scripts/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,7 @@ export const PLUS = '\u207A';
export const SIGMA = '\u03A3';

/** @constant {string} EMPTY_SET - empty set symbol for regular expressions */
export const EMPTY_SET = '\u2205';
export const EMPTY_SET = '\u2205';

/** @constant {float} LATEX_ANGLE_SCALE - used to scale angels for curved edges in tikzpicture*/
export const LATEX_ANGLE_SCALE = 8;
39 changes: 29 additions & 10 deletions scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as permalink from './permalink.js';
import * as util from './util.js';
import * as ui_setup from './ui_setup.js';
import * as regex from './regex.js';
import * as latex from './latex.js';

// if not in browser, don't run
if (typeof document !== 'undefined') {
Expand Down Expand Up @@ -389,6 +390,19 @@ function bind_permalink() {
window.addEventListener('hashchange', hash_change_handler);
}

/** button to generate latex text */
function bind_latex() {
// TODO
const latex_button = document.getElementById('latex');
latex_button.addEventListener('click', () => {
const select = document.getElementById('select_machine');
const latex_str = latex.serialize(select.value, graph);
navigator.clipboard.writeText(latex_str)
.then(() => alert('Latex text copied to clipboard'));
});
return;
}

/** change cursor style when hovering over certain elements */
function bind_mousemove() {
const canvas = drawing.get_canvas();
Expand All @@ -405,10 +419,14 @@ function bind_mousemove() {

/** bind context menu for side nav bar and secondary side navbar */
function bind_context_menu_navbar(){
const navbar = document.querySelector('.nav')
const secondBar = document.querySelector('#secondbar')
navbar.addEventListener('click', () => {menus.remove_context_menu()})
secondBar.addEventListener('click', () => {menus.remove_context_menu()})
const navbar = document.querySelector('.nav');
const secondBar = document.querySelector('#secondbar');
navbar.addEventListener('click', () => {
menus.remove_context_menu();
});
secondBar.addEventListener('click', () => {
menus.remove_context_menu();
});
/*
for(var btns of navbar){
btns.addEventListener('click', () => {remove_context_menu()})
Expand All @@ -422,7 +440,7 @@ function bind_regex() {
const convert_to_nfa_btn = document.getElementById('convert_to_nfa');
convert_to_nfa_btn.addEventListener('click', () => {
console.log(document.getElementById('regex_string').value);
let inputString = document.getElementById('regex_string').value
let inputString = document.getElementById('regex_string').value;
inputString = inputString.replace(/\s/g, '');
if (regex.isValidRegex(inputString)) {
graph = regex.process_string(inputString);
Expand All @@ -431,14 +449,14 @@ function bind_regex() {
drawing.draw(graph);
// hist.push_history(graph); NEED TO IMPLEMENT HISTORY BEFORE UNCOMMENTING
} else {
alert("Invalid regular expression.")
alert('Invalid regular expression.');
}
});
const input_field = document.getElementById('regex_string');
input_field.addEventListener('keypress', (e) => {
if (e.key === "Enter") {
if (e.key === 'Enter') {
console.log(document.getElementById('regex_string').value);
let inputString = document.getElementById('regex_string').value
let inputString = document.getElementById('regex_string').value;
inputString = inputString.replace(/\s/g, '');
if (regex.isValidRegex(inputString)) {
graph = regex.process_string(inputString);
Expand All @@ -447,10 +465,10 @@ function bind_regex() {
drawing.draw(graph);
// hist.push_history(graph); NEED TO IMPLEMENT HISTORY BEFORE UNCOMMENTING
} else {
alert("Invalid regular expression.")
alert('Invalid regular expression.');
}
}
})
});
}

/** run after all the contents are loaded to hook up callbacks */
Expand All @@ -466,6 +484,7 @@ function init() {
bind_scroll();
bind_dd();
bind_permalink();
bind_latex();
bind_mousemove();
ui_setup.bind_plus_minus();
ui_setup.add_input_bar(); // called so one input bar appears on opening of homepage
Expand Down
188 changes: 188 additions & 0 deletions scripts/latex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/** @module latex */

// -------------------------------------------------------------
// @author Meruzhan Sargsyan
//
// A module used to export the graph as text used in tikzpicture
// for easily exporting graphs into latex files
// -------------------------------------------------------------

//----------------------------------------------
// Testing Notes:
// - clipboard only available in secure contexts
//----------------------------------------------

//----------------------------------------------
// Current TODO:
// 1. Self loops only go above
// 2. Horizontally bend angles have label on the left
// 3. Fix more complicated state names
// 4. Overlapping labels for self loops
//----------------------------------------------

import * as consts from './consts.js';
import * as linalg from './linalg.js';

let debug = false; // change this to enable/disable logging

/**
* compresses graph to tikz space
* @param {String} type - type of graph (DFA, NFA, ...)
* @param {Array<Object>} states - the states of the graph
* @returns {Array<String>} formatted positions of states
*/
function compressPlanar(states) {
const distance = 8;

let centroidX = 0, centroidY = 0;
let n = states.length;

let output = Array(n);

for(let i = 0; i < n; i++) {
let state = states[i];
centroidX += state.x;
centroidY += state.y;
output[i] = [state.x, state.y];
}
if(debug) {
console.log(output);
}

centroidX /= n;
centroidY /= n;
let center = [centroidX, centroidY];

let maxDist = Number.MIN_VALUE;
for(let i = 0; i < n; i++) {
output[i] = linalg.sub(output[i], center);
maxDist = Math.max(maxDist, linalg.vec_len(output[i]));
}

let scaleFactor = distance / (2 * maxDist);
let formatted = output.map((v) => {
let scaled = linalg.scale(scaleFactor, v);
return `(${scaled[0].toFixed(2)},${-1 * scaled[1].toFixed(2)})`;
});

if(debug) {
console.log(formatted);
}
return formatted;
}

/**
* Computes the type of a given state
* @param {Object} state
* @returns {String} tikz labels for the type of state
*/
function getStateType(state) {
let inner = 'state,';
if(state.is_start) {
inner += 'initial,';
}
if(state.is_final) {
inner += 'accepting,';
}

return inner;
}

/**
* converts an edge to tikz string representation
* @param {String} type - type of graph (DFA, NFA, ...)
* @param {Object} edge - edge to convert to string
* @param {String} labelPos - where to position label on edge
* @returns {String} - tikz string representaiton of edge
*/
function edgeToString(type, edge, labelPos) {
if(debug) {
console.log(edge);
}
let bendAngle = Math.floor(edge.a2) * consts.LATEX_ANGLE_SCALE;
let inner = `bend right=${bendAngle}`;
let label = `${edge.transition}`;

if(bendAngle > consts.LATEX_ANGLE_SCALE) {
labelPos = 'right';
} else if(bendAngle < 0) {
labelPos = 'left';
}

if(edge.from === edge.to) {
inner = 'loop above';
labelPos = 'above';
}

switch (type) {
case 'PDA':
label += `,${edge.pop_symbol} \\rightarrow ${edge.push_symbol}`.replaceAll('$', '\\$');
break;
case 'Turing':
label += ` \\rightarrow ${edge.push_symbol}, ${edge.move}`.replaceAll('$', '\\$');
break;
default:
break;
}


let output = `(${edge.from}) edge [${inner}] node[${labelPos}] {$${label}$} (${edge.to})\n`;
return output.replaceAll(consts.EMPTY_SYMBOL, '\\epsilon').replaceAll(consts.EMPTY_TAPE, '\\square');
}

/**
* @param {Object} graph - graph to be converted to latex
* @return {String} representation of graph in latex tikzpicture
*/
export function serialize(type, graph) {
// setup
let distance = 2;

let output = `\\begin{tikzpicture}[->,>=stealth\',shorten >=1pt, auto, node distance=${distance}cm, semithick]\n`;
output += '\\tikzstyle{every state}=[text=black, fill=none]\n';

// initializing nodes
let states = Object.values(graph);
states.sort((a,b) => a.x - b.x); // sorts the states from left to right

let statePositions = compressPlanar(states);

let start = states[0];
let inner = getStateType(start);

for(let i = 0; i < states.length; i++) {
let current = states[i];
inner = getStateType(current);
let position = statePositions[i];
output += `\\node[${inner}] (${current.name}) at ${position} {$${current.name}$};\n`;
}

output += '\n';
output += '\\path\n';

for(let i = 0; i < states.length; i++) {
let current = states[i];
let edges = current.out; // array of edges

for(let j = 0; j < edges.length; j++) {
let edge = edges[j];
let labelPosition = 'above';

let startState = graph[edge.from];
let endState = graph[edge.to];
let angle = linalg.angle([startState.x, startState.y], [endState.x, endState.y]);

if(angle <= -80 && angle >= -110) {
labelPosition = 'left';
} else if(angle >= 80 && angle <= 110) {
labelPosition = 'right';
}

output += edgeToString(type, edge, labelPosition);
}
}
output += ';\n';

output += '\\end{tikzpicture}';
console.log(output);
}
22 changes: 22 additions & 0 deletions scripts/linalg.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,25 @@ export function inv(v1, v2) {
const inv_det = 1/det(v1, v2);
return [scale(inv_det, [v2[1], -v1[1]]), scale(inv_det, [-v2[0], v1[0]])];
}

/**
* computes angle between v1, v2 in degrees from x-axis
* @param {Array<float>} v1 - vector to compute angle from
* @param {Array<float>} v2 - vector to compute angle to
* @returns {float} angle between v1, v2
*/
export function angle(v1, v2) {
let direction = [v2[0] - v1[0], v2[1] - v1[1]];
let base = [1, 0]; // x axis

let dotProd = dot(direction, base);
let mult = vec_len(direction); // |base| = 1

let angle = Math.acos(dotProd/mult) * (180 / Math.PI);

// start is above
if(v1[1] < v2[1]) {
angle *= -1;
}
return angle;
}
Loading
Loading