<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="293728" data-post-id="293728">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It turns out it’s easy to “leak data” from Typst by raising an error on purpose and parsing the error message. With that, it’s trivial to get the dimensions of text nodes (or anything else you need)</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="293728" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-293728" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="293728"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="293751" data-post-id="293751">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>In case someone wants to be able to query element dimensions from Typst, I’m doing something like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Playfair.Typst.Measuring do
  alias Playfair.Typst.TypstAst
  alias Playfair.Typst.Serializer
  alias Playfair.Length

  def measure(elements) do
    items = Enum.map(elements, fn element -&gt; {element.id, element} end)
    dictionary = TypstAst.dictionary(items)
    serialized_dictionary = Serializer.serialize(dictionary)

    # Insert the serialized plot into a template
    typst_file = """
    #let elements = #{serialized_dictionary}

    #style(styles =&gt; {
      let sizes = ();
      for (id, element) in elements {
        let size = measure(element, styles)
        let line = (
          id,
          ":",
          repr(size.width),
          ":",
          repr(size.height)
        ).join()

        sizes.push(line)
      }

      assert(0 == 1, message: sizes.join("\\n"))

      [Unreachable]
    })
    """

    # Try to render the typst code into PDF
    # Typst will return an error
    {:error, output} = ExTypst.render_to_pdf(typst_file)
    [_ignore, data] = String.split(output, "assertion failed: ")

    sizes =
      data
      |&gt; String.split("\n")
      |&gt; Enum.map(fn line -&gt;
          [id, width, height] = String.split(line, ":")
          {id, {parse_length(width), parse_length(height)}}
        end)
      |&gt; Enum.into(%{})

    Enum.map(elements, fn element -&gt;
      {width, height} = Map.fetch!(sizes, element.id)
      %{element | width: width, height: height}
    end)
  end

  defp parse_length(text) do
    {float, ""} =
      text
      |&gt; String.trim("pt")
      |&gt; Float.parse()

    Length.pt(float)
  end
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="293751" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-293751" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="293751"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="298571" data-post-id="298571">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>New version: <a href="https://github.com/tmbb/quartz" class="inline-onebox" rel="noopener nofollow ugc">GitHub - tmbb/quartz: Plotting for Elixir using Typst · GitHub</a></p>
<p>I’ve scraped pretty much the entire project and started over. The project is now called Quartz becuase it’s similar to the vaporware (?) Basalt python library described here. I thought that quartz is a nice name because it haZ all da Qool LetterZ and also because I initially tried to make it plotting-agnostic and something one could use just to draw figures. It turns out that supporting non-trivial plots actually required the whole thing to be plot-oriented, but now it’s too inconvenient to change the name.</p>
<p>The basic idea is that Quartz converts your instructions on how to draw a plot into linear progamming constraints, which it then solves using my Dantzig library (very unstable, and only supports Linux at the moment). Dantzig uses an open source linear programming solver which is bundled as a binary in the source.</p>
<p>Unfortunately, once part of your program is based on constraint solving, then all your program must be based on constraint solving, which means that there is no compatibility between Quartz and Playfair. I believe that constraint solving is probably more extensible and even composable, but it’s definitely a bit harder to implement than the naïve version.</p>
<p>One of the good parts is that although I still use typst as a rendering engine, I now perform all layout calculations in Elixir based on the text dimensions returned by Typst (determining text layout is impossible in Elixir, but actually very easy in Typst). You’re not meant to be able to query typst for object dimensions, but it’s very easy to do by intentionally raising an exception inside typst and then parse the error logs. The fact that just before generating the typst code you have access to all object dimensions might be useful in the futuren for some “post processing” just before sending the output to typst.</p>
<p>Because it was so hard to reimplement everything in constraint solving, I can’t actually show a full plot yet, but I can show the outside decoratios of a plot (the actual data would go into the central square delimited by the solid lines. The dotted lines are the boundaries of the canvases into which Quartz divides the image.</p>
<p>A very preliminary example of two plots side by side, with proportions specificed by the user:</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/d/f/df71effeb80d22f6477a91f113d6e2383b43d075.png" data-download-href="https://forum.elixirforum.com/uploads/default/df71effeb80d22f6477a91f113d6e2383b43d075" title="image" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/optimized/3X/d/f/df71effeb80d22f6477a91f113d6e2383b43d075_2_690x283.png" alt="image" data-base62-sha1="vSGAjXak3nOxaieOj9IZdifAZxP" width="690" height="283" srcset="https://forum.elixirforum.com/uploads/default/optimized/3X/d/f/df71effeb80d22f6477a91f113d6e2383b43d075_2_690x283.png, https://forum.elixirforum.com/uploads/default/original/3X/d/f/df71effeb80d22f6477a91f113d6e2383b43d075.png 1.5x, https://forum.elixirforum.com/uploads/default/original/3X/d/f/df71effeb80d22f6477a91f113d6e2383b43d075.png 2x" data-dominant-color="F9F9F9"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">1004×412 30.6 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p>The source code thath generates the figure above:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def example() do
    use Dantzig.Polynomial.Operators
    alias Quartz.Figure
    alias Quartz.Plot2D
    alias Quartz.Length

    figure =
      Figure.new([width: Length.cm(16), height: Length.cm(6)], fn fig -&gt;
        figure_width = fig.width

        _plot_task_A =
          Plot2D.new(id: "plot_task_A", left: 0.0, right: 0.55 * figure_width)
          # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
          |&gt; Plot2D.put_title("A. Task A")
          |&gt; Plot2D.put_axis_label("y", "Y-label")
          |&gt; Plot2D.put_axis_label("x2", "X2-label")
          |&gt; Plot2D.put_axis_label("x", "X-label without $math$")
          |&gt; Plot2D.finalize()

        _plot_task_B =
          Plot2D.new(id: "plot_task_B", left: 0.55 * figure_width + Length.pt(8), right: figure_width)
          # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
          |&gt; Plot2D.put_title("B. Task B")
          |&gt; Plot2D.put_axis_label("y", "Y-label")
          |&gt; Plot2D.put_axis_label("x2", "X2-label")
          |&gt; Plot2D.put_axis_label("x", "X-label (with  math: $x^2 + y^2$)", text: [escape: false])
          |&gt; Plot2D.finalize()
      end)

    Figure.render_to_pdf!(figure, "example.pdf")
  end
</code></pre>
<p>Unlike Playfair, in which almost everything is pure and referentially transparent, with very little use of the process dictionary, Quartz is an imperative monster which invisibly builds a linear program behind your back inside the process dictionary. It does try to hide this fact very well. As a user, you never need to know that anything is happening inside the process dictionary. It does make it very hard to generate plots in parallel, though (but it’s definitely something I could address in the future).</p>
<h2><a name="p-298571-plans-for-the-future-1" class="anchor" href="#p-298571-plans-for-the-future-1" aria-label="Heading link" rel="nofollow"></a>Plans for the future</h2>
<p>The fact that Quartz has access to a linear programming solver means I can have more advanced layouts almost for free, and it makes it very easy to deal with variables whose value is substitued later (it’s basically lazy evaluation implemented on top of a linear programming solver). The main problem is that Quartz may raise an error if you tell it to draw something impossible. Supposedly, this can only be triggered by the user if the user tries to draw a figure which is too small for the fixed-size elements it contains.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="298571" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-298571" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="298571"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="298587" data-post-id="298587">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>For those who might be interested, Quartz now supports multiple axes per location (bottom, top, right or left). It just draws the axes (which are simply a line with a label, I can’t yet add any data or even axis ticks). You can see an example with some “cursed” units.</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/5/f/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790.png" data-download-href="https://forum.elixirforum.com/uploads/default/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790" title="image" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/optimized/3X/5/f/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790_2_690x282.png" alt="image" data-base62-sha1="dEGOAC9jk8jKWi42CbP9SRzJtAY" width="690" height="282" srcset="https://forum.elixirforum.com/uploads/default/optimized/3X/5/f/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790_2_690x282.png, https://forum.elixirforum.com/uploads/default/optimized/3X/5/f/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790_2_1035x423.png 1.5x, https://forum.elixirforum.com/uploads/default/original/3X/5/f/5fb5c703b6f5dedbe0c24fe47bce299a32d4a790.png 2x" data-dominant-color="F7F7F7"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">1266×518 35.6 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p>The code that generates that plot is very similar to the code above, it just adds some extra drawings to the plot:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">    figure =
      Figure.new([width: Length.cm(16), height: Length.cm(6), debug: false], fn fig -&gt;
        figure_width = fig.width

        _plot_task_A =
          Plot2D.new(id: "plot_task_A", left: 0.0, right: 0.55 * figure_width)
          |&gt; Plot2D.add_bottom_axis("x3")
          |&gt; Plot2D.add_bottom_axis("x4")
          # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
          |&gt; Plot2D.put_title("A. Task A")
          |&gt; Plot2D.put_axis_label("y", "Y-label")
          |&gt; Plot2D.put_axis_label("x", "X.A axis label (mg/m#super([-2]))", text: [escape: false])
          |&gt; Plot2D.put_axis_label("x3", "X.B axis label (Kg$dot$s#super([-2/3]))", text: [escape: false])
          |&gt; Plot2D.put_axis_label("x4", "X.C axis label (mmol$dot$kg#super([-5/7]))", text: [escape: false])
          |&gt; Plot2D.put_axis_label("x2", "X2-label")
          |&gt; Plot2D.finalize()

        _plot_task_B =
          Plot2D.new(id: "plot_task_B", left: 0.55 * figure_width + Length.pt(8), right: figure_width)
          # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
          |&gt; Plot2D.put_title("B. Task B")
          |&gt; Plot2D.put_axis_label("y", "Y-label")
          |&gt; Plot2D.put_axis_label("y2", "Y2-label")
          |&gt; Plot2D.put_axis_label("x2", "X2-label")
          |&gt; Plot2D.put_axis_label("x", "X-label (with  math: $x^2 + y^2$)", text: [escape: false])
          |&gt; Plot2D.finalize()
      end)

    path = Path.join([__DIR__, "side_by_side_plots", "example.pdf"])
    Figure.render_to_pdf_file!(figure, path)
</code></pre>
<p>Again, I’m very happy with this because the main challenge in data visualization libraries is often not displaying the data (which is just a set of simple shapes put in the correct position) but also the “boring” parts such as axis labels, plot titles and all those things which are essential in order to have a publisheable plot.</p>
<p>The labels for the vertical axes should be rotated, but I don’t support rotated text yet</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="298587" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-298587" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="298587"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="300357" data-post-id="300357">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Ok, some reality check: the the approach I took is mathematically very elegant. Pretty much every measurement in the figure is represented by a (sometimes) multivariate polynomial, which is implemented symbolically with what I think is a pretty clever canonical representation.</p>
<p>Each polynomial is a struct containing a map from products of variables to coefficients. For example, ab + 7cd + 9 is represented by <code>%{["a", "b"] =&gt; 1, ["c","d"] =&gt; 7, [] =&gt; 9}</code>. I then implemented symbolic operations such as addition, multiplication and variable substitution on top of this representation. While building the figure, I generate constraints between degree one polynomials and feed them into the linear solver in order to get numerical values. Then, I substitute the variable values in the polynomials and turn all dimensions into nice floating point numbers.</p>
<p>One of the best parts of this is that I can get the text dimensions all at once from Typst, instead of querying it every time I want to render a text element.</p>
<p>All of this (predictably) takes a huge amount of memory when drawing lots of objects if the  and size of every element is kept as as an independent polynomial. Maybe I should create some new object types like point clouds which store the dimensions of the full cloud as polynomials and the and the dimensions of the points as floating points between 0 and 1 relator to the dimensions of the cloud.</p>
<p>The basic design is quite robust and can handle these optimisations. The only problem is that because I don’t have much control over the constraint solving process, some figures might be impossible to draw and raise an error, especially if the figure size is too small. Maybe I should tag the constraints with different levels of priority and relax them if the first attempt fails.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="300357" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-300357" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="300357"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="312336" data-post-id="312336">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Quartz (this library is no longer called playfair) can now draw line plots (by very inefficiently drawing line segments individually instead of using a normal path because I haven’t iomplemented paths yet).</p>
<p>Example here:</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/8/0/802cf48c6b943596be32d950c5d00d6c56d34961.png" data-download-href="https://forum.elixirforum.com/uploads/default/802cf48c6b943596be32d950c5d00d6c56d34961" title="image" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/original/3X/8/0/802cf48c6b943596be32d950c5d00d6c56d34961.png" alt="image" data-base62-sha1="ihTqOhb04wxRh8lmgdHd5d0M0QF" width="635" height="497"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">635×497 22.7 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p>Code:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Quartz.Benchmarks.LinePlot do
  use Dantzig.Polynomial.Operators
  require Quartz.Figure, as: Figure
  alias Quartz.Plot2D
  alias Quartz.Length

  def build_plot() do
    figure =
      Figure.new([width: Length.cm(8), height: Length.cm(6), debug: false], fn _fig -&gt;
        [[bounds]] =
          Figure.bounds_for_plots_in_grid(
            nr_of_rows: 1,
            nr_of_columns: 1,
            padding: Length.pt(16)
          )

      x = for i &lt;- 1..100, do: 0.01 * i
      y = for x_i &lt;- x, do: x_i * 0.3 + (0.05 * :rand.uniform())

      data = %{x: x, y: y}

      _plot =
        Plot2D.new(id: "plot_A")
        |&gt; Plot2D.set_bounds(bounds)
        |&gt; Plot2D.line_plot("x", "y", data)
        # Use typst to explicitly style the title and labels ――――――――――――――――――――――――――――――――
        |&gt; Plot2D.put_title("A. Line plot")
        |&gt; Plot2D.put_axis_label("y", "Prediction: $f(x)$", text: [escape: false])
        |&gt; Plot2D.put_axis_label("x", "Predictor: $x$", text: [escape: false])
        |&gt; Plot2D.finalize()
      end)

    path = Path.join([__DIR__, "line_plot", "example.pdf"])
    Figure.render_to_pdf_file!(figure, path)
  end
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="312336" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-312336" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="312336"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="313111" data-post-id="313111">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I have changes the API by making axes implicit (usually one wants to plot on the x and y axis, and specifying that everytime gets boring).</p>
<p>I show an example (which actually contains no new functionality) of plotting the KDE of the posterior probability distribution for a parameter in a simple Bayesian model. The model was fit using <a href="https://mc-stan.org/" rel="noopener nofollow ugc">Stan</a>, with bibndings provided by my <a href="https://forum.elixirforum.com/t/ulam-elixir-interface-to-the-stan-probabilistic-programming-language/6027" rel="nofollow">Ulam</a> package. Below, I show the code, which is quite involved, because I build the KDE “outside” of quartz. I will move this functionality inside Quartz so that one can simply write <code>Plot2D.plot_kde(series)</code> and quartz will handle the details (usually there isn’t much you want to do with a KDE except plotting it or using linear interpolation to learn a couple things about it). This plot showcases the use of line plots (line plots become quite smooth if you choose fine subdivisions) and the use of color to distinguish between the montecarlo chains.</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/9/1/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5.png" data-download-href="https://forum.elixirforum.com/uploads/default/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5" title="image" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/optimized/3X/9/1/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5_2_690x537.png" alt="image" data-base62-sha1="kK1KNTDXj472QErSSsBxaRN8Axn" width="690" height="537" srcset="https://forum.elixirforum.com/uploads/default/optimized/3X/9/1/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5_2_690x537.png, https://forum.elixirforum.com/uploads/default/original/3X/9/1/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5.png 1.5x, https://forum.elixirforum.com/uploads/default/original/3X/9/1/915f75b625ec8f7d9d96bdcd82128c7e6e8b9bd5.png 2x" data-dominant-color="F7F4F7"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">954×743 31 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p>This visualization is inspired by what you get with the python library <a href="https://www.arviz.org/en/latest/" rel="noopener nofollow ugc">ArViz</a>, which provides much more functionality. However, once you get deep into ArViz you start to notice that a lot of what you have to deal with is to “undo” all the clever things that ArViz does in order to organize your data the way it thinks is best. The structure I build, which is simply a “raw” dataframe containing the Stan output is also quite functional for what one usually wants to do.</p>
<p>Finally, the code:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def visualize() do
    samples = DataFrame.from_parquet!("examples/bernoulli_model/samples.parquet")

    figure_attributes = [
      width: Length.cm(8),
      height: Length.cm(6)
    ]

    colors = [
      RGB.hot_pink(0.4),
      RGB.dark_violet(0.4),
      RGB.medium_blue(0.4),
      RGB.dark_red(0.4)
    ]

    figure =
      Figure.new(figure_attributes, fn _fig -&gt;
        theta_kdes =
          for chain_id &lt;- 1..4 do
            theta = DataFrame.filter(samples, chain_id__ == ^chain_id)["theta"]
            Sandbox.kde(theta, 200)
          end

        plot =
          Plot2D.new(id: "plot_A")
          |&gt; Plot2D.put_title("A. Posterior probability for $theta$ (all 4 chains)", text: [escape: false])
          |&gt; Plot2D.put_axis_label("x", "$theta$", text: [escape: false])
          |&gt; Plot2D.put_axis_minimum_margins("x", Length.pt(10))
          |&gt; Plot2D.put_axis_minimum_margins("y", Length.pt(10))

        plot =
          Enum.zip(theta_kdes, colors)
          |&gt; Enum.reduce(plot, fn {theta_kde, color}, plot -&gt;
            x = Series.to_enum(theta_kde["x"])
            y = Series.to_enum(theta_kde["y"])

            Plot2D.line_plot(plot, x, y, style: [color: color])
          end)

        Plot2D.finalize(plot)
      end)

    path = Path.join([__DIR__, "bernoulli_model", "theta.pdf"])
    Figure.render_to_pdf_file!(figure, path)
  end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="313111" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-313111" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="313111"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="324325" data-post-id="324325">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>So, Quartz is not dead yet! I have added functionality to plot KDEs. So Quartz can now do scatter plots, line plots and KDE plots. The newer version of Quartz can’t do boxlplots or bar plots yet, but they’ll come eventually.</p>
<p>The code to generate the figure above now looks like the following:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Quartz.Benchmarks.LinePlot do
  use Dantzig.Polynomial.Operators
  require Quartz.Figure, as: Figure
  require Explorer.DataFrame, as: DataFrame

  alias Quartz.Plot2D
  alias Quartz.Length
  alias Quartz.Color.RGB


  def build_plot() do
    data_path = Path.join([__DIR__, "data", "samples.parquet"])
    samples = DataFrame.from_parquet!(data_path)

    theta_1 = DataFrame.filter(samples, chain_id__ == 1)["theta"]
    theta_2 = DataFrame.filter(samples, chain_id__ == 2)["theta"]
    theta_3 = DataFrame.filter(samples, chain_id__ == 3)["theta"]
    theta_4 = DataFrame.filter(samples, chain_id__ == 4)["theta"]

    color_1 = RGB.hot_pink(0.4)
    color_2 = RGB.dark_violet(0.4)
    color_3 = RGB.medium_blue(0.4)
    color_4 = RGB.dark_red(0.4)

    figure =
      Figure.new([width: Length.cm(8), height: Length.cm(6)], fn _fig -&gt;
        _plot =
          Plot2D.new(id: "plot_A")
          |&gt; Plot2D.kde_plot(theta_1, style: [color: color_1])
          |&gt; Plot2D.kde_plot(theta_2, style: [color: color_2])
          |&gt; Plot2D.kde_plot(theta_3, style: [color: color_3])
          |&gt; Plot2D.kde_plot(theta_4, style: [color: color_4])
          # Add some margins to the plot
          |&gt; Plot2D.put_axes_margins(Length.cm(0.25))
          # Use typst to explicitly style the title and labels
          |&gt; Plot2D.put_title("A. Probability distribution")
          |&gt; Plot2D.put_axis_label("x", "$theta$", text: [escape: false])
          |&gt; Plot2D.put_axis_label("y", "$P(theta)$", text: [escape: false])
          |&gt; Plot2D.finalize()
      end)


    path = Path.join([__DIR__, "dist_plot", "example.pdf"])
    Figure.render_to_pdf_file!(figure, path)
  end
end
</code></pre>
<p>Not how tha KDE is automatically computed from the observations.</p>
<p>A natural next step would be to support lazy color maps. Lazy color maps are a bit hard, because they can’t return a color until the plot is finalized. I have to accumulate constraints and resolve them only after I decide I don’t want to draw anything else in the plot. I can’t determine the color of each line before drawing all the lines…</p>
<p>I do have a naming question, though: <em>plot</em> can be both a name and a verb. When I call <code>Plot2D.kde_plot/1</code>, I want to add a KDE plot to a plot. The name doesn’t feel natural. Renaming it to <code>plot_kde</code> would be more natural, but then it would not be consistente with <code>scatter_plot</code>… I can’t change it to <code>plot_scatter</code> because it makes no sense. Since we are adding plots to a plot (?), maybe I should rename it to <code>Plot2D.add_kde_plot</code> or <code>Plot2D.draw_kde_plot</code>, which would allow for consistent naming for all the plots: <code>draw_kde_plot</code>, <code>draw_scatter_plot</code>, <code>draw_line_plot</code>, etc. What do people here think?</p>
<p>Regarding performance, it’s still horrible for plots that have ~1000 elements. The main bottleneck is keeping all the dynamic constraints and then replace all variables by their values. This takes a lot of time and a lot of memory too.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="324325" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-324325" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="324325"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="324337" data-post-id="324337">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I think I’ve finally hit Quartz’s performance limitations, namely the memory consumption. I’m trying to generate contour plots, which for a large number of contours result in a large number of line segments, and even for modest sized grids and for modest numbers of contour levels, quartz segfaults. The problem seems to be in the more “algebra-heavy” elixir code, such as the functions that substitute the variables in the polynomials by their values after solving the linear programming.</p>
<p>An example of a contour plot (with 4 levels) and an 15x15 grid:</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/2/c/2ccaac05ff4bce9cdaf50014caca5e1ec878a4ff.png" data-download-href="https://forum.elixirforum.com/uploads/default/2ccaac05ff4bce9cdaf50014caca5e1ec878a4ff" title="image" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/original/3X/2/c/2ccaac05ff4bce9cdaf50014caca5e1ec878a4ff.png" alt="image" data-base62-sha1="6ofelrCkpH9KAawZynNjfcx0IHl" width="661" height="513"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">image</span><span class="informations">661×513 29.9 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="324337" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-324337" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="324337"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #19"></div>
  </section>
</div>
    <div class="postbit" id="324436" data-post-id="324436">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>For anyone following this, it turns out I’m a total idiot… Elixir is handling tens of thousands of elements just fine, as I have just discovered with better benchmarking. Even if each element contains a number of totally not optimised polynomials implemented as structs.</p>
<p>It turns out that unlike what I previously reported, the problem is with typst. Problems start to happen when I try to compile typst files of &gt; 20.000 lines with a large number of graphical objects. To be fair to typst, these files are probably much larger than anything anyone has tried to use typst on (I don’t know whether the critical part is the patching or the rendering, but I’m not that interested in finding out).</p>
<p>Because of this, I have decided to keep the core constraint-based architecture the same and drop typst. I’ll be try to use resvg (the rust svg rendering library, which also has Elixir bindings) for text rendering and measuring and to render SVGs into PNGs. I assume that resvg is probably better optimised than typst for rendering larger numbers of shapes.</p>
<p>The only thing I lose is math typesetting, but it turns out plots don’t use that much math typesetting. One can get by with unicode symbols, superscripts, subscripts and not much else.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="324436" data-batch-url="/posts/batch_likers">
                        5
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/quartz-data-visualization-library-aimed-at-publication-not-interactive-display/56576/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-324436" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="324436"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #20"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/56576/load_more?page=3">Load more posts (12 remaining)</a>
</div></template></turbo-stream>