https://penrose.cs.cmu.edu/blog/bloom Skip to content [favicon]Penrose SearchK Main Navigation ExamplesContributeLearnDocsBloomBlogTeamEditorJoin Appearance Menu Return to top Sidebar Navigation September 2024 Bloom: Optimization-Driven Interactive Diagramming August 2023 Tailoring Penrose domains to your needs July 2023 Announcing Penrose 3.0 June 2023 Diagram Layout in Stages What Have We Done to the Languages? Switching to Wasm for 10x Speedup On this page This page is best viewed on a desktop browser. Bloom: Optimization-Driven Interactive Diagramming [gri]@griffinteller [wod]@wodeni [sam]@samestep [jos]@joshsunshine We're excited to announce Bloom, an open-source JavaScript library for optimization-driven interactive diagram creation. Bloom makes it simple to describe complex, dynamic behavior using a rich vocabulary of optimization constraints and the declarative language behind Penrose. We aim to facilitate the creation of engaging, explorable explanations with a straightforward but powerful framework. Let's check out some examples! Examples Below, we've placed some balls in a circle. Try dragging one around to see how the others respond: Notice how the circles push each other around? With Bloom, all you need to do is specify that the circles shouldn't overlap: ts forall({ c1: Circle, c2: Circle }, ({ c1, c2 }) => { ensure(constraints.disjoint(c1.icon, c2.icon)); }); The next diagram reflects a ray off a mirror from one point to another. To be physically realistic, the angle between the incoming ray and mirror must be the same as the angle between the outgoing ray and the mirror. Try dragging the start and endpoints around, and watch how this property is maintained: How might you implement this? With Bloom, there's no need to calculate the exact point at which a reflected ray keeps these two angles equal. Instead, you can leave it to Bloom's optimizer: ts const r1y = ray1.normVec[1]; const r2y = ray2.normVec[1]; ensure(constraints.equal(r1y, mul(-1, r2y))); Bloom also provides an interface for your diagrams to communicate with the rest of your site. In the next diagram, you'll find: * two vectors, a1a_1a 1 and a2a_2a 2 * a point v1v_1v 1 * a point Av1Av_1Av 1 , connected to v1v_1v 1 by a dotted line Try dragging the gray handles to see how the transformation changes, along with the vectors and matrices on the right. If you're familiar with a little linear algebra, you'll notice that the vectors a1a_1a 1 and a2a_2a 2 form the columns of matrix AAA, which is applied to v1v_1v 1 to from Av1Av_1Av 1 . The eigenspaces of AAA are shown as lines s1s_1s 1 and s2s_2s 2 . All of this data, calculated and stored internal to the diagram, is easily synced to the LaTeX on the right using Bloom's system of shared diagram values and custom hooks. Here's another example, integrating a button to add a square on every click: Optimization-Driven Diagramming We believe that the most natural way to express complex interactive behavior is through optimization. Let's take another look at our first example: The elements of this diagram include: * A circular enclosure * A set of 10 draggable circles, which cannot overlap and cannot exit the enclosure As we saw before, ensuring that the circles do not overlap is as simple as specifying a single optimization constraint for each pair of circles: ts forall({ c1: Circle, c2: Circle }, ({ c1, c2 }) => { ensure(constraints.disjoint(c1.icon, c2.icon)); }); Without optimization, implementing this behavior would be a nasty challenge. At minimum, you'd need to: * Set up event handlers to translate circles on drag * Detect collisions between circles * Implement a physics engine to resolve collisions continuously Moreover, the simplicity of the constraint-based approach allows for easy modification. Suppose we instead wanted for these balls to repel each other within a certain padding: ts forall({ c1: Circle, c2: Circle }, ({ c1, c2 }) => { ensure(constraints.disjoint(c1.icon, c2.icon, 20)); }); Or even that they should all touch the enclosure: ts forall({ c: Circle }, ({ c }) => { ensure( constraints.equal( ops.vnorm(c.icon.center), sub(enclosure.icon.r, circleRad), ), ); }); forall({ c1: Circle, c2: Circle }, ({ c1, c2 }) => { ensure(constraints.disjoint(c1.icon, c2.icon, 20)); }); Neither change requires a substantial rewrite of the diagram's behavior; instead, we can simply modify the constraints to reflect our new requirements. Getting Started Bloom is still in the early stages of development, but we're excited to share it with you. If you're interested in learning more, you can take a look at our tutorial. We're excited to see what you build! Edit this page Pager Next pageTailoring Penrose domains to your needs Released under the MIT License. Copyright (c) 2017-present Penrose contributors