diagramatics logo Diagramatics GitHub Docs Guides Editor Examples

Diagramatics

Diagramatics is a JavaScript/TypeScript library that makes it easy to create interactive diagrams, especially math and physics diagrams. It provides a simple high-level API for creating and manipulating diagram elements and interactive controls.

Examples

let porigin = V2(0,0); let ppulley = V2(5,10); let pulley_radius = 0.5; let pulley = circle(pulley_radius).position(ppulley) .fill('lightgrey').stroke('grey').strokewidth(2); let topy = 10.8; let ceiling = rectangle(10.5, 0.1).move_origin('bottom-left').position(V2(-2.5,topy)) .fill('lightgray').stroke('none'); let pL = geometry.circle_tangent_point_from_point(porigin, pulley)[1]; let pR = pulley.origin.add(V2(pulley_radius, 0)); let trackline = line(porigin, pL).strokedasharray([5]).stroke('gray'); let track = trackline.apply(mod.slicepath(0.15, 0.75, 100)); let phand0 = trackline.parametric_point(0.8) let vhand = porigin.sub(pL).normalize(); let pBox0 = pR.sub(V2(0,1).scale(10)); let x0 = phand0.sub(pL).length(); int.draw_function = (inp) => { let phand = inp['phand']; let x = phand.sub(pL).length(); let dx = x - x0; let pBox = pBox0.add(V2(0, dx)); let box = square(2).move_origin('bottom-center').position(pBox) .fill('lightblue').stroke('none'); let ropes = diagram_combine( line(pL, phand), line(pR, box.get_anchor('top-center')), line(pulley.origin, V2(pulley.origin.x, topy)), ); let annotations = diagram_combine( annotation.length(phand, phand0, 'x', -0.5, 0.2), annotation.length(pBox0, pBox, 'x', 1.2, 0.2, 1.5), ).stroke('gray').textfill('black'); let vectors = diagram_combine( annotation.vector(V2(0,-1).scale(1.4), 'W', V2(-0.5,0.0), 0.1) .position(box.get_anchor('center-center')), annotation.vector(vhand.scale(1.4), 'F = W', V2(-0.3,0.3), 0.1) .position(phand).textanchor('end'), ).fill('black').fontsize(13); draw( trackline, ropes, pulley, box, ceiling, annotations, vectors, ); } int.locator('phand', track.parametric_point(0.8), 0.5, 'blue', track); int.draw(); int.locator_draw();
let x0 = 50; let amplitude = 25; let omega = 1; int.draw_function = (inp) => { let t = inp['t']; let x = x0 + amplitude * Math.cos(omega * t); let wall = line(V2(0,0), V2(0,20)).strokewidth(2); let floor = line(V2(0,0), V2(100,0)).strokewidth(2); let box = square(20).position(V2(x,10)) .fill('lightblue').stroke('none'); let p1 = wall.get_anchor('center-right'); let p2 = box.get_anchor('center-left'); let spring = mechanics.spring(p1,p2, 2, 10, 1.2); draw(box, wall, floor, spring) } let period = 2*Math.PI/omega; int.slider('t', 0, 2*period, period/4, 0.1, 2); int.draw();
let pip_positions = { 1 : [V2(0,0)], 2 : [V2(-1,-1), V2(1,1)], 3 : [V2(-1,-1), V2(0,0), V2(1,1)], 4 : [V2(-1,-1), V2(1,-1), V2(-1,1), V2(1,1)], 5 : [V2(-1,-1), V2(1,-1), V2(0,0), V2(-1,1), V2(1,1)], 6 : [V2(-1,-1), V2(1,-1), V2(-1,0), V2(1,0), V2(-1,1), V2(1,1)], } let generate_dice_pips = (n) => { let positions = pip_positions[n]; let pips = positions.map((p) => regular_polygon(10,0.25).position(p)); return diagram_combine(...pips).fill('black').stroke('none'); } let generate_dice = (n) => { let outline = square(4) .apply(mod.round_corner(0.5, undefined, 4)) .fill('white').stroke('black').strokewidth(2); let pips = generate_dice_pips(n); return diagram_combine(outline, pips); } const dice_diagram = [1,2,3,4,5,6].map((n) => generate_dice(n).mut()); function randint(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } let dicevalue = new Array(20*2).fill(1); let bincount = new Array(12+1).fill(1); let datanames = range_inc(1,12).map(String); function reroll() { for (let i = 0; i < dicevalue.length; i++) dicevalue[i] = randint(1,6); } function countbin(n){ bincount.fill(0); for (let i = 0; i < n; i++) { let val = dicevalue[2*i] + dicevalue[2*i+1]; bincount[val] += 1; } } // ============================= diagram let subdiagram1 = rectangle(30,30); let subdiagram2 = rectangle(40,30).move_origin('top-left').position(subdiagram1.get_anchor('top-right')); let bbox2 = subdiagram2.bounding_box(); let diagram_bg = subdiagram1.combine(subdiagram2).stroke('none'); let baropt = { yrange : [0,10.1], bbox : [bbox2[0].add(V2(2,6)), bbox2[1].sub(V2(2,3))], ticksize: 0.5, } let xax = bar.xaxes(datanames, baropt); let yax = bar.yaxes(bincount.slice(1), baropt); let xlabel = text('value').move_origin_text('top-center') .position(xax.get_anchor('bottom-center')).translate(V2(0,-3)); let ylabel = text('# of occurrence').move_origin_text('bottom-center') .position(yax.get_anchor('center-left')).translate(V2(-1.5,0)) .textangle(to_radian(-90)); let ddots_ = text('⋯').position(subdiagram1.get_anchor('bottom-right')) .translate(V2(-5.5,8)) let bgrect = rectangle(6,13).apply(mod.round_corner(1, undefined, 4)) .fill('lightblue').stroke('none').mut(); int.draw_function = (inp) => { default_textdata["font-scale"] = "0.11"; let n = inp['n']; countbin(n); // diagram1 let dicepairlist = range(0,Math.min(n,7)).map((i) => { let vtop = dicevalue[2*i]; let vbot = dicevalue[2*i+1]; let vsum = vtop + vbot; let dtop = dice_diagram[vtop-1].copy(); let dbot = dice_diagram[vbot-1].copy(); let lsum = text(vsum); let pair = distribute_vertical_and_align([lsum, dtop, dbot], 1) let bg = bgrect.copy().position(pair.origin).translate(V2(0,-4.5)); return bg.combine(pair).mut(); }); let dice = distribute_grid_row(dicepairlist, 4, 0.5, 0.5).flatten() .move_origin('top-left') .position(subdiagram1.get_anchor('top-left').add(V2(2,-2))); let ddots = n > 7 ? ddots_ : empty(V2(0,0)); // diagram2 let bars = bar.plot(bincount.slice(1), baropt).fill('lightblue').stroke('none'); // draw draw( diagram_bg, dice, ddots, bars, xax, yax, xlabel, ylabel, ); reset_default_styles(); } int.slider('n', 1, 20, 10, 1); reroll(); int.draw(); let btn = document.getElementById("roll_double"); btn.onclick = () => { reroll(); int.draw(); }

More examples

Usage

You can try online in the editor
Or see the Guides for more details.