You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by gi...@apache.org on 2026/07/02 23:32:04 UTC

(groovy-dev-site) branch asf-site updated: 2026/07/02 23:31:56: Generated dev website from groovy-website@4575078

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-dev-site.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new a0c3bd27 2026/07/02 23:31:56: Generated dev website from groovy-website@4575078
a0c3bd27 is described below

commit a0c3bd2773cd365a7f964d93ece256eb8a0f3cb8
Author: jenkins <bu...@apache.org>
AuthorDate: Thu Jul 2 23:31:56 2026 +0000

    2026/07/02 23:31:56: Generated dev website from groovy-website@4575078
---
 blog/img/mmu-groovysh-output.png          | Bin 357079 -> 419603 bytes
 blog/img/mmu-groovysh-source.png          | Bin 430316 -> 308775 bytes
 blog/index.html                           |   2 +-
 blog/multimodal-universe-with-groovy.html | 155 +++++++++++++++++++++---------
 search/search-index.json                  |   4 +-
 5 files changed, 115 insertions(+), 46 deletions(-)

diff --git a/blog/img/mmu-groovysh-output.png b/blog/img/mmu-groovysh-output.png
index d488a310..78368c9d 100644
Binary files a/blog/img/mmu-groovysh-output.png and b/blog/img/mmu-groovysh-output.png differ
diff --git a/blog/img/mmu-groovysh-source.png b/blog/img/mmu-groovysh-source.png
index 420282dd..1a008bd0 100644
Binary files a/blog/img/mmu-groovysh-source.png and b/blog/img/mmu-groovysh-source.png differ
diff --git a/blog/index.html b/blog/index.html
index 086de86c..afca1b6e 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -107,7 +107,7 @@
                     colors: am5.ColorSet.new(root, {})
                 }));
                 wc.data.setAll([
-                { category: "groovy", value: 93 }, { category: "asf", value: 2 }, { category: "apachecon", value: 3 }, { category: "communityovercode", value: 2 }, { category: "natural language processing", value: 2 }, { category: "nlp", value: 1 }, { category: "nlpcraft", value: 1 }, { category: "combinations", value: 1 }, { category: "permutations", value: 1 }, { category: "testing", value: 2 }, { category: "junit", value: 2 }, { category: "spock", value: 2 }, { category: "jqwik", valu [...]
+                { category: "groovy", value: 93 }, { category: "asf", value: 2 }, { category: "apachecon", value: 3 }, { category: "communityovercode", value: 2 }, { category: "natural language processing", value: 2 }, { category: "nlp", value: 1 }, { category: "nlpcraft", value: 1 }, { category: "combinations", value: 1 }, { category: "permutations", value: 1 }, { category: "testing", value: 2 }, { category: "junit", value: 2 }, { category: "spock", value: 2 }, { category: "jqwik", valu [...]
                 ]);
                 wc.labels.template.setAll({
                     paddingTop: 5,
diff --git a/blog/multimodal-universe-with-groovy.html b/blog/multimodal-universe-with-groovy.html
index d83cdf1b..bc137c58 100644
--- a/blog/multimodal-universe-with-groovy.html
+++ b/blog/multimodal-universe-with-groovy.html
@@ -3,7 +3,7 @@
 <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
 <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
-    <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, groovysh, huggingface, astronomy, astrophysics, data science, sixel, java2d, spectrum'/><meta name='description' content='Reproduce a viral astronomy demo in groovysh: load galaxy images and spectra from Hugging Face&apos;s Multimodal Universe and render them inline with /img.'/><title>The Apache Groov [...]
+    <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, groovysh, huggingface, astronomy, astrophysics, data science, sixel, jfreechart, spectrum'/><meta name='description' content='Reproduce a viral astronomy demo in groovysh: load galaxy images and spectra from Hugging Face&apos;s Multimodal Universe and render them inline with /img.'/><title>The Apache G [...]
 </head><body>
     <div id='fork-me'>
         <a href='https://github.com/apache/groovy'>
@@ -63,7 +63,7 @@
                                     </ul>
                                 </div>
                             </div>
-                        </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Exploring the Multimodal Universe with Groovy&trade;</a></li><li><a href='#_what_is_the_multimodal_universe' class='anchor-link'>What is the Multimodal Universe?</a></li><li><a href='#_rendering_images_in_groovysh' class='anchor-link'>Rendering images in groovysh</a></li>< [...]
+                        </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Exploring the Multimodal Universe with Groovy&trade;</a></li><li><a href='#_what_is_the_multimodal_universe' class='anchor-link'>What is the Multimodal Universe?</a></li><li><a href='#_rendering_images_in_groovysh' class='anchor-link'>Rendering images in groovysh</a></li>< [...]
 <a href="https://github.com/paulk-asert/" target="_blank" rel="noopener noreferrer"><img style="border-radius:50%;height:48px;width:auto" src="img/paulk-asert.png" alt="Paul King"></a>
 <div style="display:grid;align-items:center;margin:0.1ex;padding:0ex">
   <div><a href="https://github.com/paulk-asert/" target="_blank" rel="noopener noreferrer"><span>Paul King</span></a></div>
@@ -180,14 +180,17 @@ chart we build ourselves.</p>
 <div class="paragraph">
 <p>Here is the complete <code>mmu.groovy</code> script. It reads one cross-matched-style row from each dataset,
 decodes the galaxy PNG, filters the masked points out of the spectrum (mirroring the video&#8217;s
-<code>s = s[~s["mask"]]</code>), and hand-draws the spectrum as a Java2D line plot returned as a
+<code>s = s[~s["mask"]]</code>), and plots the spectrum with JFreeChart, handed to <code>/img</code> as a
 <code>BufferedImage</code>:</p>
 </div>
 <div class="listingblock">
 <div class="content">
 <pre class="prettyprint highlight"><code data-lang="groovy">import groovy.json.JsonSlurper
+import java.awt.Color
 import javax.imageio.ImageIO
-import module java.desktop
+import org.jfree.chart.ChartFactory
+import org.jfree.data.xy.XYSeries
+import org.jfree.data.xy.XYSeriesCollection
 
 rows = { String ds -&gt; new JsonSlurper().parse(
     new URL("https://datasets-server.huggingface.co/rows?dataset=$ds&amp;config=default&amp;split=train&amp;offset=0&amp;length=1")).rows[0].row }
@@ -199,33 +202,13 @@ galaxy = ImageIO.read(new ByteArrayInputStream(rows('UniverseTBD/mmu_gz10').rgb_
 spec = rows('UniverseTBD/mmu_sdss_sdss').spectrum
 pts = [spec.'lambda', spec.flux, spec.mask].transpose().findAll { !it[2] }.collect { [it[0] as double, it[1] as double] }
 
-// --- a tiny Java2D line plot -&gt; a BufferedImage that /img renders ---
-spectrumPlot = { data -&gt;
-    int w = 820, h = 520
-    def img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
-    def g = img.createGraphics()
-    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
-    g.color = Color.WHITE; g.fillRect(0, 0, w, h)
-    int ml = 70, mr = 20, mt = 20, mb = 44, pw = w - ml - mr, ph = h - mt - mb
-    def xs = data*.get(0), ys = data*.get(1)
-    double xmin = xs.min(), xmax = xs.max(), ymin = ys.min(), ymax = ys.max()
-    def sx = { double x -&gt; ml + (x - xmin) / (xmax - xmin) * pw }
-    def sy = { double y -&gt; mt + (1 - (y - ymin) / (ymax - ymin)) * ph }
-    g.color = Color.BLACK
-    g.drawLine(ml, mt, ml, mt + ph); g.drawLine(ml, mt + ph, ml + pw, mt + ph)
-    g.font = g.font.deriveFont(11f)
-    (0..4).each { i -&gt;
-        double xv = xmin + (xmax - xmin) * i / 4; int px = sx(xv) as int
-        g.drawLine(px, mt + ph, px, mt + ph + 4); g.drawString(sprintf('%.1f', xv), px - 20, mt + ph + 20)
-        double yv = ymin + (ymax - ymin) * i / 4; int py = sy(yv) as int
-        g.drawLine(ml - 4, py, ml, py); g.drawString(sprintf('%.1f', yv), 8, py + 4)
-    }
-    g.color = Color.MAGENTA; g.stroke = new BasicStroke(1.1f)
-    def path = new Path2D.Double()
-    data.eachWithIndex { p, i -&gt; i == 0 ? path.moveTo(sx(p[0]), sy(p[1])) : path.lineTo(sx(p[0]), sy(p[1])) }
-    g.draw(path); g.dispose(); img
-}
-chart = spectrumPlot(pts)
+// --- a JFreeChart line plot -&gt; a BufferedImage that /img renders ---
+series = new XYSeries('flux')
+pts.each { series.add(it[0], it[1]) }
+plot = ChartFactory.createXYLineChart('SDSS spectrum', 'Wavelength (Å)', 'Flux', new XYSeriesCollection(series))
+plot.XYPlot.renderer.setSeriesPaint(0, Color.MAGENTA)  // magenta, to match the original demo
+plot.removeLegend()
+chart = plot.createBufferedImage(820, 520)
 println "galaxy ${galaxy.width}x${galaxy.height}, spectrum ${pts.size()} pts -&gt; chart ${chart.width}x${chart.height}"</code></pre>
 </div>
 </div>
@@ -243,10 +226,11 @@ Groovy&#8217;s GDK gives us <code>decodeBase64()</code> on the PNG string for fr
 <code>findAll</code> the unmasked samples in one readable expression.</p>
 </li>
 <li>
-<p>Note the <code>import module java.desktop</code> on line 3. This is a
-<a href="https://openjdk.org/jeps/494">JDK module import</a> (JEP 494), supported in Groovy 6, that pulls in every
-package the <code>java.desktop</code> module exports (<code>java.awt</code>, <code>java.awt.geom</code>, <code>java.awt.image</code>, and more) in
-a single line — handy for a script that reaches into a few AWT corners.</p>
+<p>The spectrum is drawn with <a href="https://www.jfree.org/jfreechart/">JFreeChart</a>: <code>createXYLineChart</code>
+gives us axes, ticks and gridlines for free, and <code>createBufferedImage</code> hands <code>/img</code> exactly the
+<code>BufferedImage</code> it wants. That is the same trick the <code>groovysh</code> user guide uses to show off XChart,
+Smile and Orson Charts — <code>/img</code> speaks <code>BufferedImage</code>, so the whole JVM charting ecosystem is one
+<code>/grab</code> away.</p>
 </li>
 </ul>
 </div>
@@ -256,11 +240,13 @@ a single line — handy for a script that reaches into a few AWT corners.</p>
 <h2 id="_running_it">Running it</h2>
 <div class="sectionbody">
 <div class="paragraph">
-<p>Start <code>groovysh</code>, load the script, and view each result with <code>/img</code>:</p>
+<p>Start <code>groovysh</code>, pull in the one charting dependency with the shell&#8217;s <code>/grab</code> command, load the
+script, and view each result with <code>/img</code>:</p>
 </div>
 <div class="listingblock">
 <div class="content">
-<pre class="prettyprint highlight"><code>groovy> /load mmu.groovy
+<pre class="prettyprint highlight"><code>groovy> /grab org.jfree:jfreechart:1.5.6
+groovy> /load mmu.groovy
 galaxy 256x256, spectrum 3713 pts -> chart 820x520
 Loaded: mmu.groovy
 groovy> /img $galaxy
@@ -268,6 +254,11 @@ groovy> /img $chart</code></pre>
 </div>
 </div>
 <div class="paragraph">
+<p>We use <code>groovysh&#8217;s `/grab</code> command rather than an <code>@Grab</code> annotation in the script itself: <code>/load</code>
+runs the file as a plain script, so grabbing the dependency in the shell first keeps that step
+explicit (and matches how the <code>groovysh</code> guide adds XChart, Smile and the other charting libraries).</p>
+</div>
+<div class="paragraph">
 <p>Here is the script itself, viewed through the shell&#8217;s built-in <code>/less</code> pager (which syntax-highlights
 Groovy):</p>
 </div>
@@ -297,14 +288,16 @@ against wavelength (in &#8491;ngstr&ouml;ms) on the horizontal axis</strong>, fr
 left to 9200&#8491; on the right. The characteristic shape — low and noisy in the blue, rising to a
 broad, fairly flat continuum toward the red — is the combined light of the galaxy&#8217;s stellar
 populations, with narrow spikes where emission lines and residual sky features poke through. We drew
-it in magenta to match the original demo; the thin black vertical hairs you can see are simply the
-plot connecting rapidly varying adjacent samples. We removed the flagged (`mask`ed) pixels first, so
-3713 good samples remain out of the original ~3850.</p>
+the line in magenta to match the original demo, and let JFreeChart supply the axes, ticks and
+gridlines. We removed the flagged (`mask`ed) pixels first, so 3713 good samples remain out of the
+original ~3850.</p>
 </div>
 <div class="paragraph">
-<p>The point isn&#8217;t the plot&#8217;s polish — it&#8217;s that a few lines of Groovy pulled a real galaxy and a real
-spectrum out of an 80TB, 30-survey collection, over plain HTTP, and painted them in a terminal, with
-no local data download and no heavyweight plotting dependency.</p>
+<p>The real point: a few lines of Groovy pulled a real galaxy and a real spectrum out of an 80TB,
+30-survey collection, over plain HTTP, and painted them in a terminal — with no local data download.
+And because <code>groovysh&#8217;s `/img</code> speaks <code>BufferedImage</code>, reaching for a proper charting library like
+JFreeChart costs one <code>/grab</code> and a handful of lines. And if you would rather ship no dependency at
+all, the pure-JDK variant at the end of this post draws the same plot by hand.</p>
 </div>
 </div>
 </div>
@@ -321,6 +314,82 @@ the image, cleaning the spectrum, and rendering both — is pure Groovy and the
 </div>
 </div>
 <div class="sect1">
+<h2 id="_a_dependency_free_variant">A dependency-free variant</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Reaching for JFreeChart is the pragmatic choice, but the demo also works with <strong>no external dependency
+at all</strong> — the JDK&#8217;s own Java2D can paint the line plot straight into a <code>BufferedImage</code>, which is
+exactly what <code>/img</code> consumes. It is more code, but it ships with the JVM, so there is no <code>/grab</code>: just
+<code>/load mmu_java2d.groovy</code>.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">import groovy.json.JsonSlurper
+import javax.imageio.ImageIO
+import module java.desktop
+
+rows = { String ds -&gt; new JsonSlurper().parse(
+    new URL("https://datasets-server.huggingface.co/rows?dataset=$ds&amp;config=default&amp;split=train&amp;offset=0&amp;length=1")).rows[0].row }
+
+// --- the galaxy: gz10 stores a ready-made RGB PNG as base64 bytes ---
+galaxy = ImageIO.read(new ByteArrayInputStream(rows('UniverseTBD/mmu_gz10').rgb_image.bytes.decodeBase64()))
+
+// --- the spectrum: drop masked points, like s[~s["mask"]] in the video ---
+spec = rows('UniverseTBD/mmu_sdss_sdss').spectrum
+pts = [spec.'lambda', spec.flux, spec.mask].transpose().findAll { !it[2] }.collect { [it[0] as double, it[1] as double] }
+
+// --- a tiny Java2D line plot -&gt; a BufferedImage that /img renders ---
+spectrumPlot = { data -&gt;
+    int w = 820, h = 520
+    def img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)
+    def g = img.createGraphics()
+    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
+    g.color = Color.WHITE; g.fillRect(0, 0, w, h)
+    int ml = 70, mr = 20, mt = 20, mb = 44, pw = w - ml - mr, ph = h - mt - mb
+    def xs = data*.get(0), ys = data*.get(1)
+    double xmin = xs.min(), xmax = xs.max(), ymin = ys.min(), ymax = ys.max()
+    def sx = { double x -&gt; ml + (x - xmin) / (xmax - xmin) * pw }
+    def sy = { double y -&gt; mt + (1 - (y - ymin) / (ymax - ymin)) * ph }
+    g.color = Color.BLACK
+    g.drawLine(ml, mt, ml, mt + ph); g.drawLine(ml, mt + ph, ml + pw, mt + ph)
+    g.font = g.font.deriveFont(11f)
+    (0..4).each { i -&gt;
+        double xv = xmin + (xmax - xmin) * i / 4; int px = sx(xv) as int
+        g.drawLine(px, mt + ph, px, mt + ph + 4); g.drawString(sprintf('%.1f', xv), px - 20, mt + ph + 20)
+        double yv = ymin + (ymax - ymin) * i / 4; int py = sy(yv) as int
+        g.drawLine(ml - 4, py, ml, py); g.drawString(sprintf('%.1f', yv), 8, py + 4)
+    }
+    g.color = Color.MAGENTA; g.stroke = new BasicStroke(1.1f)
+    def path = new Path2D.Double()
+    data.eachWithIndex { p, i -&gt; i == 0 ? path.moveTo(sx(p[0]), sy(p[1])) : path.lineTo(sx(p[0]), sy(p[1])) }
+    g.draw(path); g.dispose(); img
+}
+chart = spectrumPlot(pts)
+println "galaxy ${galaxy.width}x${galaxy.height}, spectrum ${pts.size()} pts -&gt; chart ${chart.width}x${chart.height}"</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Two things worth noting:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>No <code>/grab</code> this time — the plot is hand-drawn with Java2D in the <code>spectrumPlot</code> closure, so we scale
+the axes, draw the ticks, and stroke the line ourselves instead of getting them for free. That is the
+cost of dropping the dependency.</p>
+</li>
+<li>
+<p>The <code>import module java.desktop</code> on line 3 is a <a href="https://openjdk.org/jeps/494">JDK module import</a>
+(JEP 494), supported in Groovy 6: it pulls in every package the <code>java.desktop</code> module exports
+(<code>java.awt</code>, <code>java.awt.geom</code>, <code>java.awt.image</code>, and more) in a single line — handy here because the
+hand-rolled plot reaches into several AWT corners (<code>BufferedImage</code>, <code>Path2D</code>, <code>BasicStroke</code>,
+<code>RenderingHints</code>, <code>Color</code>).</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
 <h2 id="_more_information">More information</h2>
 <div class="sectionbody">
 <div class="ulist">
diff --git a/search/search-index.json b/search/search-index.json
index ccd4dc10..e8dd6ee6 100644
--- a/search/search-index.json
+++ b/search/search-index.json
@@ -478,7 +478,7 @@
     {
         "id": "blog/multimodal-universe-with-groovy.html",
         "title": "The Apache Groovy programming language - Blogs - Exploring the Multimodal Universe with Groovy&trade;",
-        "content": "The Apache Groovy programming language - Blogs - Exploring the Multimodal Universe with Groovy&trade; Socialize Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences Source code on GitHub Report issues in Jira Stack Overflow questions Slack Community You are using an outdated browser. Please upgrade your browser to improve your experience. Apache Groovy&trade; Learn Documentation Download Support Contrib [...]
+        "content": "The Apache Groovy programming language - Blogs - Exploring the Multimodal Universe with Groovy&trade; Socialize Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences Source code on GitHub Report issues in Jira Stack Overflow questions Slack Community You are using an outdated browser. Please upgrade your browser to improve your experience. Apache Groovy&trade; Learn Documentation Download Support Contrib [...]
         "url": "blog/multimodal-universe-with-groovy.html",
         "site": "dev"
     },
@@ -730,7 +730,7 @@
     {
         "id": "blog/index.html",
         "title": "The Apache Groovy programming language - Blogs",
-        "content": "The Apache Groovy programming language - Blogs Socialize Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences Source code on GitHub Report issues in Jira Stack Overflow questions Slack Community You are using an outdated browser. Please upgrade your browser to improve your experience. Apache Groovy&trade; Learn Documentation Download Support Contribute Ecosystem Blog posts Socialize Blogs Exploring the  [...]
+        "content": "The Apache Groovy programming language - Blogs Socialize Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon Groovy on LinkedIn Events and conferences Source code on GitHub Report issues in Jira Stack Overflow questions Slack Community You are using an outdated browser. Please upgrade your browser to improve your experience. Apache Groovy&trade; Learn Documentation Download Support Contribute Ecosystem Blog posts Socialize Blogs Exploring the  [...]
         "url": "blog/index.html",
         "site": "dev"
     },