JS

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>&int;</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.