Nambafa
Formula generator
Introduction
Html does not have great native support for displaying mathematical expressions. To be able to formulate simple equations with ease, I have written a light-weight JavaScript generator that can represent some basic expressions in the form of tables, which then can be displayed as regular html.
An example of usage - plus the full code of the generator - can be found below. :)
Example of usage
The below formula is made using the generator.
The formula was generated using the relatively simple code below.
f.out( f.inte( f.frac('sin'+f.sup('2')+'x','x'), 'x', '0', '∞' ) + ' = ' + f.frac('π','2') );
The generator code
The below code is the full code for the generator.
class TableFormulator{ /*********************************************************************************************************************** Description: Class to generate table representations of simple formulas (mathematical equations) Made by: Kristian Stormark Version: 0.9.81 Usage (example): var f = new TableFormulator(); var eq1 = document.getElementById('eq1'); eq1.innerHTML = f.out( f.frac('1','2π') + f.inte('sin(x)','x','a','b') ); ************************************************************************************************************************/ constructor(opt={}) { // Fun: constructor //-------------------------------------------------------------------------------------> DESCRIPTION // Class constructor - does nothing except set id + adds styling rules to document // Note //-------------------------------------------------------------------------------------> SET DEFAULTS var setdef = function(o,p,v){if(!o.hasOwnProperty(p)){o[p]=v;} return o;}; // Aux: upd w/def val if unset opt = setdef( opt, 'id', 'tbf' ); // Def: id (upd for mult inst) opt = setdef( opt, 'border', false ); // Def: no borders (add for qc) opt = setdef( opt, 'color', this.el2pv(document.body,'color')); // Def: body font color //-------------------------------------------------------------------------------------> SET OBJECT ID this.id = opt.id; // Set obj id (for css classes) //-------------------------------------------------------------------------------------> INSERT STYLE-SHEET var sid = this.id + '_style'; // Id for style el if (document.getElementById(sid) === null){ // If not style el exist var c = opt.color; // Get color (for fractions) var s = document.createElement('style'); s.type='text/css'; s.id=sid; // Create style el (+set type&id) var obj = this; // ("Bind" this...) var r = function(cstr,rstr,e=''){return obj.s2s(cstr)+e+'{' + rstr + '}';}; // Short-hand: rule generator var bc = 'border-spacing:0;border-collapse:collapse;'; // Short-hand: border collapse var str = '' + // Build rule string r('itm','display:inline-table;margin:0 0.2em;' + bc) + // + In-line table w/margin r('itn','display:inline-table;margin:0;' + bc) + // + In-line table w/no margin r('sup','font-size:65%;margin:0 auto;padding:0 auto;height:0.6em;' ) + // + Sup-script (h=min-h) r('sub','font-size:65%;margin:0 auto;padding:0 auto;height:0.6em;' ) + // + Sub-script (h=min-h) r('grp','font-size:150%;display:inline-block;' ) + // + "Group" el, like int. sign r('wrp','display:flex;justify-content:center;align-items:center;' ) + // + Wrapper (for v. centering) r('wrp','padding:0 0.1em;',' td' ) + // + Wrapper (for v. centering) r('lin','border-top:1px solid ' + c + ';margin:0;padding:0;' + bc) ; // + Line for fraction if(opt.border){ str = str + obj.s2s('wrp') + ' td{border: 1px solid ' + c + ';}'; } // Opt: Add border s.innerHTML = str; // Set style str document.getElementsByTagName('head')[0].appendChild(s); // Add style to document } // End: if } // End: fun //---------------------------------------------------------------------------------------> MINI-FUNCTIONS el2pv (el,p) { return window.getComputedStyle(el, null).getPropertyValue(p); } // Aux: get el. p. value (comp.) s2c (str) { return str==='' ? '' : ' class="' + this.id + '_' + str + '"'; } // Aux: string-to-class s2s (str) { return '.' + this.id + '_' + str; } // Aux: string-to-selector beg () { return '<span ' + this.s2c('wrp') + '>'; } // Equation begin end () { return '</span>'; } // Equation end out (str) { return this.enc(str); } // Equation out enc (str) { return this.beg() + str + this.end(); } // Aux: encapsulate encif (str) { return str ==='' ? str : this.beg() + str + this.end(); } // Aux: encapsulate if not empty tagger(t) { return (d,c='',a='') =>'<'+t+this.s2c(c)+' '+a+'>'+d+'</'+t+'>';} // Aux: tag generator (req. bind) //---------------------------------------------------------------------------------------> INTEGRAL inte (f, x, a=null, b=null,symb='<i>∫</i>'){ // Fun: return str var g,tb,tr,td;g=(t)=>this.tagger(t).bind(this); tb=g('table');tr=g('tr');td=g('td'); // Gen: table,tr, td var span = g('span'); // Gen: span var s = f.includes('wrp') ? ' style="transform:scale(1.2,1.4);"' : ''; // Mod: scale if nested (once) b = b === null ? '' : tr( td(b,'sup') + td('') + td('') ); // Row: upper lim a = a === null ? '' : tr( td(a,'sub') + td('') + td('') ); // Row: lower lim var i = tr( td(span(symb,'grp',s)) + td(this.enc(f)) + td('d'+x) ); // Row: ∫+integrand+dx return tb ( b + i + a, 'itm' ); // Tbl: all rows } // End: fun //---------------------------------------------------------------------------------------> FRACTION frac (a,b){ // Fun: return str var g,tb,tr,td;g=(t)=>this.tagger(t).bind(this); tb=g('table');tr=g('tr');td=g('td'); // Gen: table,tr, td return tb ( tr(td(this.enc(a))) + tr(td(''),'lin') + tr(td(this.enc(b))), 'itm' ); // Tbl: a-line-b } // End: fun //---------------------------------------------------------------------------------------> VERTICAL STACK vert (arr){ // Fun: return str var g,tb,tr,td;g=(t)=>this.tagger(t).bind(this); tb=g('table');tr=g('tr');td=g('td'); // Gen: table,tr, td return tb ( arr.map(s=>tr(td(this.encif(s),'sup'))).join(''), 'itn' ); // Tbl: all rows } // End: fun //---------------------------------------------------------------------------------------> SUP/POW AND SUB/IND sup (n){ return this.vert([n,'']); } pow(n){ return this.sup(n); } // Fun: return str sub (i){ return this.vert(['',i]); } ind(n){ return this.sub(n); } // Fun: return str } // End: class
Additional comments
The code works by generating strings for the various equation parts, which can be combined via simple string concatenation (c=a+b). The generated strings are HTML code (in the form of tabulated text), which then can be inserted into a HTML element for rendering in the browser.
At the stage of output, the string should be given a final wrapping, which can be done with the out() function.
The display formatting is handled by a stylesheet that is created when the formulator object is instantiated.
The string components can be nested, so that more complex formulas can be produced, as (trivially) shown below.
The above equation was generated via the below code.
let a = f.inte( f.frac('sin'+f.sup('2')+'x','x'), 'x', '0', '∞' ) let b = f.frac('π','2'); let formula = f.frac(a,a) + ' = ' + f.frac(b,b) + '=' + '1'; eq2.innerHTML = f.out(formula);
The formulator is relatively simple, and more advanced features are not implemented. E.g., the integral symbol is a simple character and will not stretch.