
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/visualisation","feed_url":"https:\/\/mathspp.com\/blog\/tags\/visualisation.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"Cyclic trapezoid animation","date_published":"2026-03-14T15:51:00+01:00","id":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","url":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","content_html":"<p>See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.<\/p>\n\n<h2 id=\"the-animation\">The animation<a href=\"#the-animation\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>My brother asked for my help to build an animation of a trapezoid inscribed in a circle that kept changing his shape.\nWith <a href=\"\/blog\/til\/cyclic-quadrilateral\">a bit of maths<\/a> and the help of ChatGPT for the UI, I created the animation you can see below.\nUnder the animation you can find a control panel that allows you to tweak some animation parameters, and under that you can find a brief explanation of how the animation works.<\/p>\n<style>\n  .cyclic-trapezoid-embed {\n    --ctp-panel-bg: color-mix(in srgb, var(--bg, #0b1020) 88%, transparent);\n    --ctp-panel-border: color-mix(in srgb, var(--tx, #e5e7eb) 18%, transparent);\n    --ctp-text: var(--tx, #e5e7eb);\n    --ctp-muted: color-mix(in srgb, var(--tx, #e5e7eb) 65%, transparent);\n    --ctp-input-bg: color-mix(in srgb, var(--bg, #0b1020) 92%, black 8%);\n\n    width: 100%;\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed *,\n  .cyclic-trapezoid-embed *::before,\n  .cyclic-trapezoid-embed *::after {\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed .ctp-canvas-wrap {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed canvas {\n    display: block;\n    width: 100%;\n    height: min(70vh, 800px);\n    min-height: 360px;\n    background: var(--bg, #0b1020);\n    border-radius: 16px;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui {\n    margin-top: 16px;\n    width: 100%;\n    border-radius: 14px;\n    background: var(--ctp-panel-bg);\n    color: var(--ctp-text);\n    border: 1px solid var(--ctp-panel-border);\n    backdrop-filter: blur(8px);\n    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);\n    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n    overflow: hidden;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary {\n    list-style: none;\n    cursor: pointer;\n    padding: 14px;\n    font-size: 18px;\n    font-weight: 700;\n    user-select: none;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary::-webkit-details-marker {\n    display: none;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui-content {\n    padding: 0 14px 14px;\n  }\n\n  .cyclic-trapezoid-embed fieldset {\n    margin: 0 0 12px;\n    padding: 10px 10px 6px;\n    border-radius: 10px;\n    border: 1px solid var(--ctp-panel-border);\n    min-width: 0;\n  }\n\n  .cyclic-trapezoid-embed legend {\n    padding: 0 6px;\n    color: var(--ctp-text);\n    font-weight: 600;\n    font-size: 14px;\n  }\n\n  .cyclic-trapezoid-embed .row {\n    display: grid;\n    grid-template-columns: 1fr auto;\n    gap: 10px;\n    align-items: center;\n    margin-bottom: 8px;\n  }\n\n  .cyclic-trapezoid-embed .row label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed .row .value {\n    color: var(--ctp-muted);\n    font-size: 12px;\n    min-width: 64px;\n    text-align: right;\n    font-variant-numeric: tabular-nums;\n  }\n\n  .cyclic-trapezoid-embed .control {\n    display: grid;\n    grid-template-columns: 1fr;\n    gap: 4px;\n    margin-bottom: 10px;\n  }\n\n  .cyclic-trapezoid-embed .control label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"range\"],\n  .cyclic-trapezoid-embed input[type=\"number\"],\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed input[type=\"number\"] {\n    padding: 6px 8px;\n    border-radius: 8px;\n    border: 1px solid var(--ctp-panel-border);\n    background: var(--ctp-input-bg);\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    height: 36px;\n    padding: 0;\n    border: none;\n    background: transparent;\n    cursor: pointer;\n  }\n\n  .cyclic-trapezoid-embed .buttons {\n    display: flex;\n    gap: 8px;\n    flex-wrap: wrap;\n  }\n\n  .cyclic-trapezoid-embed button {\n    appearance: none;\n    border: 1px solid var(--ctp-panel-border);\n    background: color-mix(in srgb, var(--bg, #0b1020) 80%, var(--tx, #e5e7eb) 8%);\n    color: var(--ctp-text);\n    border-radius: 10px;\n    padding: 9px 12px;\n    cursor: pointer;\n    font-weight: 600;\n  }\n\n  .cyclic-trapezoid-embed button:hover {\n    background: color-mix(in srgb, var(--bg, #0b1020) 72%, var(--tx, #e5e7eb) 14%);\n  }\n\n  .cyclic-trapezoid-embed .hint {\n    font-size: 12px;\n    color: var(--ctp-muted);\n    line-height: 1.35;\n    margin-top: 6px;\n  }\n\n  .cyclic-trapezoid-embed code {\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n  }\n<\/style><div class=\"cyclic-trapezoid-embed\" id=\"cyclic-trapezoid-embed\">\n  <div class=\"ctp-canvas-wrap\">\n    <canvas id=\"ctp-canvas\"><\/canvas><\/div>\n\n  <details class=\"ctp-ui\"><summary class=\"ctp-summary\">Cyclic trapezoid controls<\/summary><div class=\"ctp-ui-content\">\n      <fieldset><legend>Colour<\/legend>\n\n        <div class=\"control\">\n          <label for=\"ctp-bgColor\">Background colour<\/label>\n          <input id=\"ctp-bgColor\" type=\"color\" value=\"#0b1020\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-circleColor\">Circle colour<\/label>\n          <input id=\"ctp-circleColor\" type=\"color\" value=\"#94a3b8\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-pointColor\">Points colour<\/label>\n          <input id=\"ctp-pointColor\" type=\"color\" value=\"#f8fafc\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-parallelColor\">Parallel sides colour<\/label>\n          <input id=\"ctp-parallelColor\" type=\"color\" value=\"#fbbf24\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-nonParallelColor\">Non-parallel sides colour<\/label>\n          <input id=\"ctp-nonParallelColor\" type=\"color\" value=\"#60a5fa\"><\/div>\n      <\/fieldset><fieldset><legend>Point A<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-aPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-aPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-aPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"11\"><\/fieldset><fieldset><legend>Point B<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-bMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-bMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"165\"><div class=\"row\">\n          <label for=\"ctp-bPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-bPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"7\"><div class=\"row\">\n          <label for=\"ctp-bPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-bPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\"><\/fieldset><fieldset><legend>Point D<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-dMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-dMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"90\"><div class=\"row\">\n          <label for=\"ctp-dPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-dPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"5\"><div class=\"row\">\n          <label for=\"ctp-dPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-dPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"60\"><\/fieldset><fieldset><legend>Global<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-radius\">Circle radius<\/label>\n          <div class=\"value\" id=\"ctp-radiusValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-radius\" type=\"range\" min=\"0.1\" max=\"0.48\" step=\"0.01\" value=\"0.35\"><div class=\"row\">\n          <label for=\"ctp-speed\">Global animation speed<\/label>\n          <div class=\"value\" id=\"ctp-speedValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-speed\" type=\"range\" min=\"0\" max=\"4\" step=\"0.01\" value=\"1\"><\/fieldset><div class=\"buttons\">\n        <button id=\"ctp-resetTime\" type=\"button\">Reset animation time<\/button>\n      <\/div>\n\n      <div class=\"hint\">\n        Point C is computed from the cyclic trapezoid rule:...<\/div><\/div><\/details><\/div>","summary":"See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.","date_modified":"2026-03-14T17:35:14+01:00","tags":["mathematics","geometry","visualisation","llm"],"image":"\/user\/pages\/02.blog\/cyclic-trapezoid-animation\/thumbnail.webp"},{"title":"Floodfill algorithm in Python","date_published":"2025-11-17T16:49:00+01:00","id":"https:\/\/mathspp.com\/blog\/floodfill-algorithm-in-python","url":"https:\/\/mathspp.com\/blog\/floodfill-algorithm-in-python","content_html":"<p>Learn how to implement and use the floodfill algorithm in Python.<\/p>\n\n<link rel=\"stylesheet\" href=\"https:\/\/pyscript.net\/releases\/2025.11.1\/core.css\"><script defer type=\"module\" src=\"https:\/\/pyscript.net\/releases\/2025.11.1\/core.js\"><\/script><py-script>\nimport js\n\nroot = js.document.documentElement\ncomputed = js.window.getComputedStyle(root)\n\nBG_COLOUR = computed.getPropertyValue(\"--bg\").strip()\nFG_COLOUR = computed.getPropertyValue(\"--tx\").strip()\nUI_COLOUR = computed.getPropertyValue(\"--ui\").strip()\nAC_COLOUR = computed.getPropertyValue(\"--accent\").strip()\nAC2_COLOUR = computed.getPropertyValue(\"--accent-2\").strip()\nRE_COLOUR = computed.getPropertyValue(\"--re\").strip()\nBL_COLOUR = computed.getPropertyValue(\"--bl\").strip()\nGR_COLOUR = computed.getPropertyValue(\"--gr\").strip()\nYE_COLOUR = computed.getPropertyValue(\"--ye\").strip()\nOR_COLOUR = computed.getPropertyValue(\"--or\").strip()\n\nCONTRAST = {\n    BG_COLOUR: FG_COLOUR,\n    FG_COLOUR: BG_COLOUR,\n    UI_COLOUR: FG_COLOUR,\n    AC_COLOUR: FG_COLOUR,\n    AC2_COLOUR: FG_COLOUR,\n    RE_COLOUR: FG_COLOUR,\n    BL_COLOUR: FG_COLOUR,\n    GR_COLOUR: FG_COLOUR,\n    YE_COLOUR: FG_COLOUR,\n    OR_COLOUR: FG_COLOUR,\n}\n<\/py-script><p>In this article you will learn about the floodfill algorithm.\nYou will learn the intuitive explanation of the algorithm, how it can be used to colour regions in images, and how to implement it in Python.\nYou will also see three example applications of the floodfill algorithm, with interactive demos and code.<\/p>\n<p>By the time you are finished reading this article, you will be able to apply the floodfill algorithm in your own projects and modify it or tweak it according to your needs and preferences.<\/p>\n<h2 id=\"what-is-the-floodfill-algorithm\">What is the floodfill algorithm?<a href=\"#what-is-the-floodfill-algorithm\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Click the image below to randomly colour the region you click.<\/p>\n<p>Go ahead, try it!<\/p>\n<canvas id=\"bitmap\" width=\"320\" height=\"320\" style=\"display: block; margin: 0 auto; max-width: 100%; height: auto;\"><\/canvas><script>\nfunction set_canvas_loading(canvas) {\n    let ctx = canvas.getContext(\"2d\");\n\n    \/\/ Get computed values of CSS variables\n    const styles = getComputedStyle(document.documentElement);\n    const bg = styles.getPropertyValue(\"--bg\").trim();\n    const fg = styles.getPropertyValue(\"--accent\").trim();\n\n    ctx.fillStyle = bg;\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    ctx.fillStyle = fg;\n    ctx.font = \"36px Atkinson Hyperlegible\";\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillText(\"Loading...\", canvas.width \/ 2, canvas.height \/ 2);\n}\n\nset_canvas_loading(document.getElementById(\"bitmap\"));\n<\/script><py-script>\nIMG_WIDTH = 160\nIMG_HEIGHT = 160\nPIXEL_SIZE = 2\n\nimport asyncio\nimport collections\nimport itertools\n\nfrom pyscript import display\nfrom pyodide.ffi import create_proxy\nimport js\nfrom js import fetch\n\ncanvas = js.document.getElementById(\"bitmap\")\nctx = canvas.getContext(\"2d\")\n\n_BITMAP_COLOURS = itertools.cycle([AC_COLOUR, AC2_COLOUR, RE_COLOUR, BL_COLOUR, YE_COLOUR, GR_COLOUR, OR_COLOUR])\n\n_ints = [0, 0, 0, 0, 0, 0, 0, 9903520019135137019840167936, 316912650047833978337321025536, 5069364463233662545642129457152, 40406362882311561545666757918720, 159723975628759174402796798607360, 628754697713202062365541686837248, 1216944576219100292990829487718400, 2433889152438200467762168756961280, 4543259751217974183408433589387264, 9086519502435948354150493226795008, 18173039004871896701827061989244928, 15586952552243794457451031487840256, 36366340612306816504545604379082752, 31189423920820070440268979792510976, 31224838909463946599208478310400000, 31214663042341020773348807509278720, 31295792680755627454903859026067456, 31295772873714998888819460640079872, 31295772873714998888819460640079872, 31214663042341020773208070020923392, 31214658090580863631686970424426496, 31224838909463946599067740822044672, 31191949318500212615924220889661440, 31174023946731360309543681570897920, 31158772525447364424556924360458240, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 41538374867674158118542209162674176, 41538374867674158118542209162674176, 41538374867674158118542209162674176, 1813388729527496878325760, 1813388729531894857728000, 5444517870735014810951084025942904930304, 87112285931760246042160989809567281971200, 347088014259357233337105009500967893204992, 1372018503425223884684326417278139134115840, 2613368577952807399398716985189229563609088, 5226737155905614798797433970265209426542592, 9756576024357147624421876744396907856986112, 19513152048714295248843753488680566015623168, 39026304097428590497687506977247882333241344, 78052608194857180995375013954382514968641536, 78052608194857180995375013954382514968649728, 156105216389714361990750027908651780239548416, 133804471191183738849214309636003418733572096, 312210432779428723981500055817190310781399040, 267608942382367477698428619271893587769440256, 624420865558857447963000111634267371865126912, 535217884764734955396857238543673925841197056, 535217884764734955396857238543673925841198080, 1248841731117714895926000223268421494032571392, 1070435769529469910793714477087375339473079296, 1070435769529469910793714477087375339473079296, 1070435769529469910793714477087339055589363200, 2497683462235429791852000446537115666948820480, 2497683462235429791852000446537045298204642816, 2140871539058939821587428954175243260155397632, 2140871539058939821587428954176226223550629376, 2140871539058939821587428954176243815736673792, 2140871539058939821587428954182717740201018880, 2140871539058939821587428954191192775827916288, 4995366924470859583704000893143091548121990912, 4995366924470859583704000893352579299538896640, 4995366924470859583704000897667150887862142720, 4995366924470900148523208196270458264975574784, 4995366924471184102257659318649205691078674176, 4995366924472147516713832774153879352074307328, 4995366924475889621285706507409303589789632256, 4995366924480596407964353923103877354846422784, 4995366924489042763913674615590287289608046336, 4995366924507286632895834264625967053561399040, 4995366924543708928397916780318445517146162944, 4995366924533795900704132026398741298080122624, 2140871539205541078202623227998533436869445120, 2140871539185988835344703017709848286629725696, 2140871539354576223970255702273697839319090688, 2140871539312713330548318654518670712664753664, 2140871539649563589245765596919586662022907392, 2140871539648265515031131890012454037940604416, 2497683462752063329276215795575400873427209728, 2497683462749467180846948381761135625262599168, 1070435770708121297681120348763544019020024832, 1070435770728890485115259659277666004336905216, 1248841732317135470247545405458852896384752640, 1248841732311943173389010577830322400055531520, 535217885958963232859867593105574831864158208, 535217885958963232859867593105574831864166400, 624420866753085725426010466196168277888086016, 267608943576595755161438973833794493792415744, 312210433973657001444510410379091216804372480, 133804472385412016312224664197904324756561920, 156105217583942639453760382470552686262534144, 66902236789820146887617509379959240238678016, 78052609389085458458385368516283420991782912, 39026305291656867960697861539148788356546560, 19513153242942572711854108050581472039272448, 20906949817850736658200090442621994634313728, 10453475506039507060605222502318075184414720, 5226738350133892261807788532166115481092096, 1306685483204681162709713054552146185289728, 691454963811624423185783403544767058935808, 174224436863802173805581096441418979737600, 21777936483221741569526362504672367869952, 31153781153022354500604921737379840, 31153781153626817410377051952644096, 31153781153626817410377051952644096, 31153781153626817410377051952644096, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208974706430191794651136, 31153781151209002592719084472762368, 31153781151209035487010762786865152, 31153781151209095024597836624822272, 31153781151209076577853762915270656, 31153781151209224079748758553755648, 31153781151209187195267810389393408, 31153781151209187195267810389393408, 31153781151209224088755957808496640, 36346078009744051708279254882975744, 36346078009743922653128332954042368, 15576890575604621488479174058311680, 18173039004871970415021728557170688, 9086519502435985099512434151915520, 9086519502435951809185463605919744, 4543259751217974175949346706554880, 2433889152438200452843994991296512, 1216944576219100229377484751110144, 628754697713201800030863392505856, 157188674428300515591385421709312, 39930993907189793600699199651840, 10061976639316146531601490116608, 2534063261007325117889137082368, 158456325026222832177874206720, 4951760083354544804758290432, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n\ndef parse_bitmap():\n    return [[1] * 160] + [\n        [1] + [int(c) for c in bin(i).removeprefix(\"0b\").zfill(158)] + [1]\n        for i in _ints\n    ] + [[1] * 160]\n\n'''\nasync def load_bitmap(url: str) -&gt; list[list[int]]:\n    # Fetch the text file from the URL\n    response = await fetch(url)\n    text = await response.text()\n\n    bitmap: list[list[int]] = []\n    for line in text.splitlines():\n        line = line.strip()\n        if...<\/py-script>","summary":"Learn how to implement and use the floodfill algorithm in Python.","date_modified":"2025-11-23T15:59:09+01:00","tags":["python","programming","algorithms","pyscript","visualisation","graphs"],"image":"\/user\/pages\/02.blog\/floodfill-algorithm-in-python\/thumbnail.webp"},{"title":"Animating a tree fractal","date_published":"2025-01-12T19:10:00+01:00","id":"https:\/\/mathspp.com\/blog\/animating-a-tree-fractal","url":"https:\/\/mathspp.com\/blog\/animating-a-tree-fractal","content_html":"<p>This article walks through the code used to animate a tree fractal.<\/p>\n\n<p>I really enjoy fractals and for the longest time I've been meaning to draw the classical tree fractal that starts with the main trunk which then successively splits into two branches, that themselves can be seen as the full tree.<\/p>\n<p>Today I quickly put together the code to do this and then I animated the rotation of the tree to make sure it's easy to see the self-similarity of the tree.<\/p>\n<p>This is the animated GIF that I produced:<\/p>\n<p><img alt=\"Animated GIF of a tree fractal rotating and zooming in, so that the end position is the same as the starting position, displaying the self-similarity properties of the fractal.\" src=\"\/user\/pages\/02.blog\/animating-a-tree-fractal\/_tree.gif?decoding=auto&amp;fetchpriority=auto\"><\/p>\n<h2 id=\"drawing-the-tree\">Drawing the tree<a href=\"#drawing-the-tree\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To draw the tree I wrote a recursive function (obviously!) that draws a trunk and then recursively draws the remainder of the tree.\nTo &ldquo;recursively draw the remainder of the tree&rdquo; I needed to compute the two trunks that stem from the trunk I just drew, so to do that I:<\/p>\n<ul><li>computed the direction of the trunk I just drew as a numpy vector;<\/li>\n<li>rotated this vector by 60 degrees (clockwise and counter-clockwise);<\/li>\n<li>scaled down this vector; and<\/li>\n<li>used it to compute the two new trunks.<\/li>\n<\/ul><p>The recursive function looks like this:<\/p>\n<pre><code class=\"language-py\">def tree(screen, start, to, depth=0):\n    if depth &gt;= 15:\n        return\n    sx, sy = start\n    tx, ty = to\n    pygame.draw.line(\n        screen,\n        BLACK,\n        (WIDTH \/\/ 2 + sx, HEIGHT \/\/ 2 - sy),\n        (WIDTH \/\/ 2 + tx, HEIGHT \/\/ 2 - ty),\n        1,\n    )\n    direction = np.array([[tx - sx], [ty - sy]])\n    for angle in [pi \/ 3, -pi \/ 3]:\n        rot = np.array(\n            [\n                [cos(angle), -sin(angle)],\n                [sin(angle), cos(angle)],\n            ]\n        )\n        new_direction = ((rot @ ((direction) \/ 1.7))).reshape((2,))\n        tree(screen, to, to + new_direction, depth + 1)<\/code><\/pre>\n<p>One important thing that I ended up doing, and that explains the weird calculations inside the call to <code>pygame.draw.line<\/code>, is that I decided to do all calculations as if the coordinate system were the standard one from maths class: the origin is at the centre of the pygame window and the values of the coordinate y increase by going up, not by going down.\nThen, I need to convert from this sytem to the pygame system right before drawing, which is why I add <code>(WIDTH \/\/ 2, HEIGHT \/\/ 2)<\/code> to the points we're drawing and then for the y coordinate I need to use the symmetric value (<code>-y<\/code> instead of <code>y<\/code>) to compensate for the fact that the y axis is flipped.<\/p>\n<p>The full script to draw the tree looks like this:<\/p>\n<pre><code class=\"language-py\">from math import sin, cos, pi\nimport sys\n\nimport numpy as np\nimport pygame\nimport pygame.locals\n\nWIDTH, HEIGHT = 600, 400\n\nWHITE = (255, 255, 255)\nBLACK = (0, 0, 0)\n\nscreen = pygame.display.set_mode((WIDTH, HEIGHT))\n\ndef tree(screen, start, to, depth=0):\n    ...\n\ndef main() -&gt; None:\n    screen.fill(WHITE)\n    tree(screen, np.array((0, -200)), np.array((0, 0)))\n    pygame.display.flip()\n    pygame.image.save(screen, \"tree.png\")\n\n    while True:\n        for event in pygame.event.get():\n            if event.type == pygame.locals.QUIT:\n                pygame.quit()\n                sys.exit(0)\n\nif __name__ == \"__main__\":\n    main()<\/code><\/pre>\n<h2 id=\"animating-the-tree\">Animating the tree<a href=\"#animating-the-tree\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To make it easier to animate the tree I first started by splitting the function <code>tree<\/code> in two:<\/p>\n<ol><li>a...<\/li><\/ol>","summary":"This article walks through the code used to animate a tree fractal.","date_modified":"2025-07-23T16:49:02+02:00","tags":["fractals","mathematics","programming","pygame","python","recursion","visualisation"],"image":"\/user\/pages\/02.blog\/animating-a-tree-fractal\/thumbnail.webp"},{"title":"Animating a rotating spiral","date_published":"2024-07-10T00:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/animating-a-rotating-spiral","url":"https:\/\/mathspp.com\/blog\/animating-a-rotating-spiral","content_html":"<p>With a couple of loops and a bit of maths you can create a rotating spiral.<\/p>\n\n<video width=\"400\" height=\"400\" poster=\"\/blog\/animating-a-rotating-spiral\/_rotating.mp4.thumb.png\" controls><source src=\"\/blog\/animating-a-rotating-spiral\/_rotating.mp4\" type=\"video\/mp4\">\n  A video animation of a colourful rotating spiral that keeps expanding and contracting and changing colour.\n<\/source><\/video><p>Following up on the concepts covered in my previous article <a href=\"\/blog\/animations-from-first-principles-in-5-minutes\">&ldquo;Animations from first principles in 5 minutes&rdquo;<\/a> and <a href=\"\/blog\/more-animations-from-first-principles-in-5-minutes\">&ldquo;More animations from first principles in 5 minutes&rdquo;<\/a>, in this article we will create the animation you can see above.<\/p>\n<p>We start by modifying the parametrisation of the circle to create a spiral:<\/p>\n<pre><code class=\"language-py\">SIDE = 600\n\ndef spiral(percentage):\n    return (\n        SIDE \/\/ 2\n        * percentage\n        * cos(10 * pi * percentage),\n        SIDE \/\/ 2\n        * percentage\n        * sin(10 * pi * percentage),\n    )<\/code><\/pre>\n<p>The <code>10<\/code> inside <code>cos<\/code>\/<code>sin<\/code> dictate how many turns the spiral does, all you have to do is divide that number by <code>2<\/code>, so a <code>10<\/code> means we do <code>5<\/code> turns around the centre of the spiral.<\/p>\n<p>You can &ldquo;easily&rdquo; put the spiral on the screen:<\/p>\n<pre><code class=\"language-py\">from itertools import product\nfrom math import sin, cos, pi\n\nimport pygame\n\nSIDE = 600\nWHITE = (255, 255, 255)\nBLACK = (0, 0, 0)\n\nscreen = pygame.display.set_mode((SIDE, SIDE))\nscreen.fill(WHITE)\n\ndef draw_pixel(screen, x, y, colour):\n    x, y = round(x), round(y)\n    for dx, dy in product(range(-1, 2), repeat=2):\n        screen.set_at((x + dx, y + dy), colour)\n\ndef spiral(percentage):\n    return (\n        SIDE \/\/ 2 * percentage * cos(10 * pi * percentage),\n        SIDE \/\/ 2 * percentage * sin(10 * pi * percentage),\n    )\n\nSTEPS = 3000\nfor step in range(STEPS + 1):\n    percentage = step \/ STEPS\n    x, y = rotating_spiral(percentage, tick \/ 10)\n    draw_pixel(screen, x, y, BLACK)\npygame.display.flip()\ninput()<\/code><\/pre>\n<p>By modifying the function <code>spiral<\/code> to accept an argument that represents time and by creating an outer loop that emulates ticking of time, we can rotate this spiral:<\/p>\n<pre><code class=\"language-py\"># ...\n\ndef spiral(percentage, time):\n    return (\n        SIDE \/\/ 2 * percentage * cos(10 * pi * percentage + time),\n        SIDE \/\/ 2 * percentage * sin(10 * pi * percentage + time),\n    )\n\n# ...\n\nSTEPS = 3000\nfor tick in count():\n    screen.fill(WHITE)\n    for step in range(STEPS + 1):\n        percentage = step \/ STEPS\n        x, y = rotating_spiral(percentage, tick \/ 10)\n        draw_pixel(screen, x, y, BLACK)\n    pygame.display.flip()<\/code><\/pre>\n<p>To make the spiral expand and contract, we must make it so that the radius has to change as time ticks:<\/p>\n<pre><code class=\"language-py\">def rotating_spiral(percentage, time):\n    return (\n        SIDE \/\/ 2\n        + (1 + sin(time) \/ 10)  # &lt;-- new\n        * percentage * (SIDE \/\/ 3) * cos(10 * pi * percentage + time),\n        SIDE \/\/ 2\n        + (1 + sin(time) \/ 10)  # &lt;-- new\n        * percentage * (SIDE \/\/ 3) * sin(10 * pi * percentage + time),\n    )<\/code><\/pre>\n<p>Finally, to add colour, we create two functions that generate the background and foreground colours for each frame instead of using the constants <code>WHITE<\/code> \/ <code>BLACK<\/code>:<\/p>\n<pre><code class=\"language-py\"># ...\n\ndef bg(time):\n    return (\n        40 + int(abs(30 * sin(0.05 * time))),\n        40 + int(abs(30 * sin(0.05 * time))),\n        40 + int(abs(30 * sin(0.05 * time))),\n    )\n\ndef...<\/code><\/pre>","summary":"With a couple of loops and a bit of maths you can create a rotating spiral.","date_modified":"2025-07-23T16:49:02+02:00","tags":["geometry","mathematics","programming","pygame","python","visualisation"],"image":"\/user\/pages\/02.blog\/animating-a-rotating-spiral\/thumbnail.webp"},{"title":"ASCII rain scroll art","date_published":"2024-05-24T12:30:00+02:00","id":"https:\/\/mathspp.com\/blog\/ascii-rain-scroll-art","url":"https:\/\/mathspp.com\/blog\/ascii-rain-scroll-art","content_html":"<p>Using ASCII characters we can create a simple rain animation in the terminal.<\/p>\n\n<p>&ldquo;Scroll art&rdquo; is a term I've heard used to refer to images or animations made out of ASCII characters.\nIn this short article we'll go over the code necessary to create this raining animation:<\/p>\n<figure class=\"image-caption\"><img title=\"Rain scroll art animation.\" alt=\"An animation of random characters falling from the screen.\" src=\"\/user\/pages\/02.blog\/ascii-rain-scroll-art\/_ascii_scroll_art_rain.gif?decoding=auto&amp;fetchpriority=auto\"><figcaption class=\"\">Rain scroll art animation.<\/figcaption><\/figure><p>This animation runs on the terminal and only requires using the built-in <code>print<\/code>.<\/p>\n<h2 id=\"coding-the-animation\">Coding the animation<a href=\"#coding-the-animation\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>An animation is a series of images (frames) shown in quick succession.\nIn this case, each frame is composed of a series of printed lines in a loop, so it's the way in which we manage the lines that gives the impression of an animation.<\/p>\n<p>We will print some lines to fill the screen, we will pause for a fraction of a second, and then we will repeat this process.<\/p>\n<p>We will use a <code>deque<\/code> to hold all the lines that will fill the screen.\nTo create the animation of falling rain, for each frame we need to create a new line that gets printed at the top of the screen and we need to get rid of the line that was printed at the bottom of the screen in the previous frame.\nA <code>deque<\/code> is a good data structure for this because we can use <a href=\"\/blog\/python-deque-tutorial#how-to-create-a-deque\">the parameter <code>maxlen<\/code><\/a> to do this management automatically.<\/p>\n<p>So, our animation code starts like this:<\/p>\n<pre><code class=\"language-py\">from collections import deque\nimport shutil\n\nTERMINAL_WIDTH, TERMINAL_HEIGHT = shutil.get_terminal_size()\nRAIN_DENSITY = 0.05\nEMPTY_LINE = \" \" * TERMINAL_WIDTH\n\nlines = deque([EMPTY_LINE for _ in range(TERMINAL_HEIGHT)], maxlen=TERMINAL_HEIGHT)<\/code><\/pre>\n<p>At this point, if we print all of the lines inside <code>lines<\/code>, we clear the screen of the terminal.<\/p>\n<p>What we will do next is write a short function <code>build_next_line<\/code> that accepts a line of the animation and creates a new random line that we can print at the top of the animation.\nThis happens in two passes:<\/p>\n<ol><li>we put tails <code>|<\/code> on top of the drops <code>v<\/code> from the previous line; and<\/li>\n<li>we add drops <code>v<\/code> in some random positions.<\/li>\n<\/ol><p>Suppose the previous animation had this top line:<\/p>\n<pre><code>  |  vv  |v<\/code><\/pre>\n<p>In the first pass, we create this line:<\/p>\n<pre><code>     ||   |\n  |  vv  |v<\/code><\/pre>\n<p>In the second pass, we add random drops (which might replace a previous tail):<\/p>\n<pre><code>vv   ||   v\n  |  vv  |v<\/code><\/pre>\n<p>The function <code>build_next_line<\/code> looks like this:<\/p>\n<pre><code class=\"language-py\">import random\n\ndef build_next_line(source):\n    next_row = [\"|\" if char == \"v\" else \" \" for char in source]\n    for idx, _ in enumerate(next_row):\n        if random.random() &lt; RAIN_DENSITY:\n            next_row[idx] = \"v\"\n    return \"\".join(next_row)<\/code><\/pre>\n<p>To run the animation, we need an infinite loop!\nAt each step, we build a new line with the function <code>build_next_line<\/code> and append it to the left of the <code>deque<\/code> (and its <code>maxlen<\/code> parameter will make sure that the oldest line gets thrown away automatically).\nThen, we print all of the lines to the screen and pause the program for a bit:<\/p>\n<pre><code class=\"language-py\">from time import sleep\n\nwhile True:\n    new_row = build_next_line(lines[0])\n    lines.appendleft(new_row)\n    for row in lines:\n        print(row)\n    sleep(0.05)<\/code><\/pre>\n<h2 id=\"full-code\">Full code<\/h2>...","summary":"Using ASCII characters we can create a simple rain animation in the terminal.","date_modified":"2025-07-23T16:49:02+02:00","tags":["programming","python","scroll art","visualisation"],"image":"\/user\/pages\/02.blog\/ascii-rain-scroll-art\/thumbnail.webp"},{"title":"(More) Animations from first principles","date_published":"2023-09-08T12:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/more-animations-from-first-principles-in-5-minutes","url":"https:\/\/mathspp.com\/blog\/more-animations-from-first-principles-in-5-minutes","content_html":"<p>Create a zooming animation from first principles in Python. In 5 minutes. Kind of.<\/p>\n\n<video width=\"400\" height=\"400\" poster=\"\/blog\/more-animations-from-first-principles-in-5-minutes\/_zoom_triangle.mp4.thumb.png\" controls><source src=\"\/blog\/more-animations-from-first-principles-in-5-minutes\/_zoom_triangle.mp4\" type=\"video\/mp4\">\n  You can't watch the video animation of a recursive triangular structure, zooming in and rotating, animated with a Python and pygame script.\n<\/source><\/video><p>This is a follow-up to my recent article called <a href=\"\/blog\/animations-from-first-principles-in-5-minutes\">&ldquo;Animations from first principles (in 5 minutes)&rdquo;<\/a>.\nIn that article, we created a simple animation with Python and pygame.<\/p>\n<p>In <em>this<\/em> article, we will create a different type of animation, where we will draw a recursive triangular structure that can be zoomed in indefinitely.\nWe'll be using Python and <code>pygame<\/code>, so make sure you install it in your preferred way, and make sure it was installed properly:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; import pygame\npygame 2.5.1 (SDL 2.28.2, Python 3.11.4)\nHello from the pygame community. https:\/\/www.pygame.org\/contribute.html<\/code><\/pre>\n<p>The code in this article was written with Python 3.11 and <code>pygame<\/code> 2.5.1.\nHowever, it should run well on older versions of Python\/<code>pygame<\/code> because we're only using fundamental features that have been around for a while and that are unlikely to go anywhere.<\/p>\n<h2 id=\"in-5-minutes\">In 5 minutes?<a href=\"#in-5-minutes\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I have no idea if you can go through this in 5 minutes.\nYou'll likely need more time if these ideas are new to you and you're typing the code yourself.\nHowever, I did present most of this material in 5 minutes in <a href=\"\/talks\">a lightning talk at PyCon Portugal<\/a>.<\/p>\n<h2 id=\"i-ll-cheat-a-bit\">I'll cheat a bit<a href=\"#i-ll-cheat-a-bit\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>For this demo, I'll cheat a bit and I will add a little bit of boilerplate for free.\nThis is a class <code>Point<\/code> that I'm using to represent 2D points.\nThe idea is to make it easier to add and subtract points, and to multiply and divide points by numbers.<\/p>\n<p>The class <code>Point<\/code> looks like this:<\/p>\n<pre><code class=\"language-py\">class Point:\n    def __init__(self, x, y):\n        self.values = (x, y)\n\n    def __add__(self, other):\n        x, y = self.values\n        x_, y_ = other.values\n        return Point(x + x_, y + y_)\n\n    def __sub__(self, other):\n        x, y = self.values\n        x_, y_ = other.values\n        return Point(x - x_, y - y_)\n\n    def __mul__(self, other):\n        return Point(self.values[0] * other, self.values[1] * other)\n\n    __rmul__ = __mul__\n\n    def __truediv__(self, other):\n        return Point(self.values[0] \/ other, self.values[1] \/ other)\n\n    @property\n    def length(self):\n        return self.values[0] ** 2 + self.values[1] ** 2<\/code><\/pre>\n<p>It uses <a href=\"\/blog\/pydonts\/overloading-arithmetic-operators-with-dunder-methods\">arithmetic dunder methods<\/a> and a <a href=\"\/blog\/pydonts\/properties\">property<\/a>, but it is just so I can do operations like <code>Point(1, 2) + Point(3, 4)<\/code> or <code>3 * Point(1, 0)<\/code>.<\/p>\n<p>With this out of the way, let us start!<\/p>\n<h2 id=\"creating-the-canvas-to-draw-on\">Creating the canvas to draw on<a href=\"#creating-the-canvas-to-draw-on\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Just like last time, we'll start by making sure we can get a black screen in front of us:<\/p>\n<pre><code class=\"language-py\">import pygame\n\nWHITE = (255, 255, 255)\nBLACK = (0, 0, 0)\n\nscreen = pygame.display.set_mode((400, 400))\nscreen.fill(BLACK)\n\nwhile True:\n    pygame.display.flip()<\/code><\/pre>\n<p>The code above fills a 400 by 400 screen with black and then it refreshes it forever.\nIf you run this program, you'll see a black screen:<\/p>\n<figure class=\"image-caption\"><img title=\"A black screen.\" alt=\"\" src=\"\/user\/pages\/02.blog\/more-animations-from-first-principles-in-5-minutes\/_black_screen.webp\"><figcaption class=\"\">A black screen.<\/figcaption><\/figure><p>To close this window, you'll need to interrupt <em>the Python shell where you ran your program<\/em>.<\/p>\n<h2 id=\"drawing-a-triangle\">Drawing a triangle<\/h2>...","summary":"Create a zooming animation from first principles in Python. In 5 minutes. Kind of.","date_modified":"2025-07-23T16:49:02+02:00","tags":["fractals","geometry","mathematics","programming","pygame","python","recursion","visualisation"],"image":"\/user\/pages\/02.blog\/more-animations-from-first-principles-in-5-minutes\/thumbnail.webp"},{"title":"Animations from first principles","date_published":"2023-09-07T12:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/animations-from-first-principles-in-5-minutes","url":"https:\/\/mathspp.com\/blog\/animations-from-first-principles-in-5-minutes","content_html":"<p>Create animations from first principles and morph between different figures with Python. In 5 minutes. Kind of.<\/p>\n\n<video width=\"400\" height=\"400\" poster=\"\/blog\/animations-from-first-principles-in-5-minutes\/_morph.mp4.thumb.png\" controls><source src=\"\/blog\/animations-from-first-principles-in-5-minutes\/_morph.mp4\" type=\"video\/mp4\">\n  A video animation of a colourful circle morphing into a figure eight and back, animated with a Python and pygame script.\n<\/source><\/video><p>Let me guide you through creating your first animation.\nWe'll be using Python and <code>pygame<\/code>, so make sure you install it in your preferred way, and make sure it was installed properly:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; import pygame\npygame 2.5.1 (SDL 2.28.2, Python 3.11.4)\nHello from the pygame community. https:\/\/www.pygame.org\/contribute.html<\/code><\/pre>\n<p>The code in this article was written with Python 3.11 and <code>pygame<\/code> 2.5.1.\nHowever, it should run well on older versions of Python\/<code>pygame<\/code> because we're only using fundamental features that have been around for a while and that are unlikely to go anywhere.<\/p>\n<h2 id=\"in-5-minutes\">In 5 minutes?<a href=\"#in-5-minutes\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I have no idea if you can go through this in 5 minutes.\nYou surely can't if these ideas are new to you and you're typing the code yourself.\nHowever, I did present most of this material in 5 minutes in <a href=\"\/talks\">a lightning talk at PyCon Portugal<\/a>.<\/p>\n<h2 id=\"creating-the-canvas-to-draw-on\">Creating the canvas to draw on<a href=\"#creating-the-canvas-to-draw-on\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The first thing we need is a canvas to draw on, so we'll import <code>pygame<\/code>, create a small canvas, and fill it with black.\nSince we'll be drawing in white, we can also set that up already:<\/p>\n<pre><code class=\"language-py\">import pygame\n\nWHITE = (255, 255, 255)\nBLACK = (0, 0, 0)\n\nWIDTH = 400\nHEIGHT = 400\n\nscreen = pygame.display.set_mode((WIDTH, HEIGHT))\nscreen.fill(BLACK)\n\nwhile True:\n    pygame.display.flip()<\/code><\/pre>\n<p>The code above fills a 400 by 400 screen with black and then it refreshes it forever.\nIf you run this program, you'll see a black screen:<\/p>\n<figure class=\"image-caption\"><img title=\"A black screen.\" alt=\"\" src=\"\/user\/pages\/02.blog\/animations-from-first-principles-in-5-minutes\/_black_screen.webp\"><figcaption class=\"\">A black screen.<\/figcaption><\/figure><p>To close this, you'll need to interrupt <em>the Python shell where you ran your program<\/em>.<\/p>\n<h2 id=\"drawing-a-circle\">Drawing a circle<a href=\"#drawing-a-circle\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>We'll start by animating the drawing of a circle, so we need to be able to draw a circle.\nIn order to do this, we'll write a function <code>circle<\/code> that accepts a single argument <code>progress<\/code> that is a float between 0 and 1:<\/p>\n<pre><code class=\"language-py\">def circle(progress):\n    ...<\/code><\/pre>\n<p>This function <code>circle<\/code> will return a point &ndash; a tuple with two floats &ndash; that will represent a position on the screen that belongs to the circle...\nBut what position?<\/p>\n<p>Think of yourself drawing a circle.\nAs the pen moves around the piece of paper, you can think of the \"progress\" you've made in drawing the circle.\nIf you have put the pen down on the paper, you've drawn 0% of the circle, so the progress is <code>0<\/code>.<\/p>\n<p>After you've drawn half a circle, you've drawn 50% of the circle, so that's <code>progress=0.5<\/code>.\nAnd as soon as you finish the circle, when your pen has gone around the paper and it touches the line you started drawing, at that time, your progress is <code>1<\/code>.<\/p>\n<p>So, the function <code>circle<\/code> is supposed to return the position your pen was in when your drawing was at a given percentage of progress.<\/p>\n<p>If you know...<\/p>","summary":"Create animations from first principles and morph between different figures with Python. In 5 minutes. Kind of.","date_modified":"2025-07-23T16:49:02+02:00","tags":["geometry","mathematics","programming","pygame","python","visualisation"],"image":"\/user\/pages\/02.blog\/animations-from-first-principles-in-5-minutes\/thumbnail.webp"},{"title":"TIL #080 \u2013 how to draw a B\u00e9zier curve","date_published":"2023-09-04T00:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/how-to-draw-a-bezier-curve","url":"https:\/\/mathspp.com\/blog\/til\/how-to-draw-a-bezier-curve","content_html":"<p>Today I learned how to draw a B&eacute;zier curve with the De Casteljau's algorithm.<\/p>\n\n<figure class=\"image-caption\"><img title=\"An animated B&eacute;zier curve.\" alt=\"A GIF animation of a B&eacute;zier curve being drawn in a Python program that uses pygame to draw the curve and provide user interaction.\" src=\"\/user\/pages\/02.blog\/04.til\/080.how-to-draw-a-bezier-curve\/_bezier.gif?decoding=auto&amp;fetchpriority=auto\"><figcaption class=\"\">An animated B&eacute;zier curve.<\/figcaption><\/figure><h2 id=\"how-to-draw-a-bezier-curve\">How to draw a B&eacute;zier curve<a href=\"#how-to-draw-a-bezier-curve\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Today I learned that there is a straightforward way to draw B&eacute;zier curves and I wrote a small Python program to test this out.<\/p>\n<p>The code uses <code>pygame<\/code> and it creates a window with the two endpoints for the curve and a control point (so, we're drawing a quadratic B&eacute;zier curve).<\/p>\n<p>The animation above shows the program running and the code has been included below.<\/p>\n<p>The main part of that program is the little bit of maths that actually computes the points of the curve itself.\nFor a given <span class=\"mathjax mathjax--inline\">\\(t \\in [0, 1]\\)<\/span>, if <span class=\"mathjax mathjax--inline\">\\(s\\)<\/span>, <span class=\"mathjax mathjax--inline\">\\(e\\)<\/span>, and <span class=\"mathjax mathjax--inline\">\\(c\\)<\/span> and the start, end, and control points, respectively, then the point <span class=\"mathjax mathjax--inline\">\\(p(t)\\)<\/span> of the curve is<\/p>\n<p class=\"mathjax mathjax--block\">\\[\n\\begin{cases}\np_1(t) = s + (c - s) \\times t \\\\\np_2(t) = c + (e - c) \\times t \\\\\np(t) = p_1(t) + (p_2(t) - p_1(t)) \\times t\n\\end{cases}\\]<\/p>\n<p>This formula is the application of the De Casteljau's algorithm to quadratic B&eacute;zier curves.<\/p>\n<p>What the code does, inside the function <code>draw_curve<\/code>, is use a <code>for<\/code> loop to sample multiple values of <code>t<\/code> to approximate the curve by drawing a bunch of points.<\/p>\n<p>The code below ran on Python 3.11 and pygame 2.5.1.\nIt shouldn't have major compatibility issues with other version of Python\/pygame, though, as I'm only using fairly basic functionality.<\/p>\n<pre><code class=\"language-py\">import dataclasses\nfrom dataclasses import dataclass\nimport sys\n\nimport pygame\nimport pygame.locals\n\nWIDTH = 800\nHEIGHT = 600\nENDPOINT_COLOUR = (248, 248, 242)\nBACKGROUND = (40, 42, 54)\nCONTROL_POINT_COLOUR = (255, 85, 85)\nCURVE_COLOUR = (189, 147, 249)\nGUIDELINE_COLOUR = (139, 233, 253)\nPOINT_RADIUS = 10\n\nscreen = pygame.display.set_mode((WIDTH, HEIGHT))\nscreen.fill(BACKGROUND)\n\n@dataclass\nclass Curve:\n    start: tuple[int, int]\n    end: tuple[int, int]\n    control_point: tuple[int, int]\n\ndef dist_squared(p1, p2):\n    x1, y1 = p1\n    x2, y2 = p2\n    return (x2 - x1) ** 2 + (y2 - y1) ** 2\n\ndef draw_endpoint(screen, point):\n    pygame.draw.circle(\n        screen,\n        ENDPOINT_COLOUR,\n        point,\n        radius=POINT_RADIUS,\n    )\n\ndef draw_control_point(screen, point):\n    pygame.draw.circle(\n        screen,\n        CONTROL_POINT_COLOUR,\n        point,\n        radius=POINT_RADIUS,\n    )\n\ndef draw_curve(screen, curve):\n    pygame.draw.line(screen, GUIDELINE_COLOUR, curve.start, curve.control_point)\n    pygame.draw.line(screen, GUIDELINE_COLOUR, curve.end, curve.control_point)\n\n    sx, sy = curve.start\n    ex, ey = curve.end\n    cx, cy = curve.control_point\n\n    iters = 1000\n    for iter in range(0, iters + 1):\n        delta = iter \/ iters\n        m1x = sx + (cx - sx) * delta\n        m2x = cx + (ex - cx) * delta\n        m1y = sy + (cy - sy) * delta\n        m2y = cy + (ey - cy) * delta\n        px = m1x + (m2x - m1x) * delta\n        py = m1y + (m2y - m1y) * delta\n        pygame.draw.circle(screen, CURVE_COLOUR, (px, py), radius=1)\n\n    draw_endpoint(screen, curve.start)\n    draw_endpoint(screen, curve.end)\n    draw_control_point(screen, curve.control_point)\n\nprevious_curve = None\ncurve = Curve((100, 100), (WIDTH - 100, 100), (WIDTH \/\/ 2, HEIGHT \/\/ 2))\ndragging = False\npoint_being_dragged = None\n\nprint(\"Click a point to &ldquo;pick it up&rdquo; and click again to drop it.\")\n\nwhile True:\n    for event in pygame.event.get():\n        if (\n            event.type == pygame.locals.QUIT...<\/code><\/pre>","summary":"Today I learned how to draw a B\u00e9zier curve with the De Casteljau&#039;s algorithm.","date_modified":"2025-07-23T16:49:02+02:00","tags":["mathematics","programming","pygame","python","visualisation"],"image":"\/user\/pages\/02.blog\/04.til\/080.how-to-draw-a-bezier-curve\/thumbnail.webp"},{"title":"TIL #032 \u2013 t-SNE for dimensionality reduction","date_published":"2022-02-19T00:00:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/032","url":"https:\/\/mathspp.com\/blog\/til\/032","content_html":"<p>Today I learned about t-SNE for dimensionality reduction.<\/p>\n\n<h2 id=\"t-sne\">t-SNE<a href=\"#t-sne\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>t-SNE (t-distributed Stochastic Neighbourhood Embedding) is a nice statistical method\nthat can be used to visualise high-dimensional data in a smaller number of dimensions.<\/p>\n<p>I guess I could say I learnt about this method in the context of my MSc thesis,\nand even though I only used it once yet,\nI found the results to be absolutely great!<\/p>\n<p>I don't want to give too much detail\n(and I don't think I could, if I wanted...)\nbut I was working with a space of 24 dimensions.\n(If there are people who struggle with doing 3D visualisation in their heads,\nimagine 24D!)\nDespite the fact that the data had 24 dimensions,\nI knew (from the context) that there were two main groups of datapoints,\nwith one of them accounting for roughly 25% of the data.<\/p>\n<p>I applied t-SNE to that data,\nroughly 10.000 datapoints of that 24D space,\nand the results I got looked really great, to be honest.\nBelow, you can see the result I got with minimal effort,\nand you can even see that it does a pretty decent job of separating the two groups of data:<\/p>\n<figure class=\"image-caption\"><img title=\"2D projection of a 24D space.\" alt=\"A 2D visualisation of the data that t-SNE projects in the lower dimensional space.\" src=\"\/images\/0\/d\/f\/9\/e\/0df9edbed2c7e355bd42468fbd3c124516edf719-thumbnail.webp\"><figcaption class=\"\">2D projection of a 24D space.<\/figcaption><\/figure>\n<p>I was really impressed by the results,\nand I was also very curious about how hard it would be to implement this method.\nFrom the paper referenced below,\nthe method doesn't seem hard to implement,\nso I might give it a go...<\/p>\n<p>(By the way, if you are into Python, you can <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.manifold.TSNE.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">use t-SNE with the package <code>sklearn<\/code><\/a>.<\/p>\n<p>That's it for now! <a href=\"\/subscribe\">Stay tuned<\/a> and I'll see you around!<\/p>","summary":"Today I learned about t-SNE for dimensionality reduction.","date_modified":"2025-07-23T16:49:02+02:00","tags":["mathematics","visualisation"],"image":"\/user\/pages\/02.blog\/04.til\/032.tsne-for-dimensionality-reduction\/thumbnail.webp"},{"title":"Studying the &quot;24 Game&quot;","date_published":"2020-07-12T00:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/24-game","url":"https:\/\/mathspp.com\/blog\/24-game","content_html":"<p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/24_Game\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">24 Game<\/a> is a well-known maths game that is played with kids in school to help them master the four basic arithmetic operations. In this blog post we will study the game in depth.<\/p>\n\n<h3 id=\"the-game\">The game<a href=\"#the-game\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The \"24 Game\" is a simple game. You are given four numbers between <span class=\"mathjax mathjax--inline\">\\(1\\)<\/span> and <span class=\"mathjax mathjax--inline\">\\(9\\)<\/span> (for example <span class=\"mathjax mathjax--inline\">\\(\\{1, 2, 3, 4\\}\\)<\/span>) and your objective is to find an expression that evaluates to <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span>. The rules are fairly simple:<\/p>\n<ul><li>each given number must be used exactly once;<\/li>\n<li>the only operations available are addition, subtraction, multiplication and division;<\/li>\n<li>operations may be used repeatedly or not at all;<\/li>\n<li>operation precedence can be manipulated by the use of parentheses;<\/li>\n<li>no \"clever tricks\" should be used, only simple arithmetic.<\/li>\n<\/ul><h4 id=\"examples\">Examples<a href=\"#examples\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h4>\n<p>If the given numbers are <span class=\"mathjax mathjax--inline\">\\(\\{1, 2, 3, 4\\}\\)<\/span>, an answer could be<\/p>\n<ul><li><span class=\"mathjax mathjax--inline\">\\(1 \\times 2 \\times 3 \\times 4\\)<\/span>.<\/li>\n<\/ul><p>If the given numbers are <span class=\"mathjax mathjax--inline\">\\(\\{2, 5, 7, 8\\}\\)<\/span>, an answer could be<\/p>\n<ul><li><span class=\"mathjax mathjax--inline\">\\((2\\times 5 - 7)\\times 8\\)<\/span>.<\/li>\n<\/ul><h3 id=\"the-motivation\">The motivation<a href=\"#the-motivation\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>I was talking to a friend who had just challenged me to make <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> out of <span class=\"mathjax mathjax--inline\">\\(\\{3, 3, 8, 8\\}\\)<\/span> (<a href=\"\/blog\/problems\/make-24-with-3-3-8-8\">give it a try yourself<\/a>) and as we talked about the game, we asked each other \"Why is <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> the target value? Is <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> special in any way?\".<\/p>\n<p>I had already written a computer program that solves instances of the game so we decided we could use said program to find out neat things about the game itself.<\/p>\n<div class=\"notices blue\">\n<p>To make my life easier writing this blog post, let's agree that when I talk about <em>input<\/em> I mean the numbers you have to use to make <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> and when I talk about the <em>target<\/em> I mean the number you are trying to make, which is <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> in the standard game.<\/p>\n<\/div>\n<h3 id=\"the-code\">The code<a href=\"#the-code\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The questions I will be asking in this blog post were answered with the help of some programming I did in <a href=\"\/blog\/lsbasi-apl-part1\">APL<\/a>. I defined a couple of useful functions that you can find at the end of this post; throughout the blog post I will be using those functions to answer the questions I will present.<\/p>\n<p>The first question me and my friend asked was<\/p>\n<h4 id=\"does-24-work-for-any-input\">Does <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> work for any input?<a href=\"#does-24-work-for-any-input\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h4>\n<p>As far as I know, the game is usually played by giving four distinct numbers as input, so <span class=\"mathjax mathjax--inline\">\\(\\{2, 3, 4, 5\\}\\)<\/span> is a valid input but <span class=\"mathjax mathjax--inline\">\\(\\{3, 3, 4, 5\\}\\)<\/span> is not.<\/p>\n<p>Defining these types of inputs in APL is rather easy (note that I have <code>&#9109;IO &larr; 0<\/code>) and counting them is even easier:<\/p>\n<pre><code class=\"language-apl\">      uinps &larr; all\/&#9064; {&and;\/2&lt;\/&#9077;}&uml; all &larr; 1+,&#9075;4&#9076;9  &#9053; inputs with unique digits\n      &#9109;&larr; &#8802;uinps \n126\n      nuinps &larr; all\/&#9064; {&and;\/2&le;\/&#9077;}&uml; all  &#9053; digits may repeat\n      &#9109;&larr; &#8802;nuinps\n495<\/code><\/pre>\n<p>Turns out that <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> does <em>not<\/em> work for any input. In fact, out of the <span class=\"mathjax mathjax--inline\">\\(126\\)<\/span> valid inputs, <span class=\"mathjax mathjax--inline\">\\(24\\)<\/span> only works for <span class=\"mathjax mathjax--inline\">\\(124\\)<\/span> of them, failing for<\/p>\n<ul><li><span class=\"mathjax mathjax--inline\">\\(\\{1, 6, 7, 8\\}\\)<\/span><\/li>\n<li><span class=\"mathjax mathjax--inline\">\\(\\{3, 4, 6, 7\\}\\)<\/span><\/li>\n<\/ul><p>You can check this fact by trying...<\/p>","summary":"The &quot;24 game&quot; is a well-known maths game for kids and in this post we study it in depth.","date_modified":"2024-08-14T18:52:06+02:00","tags":["mathematics","programming","apl","visualisation","arithmetic"],"image":"\/user\/pages\/02.blog\/24-game\/non_unique_100.webp"}]}
