<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TianYi Zhang</title>
    <description>The latest articles on DEV Community by TianYi Zhang (@tianyizhang).</description>
    <link>https://dev.to/tianyizhang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4009977%2F830e5076-2832-4840-8729-ac725c796a22.png</url>
      <title>DEV Community: TianYi Zhang</title>
      <link>https://dev.to/tianyizhang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tianyizhang"/>
    <language>en</language>
    <item>
      <title>What is a Histogram? A Complete Guide</title>
      <dc:creator>TianYi Zhang</dc:creator>
      <pubDate>Sat, 08 Aug 2026 17:19:48 +0000</pubDate>
      <link>https://dev.to/tianyizhang/what-is-a-histogram-a-complete-guide-hji</link>
      <guid>https://dev.to/tianyizhang/what-is-a-histogram-a-complete-guide-hji</guid>
      <description>&lt;p&gt;A &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Histogram" rel="noopener noreferrer"&gt;histogram&lt;/a&gt;&lt;/strong&gt; is a graphical representation that groups numeric data into intervals—called bins or class intervals—and displays the frequency (count) of observations in each bin as adjacent vertical bars. Unlike bar charts that compare categorical data, a histogram reveals the underlying frequency distribution of a continuous variable: its shape, spread, central tendency, and potential outliers. First introduced by statistician &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Karl_Pearson" rel="noopener noreferrer"&gt;Karl Pearson&lt;/a&gt;&lt;/strong&gt; in 1891, the histogram is one of the most fundamental and widely used tools in exploratory data analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Terms
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bin / Class Interval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An equal-width interval that partitions the data range. For example, 10–19 is a class interval with width 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frequency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The count of data points falling within a given bin. Taller bars indicate higher data density in that range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bin Width&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The width of each interval = upper bound − lower bound. All bins must be of equal width; otherwise bar areas mislead visual interpretation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distribution Shape&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The overall pattern revealed by bar heights—symmetry, skewness, modality, and whether the data is single- or multi-peaked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skewness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A measure of distribution asymmetry. Right-skew (positive) means the tail extends to higher values; left-skew (negative) extends to lower values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Modality&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The number of peaks in a distribution. Unimodal = one peak; bimodal = two peaks suggesting mixed populations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Use a Histogram?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See distribution shape at a glance.&lt;/strong&gt; The rise and fall of bars naturally reveal whether your data is symmetric, skewed, unimodal, or multimodal—insights that summary statistics like mean and standard deviation cannot convey alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quickly identify the densest region.&lt;/strong&gt; The tallest bar marks the interval where data clusters most heavily, giving you the most "typical" value range.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect outliers and data gaps.&lt;/strong&gt; Isolated tall bars or blank gaps between bars may indicate data entry errors, measurement anomalies, or phenomena warranting further investigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare multiple datasets.&lt;/strong&gt; Place histograms side by side (or overlay them) to visually compare distributions across different groups or time periods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High teaching value.&lt;/strong&gt; The histogram is a staple of middle school, high school, and college statistics curricula worldwide, used to teach data grouping, frequency distributions, and distribution shape analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works for all numeric data.&lt;/strong&gt; Whether exam scores, heights and weights, income levels, or production quality metrics—any continuous numeric variable can be visualized as a histogram.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Relationship to Box Plots and Stem-and-Leaf Plots
&lt;/h2&gt;

&lt;p&gt;Histograms, box plots, and stem-and-leaf plots all belong to John Tukey's &lt;strong&gt;Exploratory Data Analysis (EDA)&lt;/strong&gt; toolkit, each with distinct strengths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chart&lt;/th&gt;
&lt;th&gt;Preserves Raw Values&lt;/th&gt;
&lt;th&gt;Shows Distribution Shape&lt;/th&gt;
&lt;th&gt;Reveals Multimodality&lt;/th&gt;
&lt;th&gt;Best Data Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Histogram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ Binned into intervals&lt;/td&gt;
&lt;td&gt;✓ Bar heights show frequency&lt;/td&gt;
&lt;td&gt;✓ Bimodal/multimodal visible&lt;/td&gt;
&lt;td&gt;20+ points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Box Plot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ Five-number summary only&lt;/td&gt;
&lt;td&gt;✓ But hides multimodal patterns&lt;/td&gt;
&lt;td&gt;✗ Cannot show&lt;/td&gt;
&lt;td&gt;Any size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stem-and-Leaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓ Every value preserved&lt;/td&gt;
&lt;td&gt;✓ Leaf arrangement shows shape&lt;/td&gt;
&lt;td&gt;✓ But less intuitive than histogram&lt;/td&gt;
&lt;td&gt;10–50 points, ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Worked Example
&lt;/h2&gt;

&lt;p&gt;Let's work through a set of 30 math exam scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;55, 58, 62, 65, 67, 68, 70, 72, 73, 74,
75, 76, 77, 78, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 90, 92, 95, 97, 99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Determine the Data Range
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Minimum = 55
Maximum = 99
Range = 99 − 55 = 44
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Choose the Number of Bins and Bin Width
&lt;/h3&gt;

&lt;p&gt;Common rules of thumb include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sturges' formula&lt;/strong&gt;: k = ⌈log₂(n) + 1⌉. For n = 30, k ≈ ⌈4.9 + 1⌉ = 6 bins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freedman-Diaconis rule&lt;/strong&gt;: bin width = 2 × IQR × n^(−1/3). More robust for skewed data or data with outliers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using Sturges' formula, we choose 6 bins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bin width = 44 ÷ 6 ≈ 7.33 → round up to 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rounding the bin width up to a "nice" number makes axis labels cleaner—a common practice in real-world charting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Define Intervals and Count Frequencies
&lt;/h3&gt;

&lt;p&gt;Starting at 55, with a width of 8, we define 6 intervals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Interval        Frequency    Data points
55 – 62         4            55, 58, 62
63 – 70         4            65, 67, 68, 70
71 – 78         8            72, 73, 74, 75, 76, 77, 78, 78
79 – 86         8            79, 80, 81, 82, 83, 84, 85, 86
87 – 94         4            87, 88, 90, 92
95 – 102        2            95, 97, 99
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Interpretation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The tallest bars are in the 71–78 and 79–86 intervals&lt;/strong&gt; (8 students each), meaning the bulk of scores cluster between 70 and 86&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;55–62 and 63–70 each have 4 students&lt;/strong&gt;, with low scores spread evenly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;87–94 has 4 students, 95–102 has 2&lt;/strong&gt;, showing a gradual decline in high scores&lt;/li&gt;
&lt;li&gt;Overall, the distribution is &lt;strong&gt;roughly symmetric and unimodal&lt;/strong&gt;, centered around 80, with a slight right skew (a few very high scores stretch the right tail)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Bin Width Affects the Histogram
&lt;/h2&gt;

&lt;p&gt;Bin width is the single most important parameter in a histogram. The same dataset can produce dramatically different visual impressions depending on bin width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Same 30 exam scores, different bin widths:

Bin width = 4  (~11 bins):
High resolution—many narrow bars reveal local fluctuations,
but may introduce excessive noise

Bin width = 8  (~6 bins):
Moderate resolution—distribution shape is clear and interpretable;
this is the sweet spot for most analyses

Bin width = 15 (~3 bins):
Low resolution—few wide bars oversmooth the data,
hiding important distribution features
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rules of thumb&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Too few bins → hide meaningful patterns (under-smoothing)&lt;/li&gt;
&lt;li&gt;Too many bins → amplify random noise (over-smoothing)&lt;/li&gt;
&lt;li&gt;5–20 bins is the optimal range for most datasets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Computation Logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeHistogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numBins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Sort and get range&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Determine bin count (Freedman-Diaconis or manual)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numBins&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cbrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;binWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dMin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;bins&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Count frequencies in each bin&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bins&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bins&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dMin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;binWidth&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Build bin objects&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dMin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;binWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dMin&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;binWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;74&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;histogram&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeHistogram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 55, end: 62.33, count: 4 },&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 62.33, end: 69.67, count: 3 },&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 69.67, end: 77, count: 6 },&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 77, end: 84.33, count: 7 },&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 84.33, end: 91.67, count: 5 },&lt;/span&gt;
&lt;span class="c1"&gt;//   { start: 91.67, end: 99, count: 5 },&lt;/span&gt;
&lt;span class="c1"&gt;// ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Distribution Shapes
&lt;/h2&gt;

&lt;p&gt;Familiarity with these typical shapes helps you quickly diagnose your data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Typical Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bell-shaped&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High center, symmetric tails&lt;/td&gt;
&lt;td&gt;Heights, weights, test scores, measurement errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Right-skewed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bulk on the left, long right tail&lt;/td&gt;
&lt;td&gt;Income, housing prices, page load times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Left-skewed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bulk on the right, long left tail&lt;/td&gt;
&lt;td&gt;Easy exam scores, product lifespan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bimodal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Two distinct peaks&lt;/td&gt;
&lt;td&gt;Combined male/female heights, two production batches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uniform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bars of roughly equal height&lt;/td&gt;
&lt;td&gt;Lottery numbers, random number generation, fair dice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;J-shaped&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One end very low, the other very high&lt;/td&gt;
&lt;td&gt;Queue waiting times, time between failures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between a histogram and a bar chart?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most common confusion. A bar chart compares &lt;strong&gt;categorical data&lt;/strong&gt; (e.g., sales by product), bars have gaps between them, and the order can be rearranged arbitrarily. A histogram displays the distribution of &lt;strong&gt;continuous numeric data&lt;/strong&gt;, bars touch each other (no gaps), and the order is fixed by the numeric scale. In short: bar charts answer "which is larger?"—histograms answer "what does the data look like?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many data points do I need for a histogram?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Technically, 3 data points can produce a histogram, but at least 20 are needed to reveal a meaningful distribution shape. With very small datasets, a stem-and-leaf plot may be a better choice—it preserves every original value and is better suited for small samples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I choose the right bin width?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no single "correct" bin width, but there are established rules of thumb. Sturges' formula (k = log₂(n) + 1) works well for approximately normal data. The Freedman-Diaconis rule (2 × IQR × n^(−1/3)) is more robust against outliers and skewed data. In practice, try 2–3 different bin widths and choose the one that best reveals the data's underlying structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a histogram show percentages instead of counts?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Divide each bin's frequency by the total number of data points to obtain the relative frequency (percentage). The vertical axis changes from "frequency" to "relative frequency." Bar heights remain proportional, but absolute values become percentages. This is especially useful when comparing datasets of different sample sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a histogram detect outliers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, but less directly than a box plot. If a single bin has very low frequency while adjacent bins have much higher frequencies, this "gap" may signal outliers. A more systematic approach is to use box plot IQR fences (Q1 − 1.5×IQR and Q3 + 1.5×IQR) to flag outliers first, then observe their positions in the histogram.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why must histogram bins be of equal width?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because a histogram represents frequency through &lt;strong&gt;area&lt;/strong&gt; (not just height). If bins have unequal widths, wider bins would have disproportionately larger areas, misleading the reader into thinking those intervals have higher frequency. Equal-width bins ensure bar height is directly proportional to frequency, making visual interpretation accurate. If unequal-width bins are unavoidable (e.g., logarithmic income brackets), the vertical axis must be labeled "density" (density = frequency ÷ bin width) rather than "frequency."&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensions of the Histogram
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frequency Polygon
&lt;/h3&gt;

&lt;p&gt;Connect the midpoints of each histogram bar's top edge with straight lines to form a frequency polygon. Overlaying it on the histogram helps readers trace the overall trend of the distribution more intuitively. Frequency polygons are especially useful when comparing multiple distributions in the same coordinate system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cumulative Frequency Histogram
&lt;/h3&gt;

&lt;p&gt;Change the vertical axis to cumulative frequency (each bin's count plus all previous bins' counts), producing a step-like graph that rises from left to right. A cumulative frequency histogram directly answers questions like "how many data points are less than or equal to a given value?"&lt;/p&gt;

&lt;h3&gt;
  
  
  Density Curve Overlay
&lt;/h3&gt;

&lt;p&gt;Superimpose a smooth kernel density estimation (KDE) curve over the histogram. This preserves the raw bin information while adding a smoothed trend line, making it easier to compare the empirical distribution against a theoretical distribution such as the normal curve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Histogram Output
&lt;/h2&gt;

&lt;p&gt;The chart was generated by &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;https://aiboxplot.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftms848aodqi2qkartdwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftms848aodqi2qkartdwi.png" alt=" " width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pearson, K. (1895). Contributions to the Mathematical Theory of Evolution. II. Skew Variation in Homogeneous Material. &lt;em&gt;Philosophical Transactions of the Royal Society of London&lt;/em&gt;, 186, 343–414.&lt;/li&gt;
&lt;li&gt;Freedman, D., &amp;amp; Diaconis, P. (1981). On the histogram as a density estimator: L₂ theory. &lt;em&gt;Zeitschrift für Wahrscheinlichkeitstheorie und verwandte Gebiete&lt;/em&gt;, 57(4), 453–476.&lt;/li&gt;
&lt;li&gt;Sturges, H. A. (1926). The choice of a class interval. &lt;em&gt;Journal of the American Statistical Association&lt;/em&gt;, 21(153), 65–66.&lt;/li&gt;
&lt;li&gt;Tukey, J. W. (1977). &lt;em&gt;Exploratory Data Analysis&lt;/em&gt;. Addison-Wesley.&lt;/li&gt;
&lt;li&gt;Scott, D. W. (1979). On optimal and data-based histograms. &lt;em&gt;Biometrika&lt;/em&gt;, 66(3), 605–610.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dataviz</category>
      <category>stastistic</category>
      <category>histogram</category>
    </item>
    <item>
      <title>What is a Stem-and-Leaf Plot? A Complete Guide</title>
      <dc:creator>TianYi Zhang</dc:creator>
      <pubDate>Sat, 08 Aug 2026 14:38:31 +0000</pubDate>
      <link>https://dev.to/tianyizhang/what-is-a-stem-and-leaf-plot-a-complete-guide-499j</link>
      <guid>https://dev.to/tianyizhang/what-is-a-stem-and-leaf-plot-a-complete-guide-499j</guid>
      <description>&lt;p&gt;A &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Stem-and-leaf_display" rel="noopener noreferrer"&gt;stem-and-leaf plot&lt;/a&gt;&lt;/strong&gt; is a semi-tabular format for displaying the distribution of quantitative data. Each value is split into a &lt;strong&gt;stem&lt;/strong&gt; (the leading digits) and a &lt;strong&gt;leaf&lt;/strong&gt; (the final digit), preserving every original data point while revealing the shape of the distribution at a glance. Invented by statistician John Tukey in the 1970s, it remains a staple of introductory statistics courses worldwide.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Terms
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The leading digit(s) of a number. For 73, the stem is 7. For 125, the stem is 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Leaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The final digit of a number. For 73, the leaf is 3. For 125, the leaf is 5. Leaves are always single digits from 0 to 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explains how to reconstruct values from stems and leaves. For example, "2 | 3 = 23" means stem=2, leaf=3 → value 23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The overall pattern of data revealed by the arrangement of leaves across stems — symmetry, skewness, gaps, and clusters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Use Stem-and-Leaf Plots?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Preserve every data point.&lt;/strong&gt; Unlike histograms that group values into bins, a stem-and-leaf plot keeps each individual number, so no information is lost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;See distribution shape instantly.&lt;/strong&gt; The arrangement of leaves naturally reveals the shape of the data — symmetry, skewness, gaps, and clusters are visible at a glance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect for small datasets.&lt;/strong&gt; When you have 10 to 50 values, a stem-and-leaf plot provides a clear, compact view without the bin-width decisions required by histograms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to create by hand or online.&lt;/strong&gt; The format is simple: split each number into stem and leaf, then sort. This free online tool does the computation automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ideal for education.&lt;/strong&gt; Stem-and-leaf plots are widely used in middle school, high school, and college statistics courses to teach data organization, median finding, and distribution analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recover original data.&lt;/strong&gt; Unlike histograms and box plots, you can reconstruct every exact original value from the display.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stem-and-Leaf vs Box Plot vs Histogram
&lt;/h2&gt;

&lt;p&gt;All three belong to John Tukey's &lt;strong&gt;exploratory data analysis (EDA)&lt;/strong&gt; toolkit, each with distinct strengths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chart&lt;/th&gt;
&lt;th&gt;Preserves raw values&lt;/th&gt;
&lt;th&gt;Shows distribution&lt;/th&gt;
&lt;th&gt;Best data size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stem-and-Leaf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✓ Every value visible&lt;/td&gt;
&lt;td&gt;✓ Leaf arrangement shows shape&lt;/td&gt;
&lt;td&gt;10–50, optimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Box Plot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ Only five-number summary&lt;/td&gt;
&lt;td&gt;✓ But hides multi-modal patterns&lt;/td&gt;
&lt;td&gt;Any size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Histogram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✗ Grouped into bins&lt;/td&gt;
&lt;td&gt;✓ But sensitive to bin width&lt;/td&gt;
&lt;td&gt;20+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Worked Example
&lt;/h2&gt;

&lt;p&gt;We'll use the &lt;strong&gt;Test Scores&lt;/strong&gt; sample from the tool — 20 students' exam scores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;52, 58, 63, 67, 71, 74, 78, 82, 85, 88, 91, 94, 97, 99, 72, 64, 81, 76, 93, 55
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Split Each Value into Stem and Leaf
&lt;/h3&gt;

&lt;p&gt;Split each number into stem (tens digit) and leaf (ones digit). For example, 52 → stem=5, leaf=2; 71 → stem=7, leaf=1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;52 → stem5 leaf2    58 → stem5 leaf8    63 → stem6 leaf3
67 → stem6 leaf7    71 → stem7 leaf1    74 → stem7 leaf4
78 → stem7 leaf8    82 → stem8 leaf2    85 → stem8 leaf5
88 → stem8 leaf8    91 → stem9 leaf1    94 → stem9 leaf4
97 → stem9 leaf7    99 → stem9 leaf9    72 → stem7 leaf2
64 → stem6 leaf4    81 → stem8 leaf1    76 → stem7 leaf6
93 → stem9 leaf3    55 → stem5 leaf5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Group by Stem, Sort Leaves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stem | Leaf
5    | 2 5 8
6    | 3 4 7
7    | 1 2 4 6 8
8    | 1 2 5 8
9    | 1 3 4 7 9

Key: 5 | 2 = 52
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Interpret
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stem 5 (50–59 pts):&lt;/strong&gt; 3 students, lower scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stem 6 (60–69 pts):&lt;/strong&gt; 3 students&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stem 7 (70–79 pts):&lt;/strong&gt; 5 students, the largest group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stem 8 (80–89 pts):&lt;/strong&gt; 4 students&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stem 9 (90–99 pts):&lt;/strong&gt; 5 students, strong high-end presence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The leaf arrangement reveals a &lt;strong&gt;mild bimodal pattern&lt;/strong&gt; — two peaks at the 70–79 and 90–99 ranges, with slightly fewer students in the 80–89 range. Overall scores are high with no extreme low outliers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Back-to-Back Stem-and-Leaf Plot
&lt;/h2&gt;

&lt;p&gt;When comparing two datasets, you can share a central "Stem" column with leaves from the first group on the left and leaves from the second group on the right — a back-to-back display.&lt;/p&gt;

&lt;p&gt;We'll use the &lt;strong&gt;Back-to-Back: Math vs Physics&lt;/strong&gt; sample from the tool:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Math scores:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;72, 85, 78, 90, 65, 88, 76, 92, 70, 84, 79, 86, 74, 91, 68
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Physics scores:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;68, 75, 82, 70, 88, 74, 80, 66, 85, 73, 90, 72, 69, 77, 81
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The back-to-back stem-and-leaf plot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       Math           Stem       Physics
       5 8             6         6 8 9
     0 2 4 6 8 9       7         0 2 3 4 5 7
     4 5 6 8           8         0 1 2 5 8
     0 1 2             9         0

Key: 6 | 5 = 65
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this display we can see immediately: Math scores are generally higher and more spread out (65–92, with leaves across four stems 6–9), while Physics scores are more concentrated (66–90, with most leaves in stems 7 and 8). Physics also has more low-scoring students (3 at stem 6 vs. Math's 2).&lt;/p&gt;




&lt;h2&gt;
  
  
  Stem-and-Leaf Plot Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnuxxrvn3cocor1u8knam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnuxxrvn3cocor1u8knam.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart was generated by &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;https://aiboxplot.com&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Extended Forms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Split Stems
&lt;/h3&gt;

&lt;p&gt;When data is concentrated in a narrow range, each stem can be split into two (e.g., 5 for leaves 0–4, 5* for leaves 5–9) to reveal finer detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stem | Leaf
5    | 2 3 4
5*   | 5 6 7 8 8 9
6    | 0 1 2 2 3
6*   | 5 6 7 8 9 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Computation Logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeStemLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Sort&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Split into stem and leaf, group by stem&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;leaf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
    &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leaf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Convert to ordered rows&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// already sorted because input was sorted&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minStem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;maxStem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;stems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Dynamic key: use first stem's first leaf as example&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt; = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;stem&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;leaves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Using the Test Scores sample data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;74&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;93&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeStemLeaf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Output:&lt;/span&gt;
&lt;span class="c1"&gt;// rows = [&lt;/span&gt;
&lt;span class="c1"&gt;//   { stem: 5, leaves: [2, 5, 8] },&lt;/span&gt;
&lt;span class="c1"&gt;//   { stem: 6, leaves: [3, 4, 7] },&lt;/span&gt;
&lt;span class="c1"&gt;//   { stem: 7, leaves: [1, 2, 4, 6, 8] },&lt;/span&gt;
&lt;span class="c1"&gt;//   { stem: 8, leaves: [1, 2, 5, 8] },&lt;/span&gt;
&lt;span class="c1"&gt;//   { stem: 9, leaves: [1, 3, 4, 7, 9] },&lt;/span&gt;
&lt;span class="c1"&gt;// ]&lt;/span&gt;
&lt;span class="c1"&gt;// key = "5 | 2 = 52"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the minimum number of data points needed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need at least 3 data points to create a stem-and-leaf plot, but 10 to 50 points produce the best results. Too few and no pattern is visible; too many and the leaf columns become unwieldy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between a stem-and-leaf plot and a histogram?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A histogram groups data into bins and shows only frequencies (bar heights), discarding individual values. A stem-and-leaf plot preserves every data point — you can recover the exact original numbers from the display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can stem-and-leaf plots handle decimals?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. For decimals like 7.3, 8.1, 9.6, the stem can be the integer part (7, 8, 9) and the leaf the decimal part (3, 1, 6). The key must specify this, e.g., "7 | 3 = 7.3".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can stem-and-leaf plots handle three-digit numbers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. For three-digit numbers like 125, 138, 142, the stem is the first two digits (12, 13, 14) and the leaf is the last digit (5, 8, 2). Key example: "12 | 5 = 125".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there an online stem and leaf plot maker?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. This tool provides a free online stem and leaf plot maker with support for both single-dataset and back-to-back (dual-dataset) modes. Paste your data to generate instantly — no sign-up, no watermarks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why are stem-and-leaf plots widely used in education?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the operation is simple (just split digits), the original data is preserved, and the distribution shape is immediately visible. Students who create stem-and-leaf plots by hand gain a deep understanding of data organization and the intuitive meaning of distribution.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What is a Box Plot? A Complete Guide</title>
      <dc:creator>TianYi Zhang</dc:creator>
      <pubDate>Sat, 08 Aug 2026 14:36:38 +0000</pubDate>
      <link>https://dev.to/tianyizhang/what-is-a-box-plot-a-complete-guide-29c4</link>
      <guid>https://dev.to/tianyizhang/what-is-a-box-plot-a-complete-guide-29c4</guid>
      <description>&lt;p&gt;A &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Box_plot" rel="noopener noreferrer"&gt;box plot&lt;/a&gt;&lt;/strong&gt; (also called a &lt;strong&gt;box-and-whisker plot&lt;/strong&gt;) is a standardized way of displaying the distribution of a dataset based on a &lt;strong&gt;five-number summary&lt;/strong&gt;: minimum, first quartile (Q1), median, third quartile (Q3), and maximum. Invented by statistician John Tukey, the plot gets its name from the rectangular "box" spanning the interquartile range, with "whiskers" extending outward like a cat's whiskers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Terms
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum (Min)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The smallest data point excluding outliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First Quartile (Q1)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The median of the lower half. 25% of data falls below Q1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Median (Q2)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The middle value. 50% of data falls below the median&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Third Quartile (Q3)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The median of the upper half. 75% of data falls below Q3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maximum (Max)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The largest data point excluding outliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interquartile Range (IQR)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Q3 − Q1. The width of the box, measuring the spread of the middle 50% of data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The arithmetic average of all data points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Outliers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data points beyond Q1 − 1.5×IQR or Q3 + 1.5×IQR&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Use Box Plots?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compare groups instantly.&lt;/strong&gt; Place boxes side by side to see medians, spreads, and outliers across categories at a glance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect skewness visually.&lt;/strong&gt; A lopsided box reveals asymmetry — a long upper whisker indicates right skew, a long lower whisker indicates left skew.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify outliers objectively.&lt;/strong&gt; The IQR-based Tukey fences method flags statistically unusual data points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarize large datasets compactly.&lt;/strong&gt; One box plot can represent the distribution of thousands of data points.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Worked Example
&lt;/h2&gt;

&lt;p&gt;Let's walk through a real example using exam scores. Suppose we have scores for four subjects, each with 12 students:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Math:      72, 85, 78, 90, 65, 88, 76, 92, 70, 84, 79, 86
Physics:   68, 75, 82, 70, 88, 74, 80, 66, 85, 73, 90, 72
Chemistry: 60, 68, 72, 58, 75, 70, 65, 80, 62, 74, 68, 67
Biology:   78, 85, 82, 90, 88, 84, 86, 92, 80, 87, 83, 89
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll use the &lt;strong&gt;Math&lt;/strong&gt; scores to demonstrate how each key term is calculated step by step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Sort the Data
&lt;/h3&gt;

&lt;p&gt;First, sort all values in ascending order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sorted: 65, 70, 72, 76, 78, 79, 84, 85, 86, 88, 90, 92
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Calculate Quartiles (Linear Interpolation)
&lt;/h3&gt;

&lt;p&gt;We use &lt;strong&gt;linear interpolation&lt;/strong&gt; (Hyndman &amp;amp; Fan method 7) to compute percentiles — the same method used by Python's NumPy and pandas.&lt;/p&gt;

&lt;h4&gt;
  
  
  Core Idea
&lt;/h4&gt;

&lt;p&gt;With 12 sorted data points, the "25th percentile position" may not fall exactly on a data point — it can land between two values. Linear interpolation takes the value &lt;strong&gt;proportionally between the two surrounding data points&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Computing Q1 (25th percentile)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Sorted data with indices:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index:  0   1   2   3   4   5   6   7   8   9   10  11
Value: 65  70  72  76  78  79  84  85  86  88  90  92
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Calculate the position for Q1:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The 12 data points occupy positions 0 through 11. The 25th percentile position is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;position = (12 - 1) × 0.25 = 2.75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Position 2.75 means we are between index 2 (value 72) and index 3 (value 76), at 75% of the distance from index 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interpolate proportionally:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Q1 = 72 + 0.75 × (76 - 72)
   = 72 + 0.75 × 4
   = 72 + 3
   = 75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visual diagram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index:     2              2.75             3
Value:    72 ──────────────●────────────── 76
                           ↑
                    75% of the way
                    72 + 0.75×4 = 75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Q1 = 75&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means 25% of students scored below 75 in Math.&lt;/p&gt;

&lt;h4&gt;
  
  
  Computing the Median Q2 (50th percentile)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;position = (12 - 1) × 0.5 = 5.5
lo = 5  →  sorted[5] = 79
hi = 6  →  sorted[6] = 84
fraction = 5.5 - 5 = 0.5

Median = 79 + 0.5 × (84 - 79) = 79 + 2.5 = 81.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Median = 81.5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Half the students scored below 81.5, half above.&lt;/p&gt;

&lt;h4&gt;
  
  
  Computing Q3 (75th percentile)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;position = (12 - 1) × 0.75 = 8.25
lo = 8  →  sorted[8] = 86
hi = 9  →  sorted[9] = 88
fraction = 8.25 - 8 = 0.25

Q3 = 86 + 0.25 × (88 - 86) = 86 + 0.5 = 86.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Q3 = 86.5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;75% of students scored below 86.5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Calculate the Interquartile Range (IQR)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IQR = Q3 − Q1 = 86.5 − 75 = 11.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: IQR = 11.5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The middle 50% of students' scores span 11.5 points. A larger IQR indicates greater spread in the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Calculate Outlier Fences (Tukey's Fences)
&lt;/h3&gt;

&lt;p&gt;Tukey's fences method uses 1.5 × IQR as the threshold for flagging outliers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lowerFence = 75 − 1.5 × 11.5 = 75 − 17.25 = 57.75
upperFence = 86.5 + 1.5 × 11.5 = 86.5 + 17.25 = 103.75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the original data for values below 57.75 or above 103.75:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original: 72, 85, 78, 90, 65, 88, 76, 92, 70, 84, 79, 86
All values are within [57.75, 103.75]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: No outliers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These Math scores are fairly concentrated — no extreme high or low values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Determine Whisker Endpoints (Min and Max)
&lt;/h3&gt;

&lt;p&gt;The whiskers extend to the smallest and largest non-outlier values:&lt;/p&gt;

&lt;p&gt;Since there are no outliers, the whiskers reach directly to the data extremes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Min = 65  (first sorted value)
Max = 92  (last sorted value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Calculate the Mean
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sum = 65 + 70 + 72 + 76 + 78 + 79 + 84 + 85 + 86 + 88 + 90 + 92 = 965
Mean = 965 ÷ 12 ≈ 80.42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: Mean ≈ 80.42&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note that the mean (80.42) is slightly lower than the median (81.5), indicating a mild left skew — a few lower scores pull the average down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete Calculation Code
&lt;/h2&gt;

&lt;p&gt;Here is all the logic consolidated into a single function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateBoxPlotStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Sort&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Percentile via linear interpolation&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Five-number summary&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;median&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Outlier fences&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. Mean&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 6. Outliers &amp;amp; whisker endpoints&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outliers&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mathScores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateBoxPlotStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mathScores&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   min: 65,&lt;/span&gt;
&lt;span class="c1"&gt;//   q1: 75,&lt;/span&gt;
&lt;span class="c1"&gt;//   median: 81.5,&lt;/span&gt;
&lt;span class="c1"&gt;//   q3: 86.5,&lt;/span&gt;
&lt;span class="c1"&gt;//   max: 92,&lt;/span&gt;
&lt;span class="c1"&gt;//   iqr: 11.5,&lt;/span&gt;
&lt;span class="c1"&gt;//   mean: 80.42,&lt;/span&gt;
&lt;span class="c1"&gt;//   lowerFence: 57.75,&lt;/span&gt;
&lt;span class="c1"&gt;//   upperFence: 103.75,&lt;/span&gt;
&lt;span class="c1"&gt;//   outliers: []&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Interpreting the Result
&lt;/h2&gt;

&lt;p&gt;When you plot all four subjects side by side, the comparison reveals clear patterns: &lt;strong&gt;Biology&lt;/strong&gt; scores are consistently high and tightly clustered, while &lt;strong&gt;Chemistry&lt;/strong&gt; scores are lower overall but evenly spread. This kind of multi-group comparison is exactly what makes box plots so powerful for exploratory data analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Box Plot Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3s0v2hvydmaro4j1plf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb3s0v2hvydmaro4j1plf.png" alt=" " width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart was generated by &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;https://aiboxplot.com&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Box Plot vs Other Chart Types
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chart&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Box Plot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Comparing distributions, spotting outliers&lt;/td&gt;
&lt;td&gt;Hides multi-modal patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Violin Plot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Showing distribution shape and density&lt;/td&gt;
&lt;td&gt;Less familiar to general audiences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Histogram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Revealing frequency and modality&lt;/td&gt;
&lt;td&gt;Bin width choices affect appearance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scatter Plot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exploring XY relationships&lt;/td&gt;
&lt;td&gt;Needs two numeric variables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Computation Method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quartiles:&lt;/strong&gt; Linear interpolation (Hyndman &amp;amp; Fan method 7), consistent with Python's NumPy and pandas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outliers:&lt;/strong&gt; Tukey fences — lower fence = Q1 − 1.5×IQR, upper fence = Q3 + 1.5×IQR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notched boxes:&lt;/strong&gt; 95% CI around median (±1.58 × IQR / √n), enabling visual significance testing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the minimum number of data points needed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need at least 5 data points for a box plot. For reliable outlier detection and stable quartile estimates, 20+ points per dataset is recommended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How accurate is the outlier detection?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tool uses the standard Tukey fences method (1.5 × IQR), the industry standard taught in statistics courses and used in research. You can adjust sensitivity to 2× or 3× IQR for more conservative detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is my data stored or shared?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. All computation happens locally in your browser. Data is never uploaded to any server, stored, or shared. This tool is completely private.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use exported charts in academic papers?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. SVG exports are vector graphics that scale to any resolution — ideal for publications. PNG exports at 2x resolution for sharp rendering in presentations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this tool work on mobile devices?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The interface is fully responsive and works on phones, tablets, and desktops. The chart canvas adapts to screen size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this tool really free?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, completely free. No sign-up required, no ads, no watermarks. All statistical tools are available with full functionality at zero cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hyndman, R. J. &amp;amp; Fan, Y. (1996). "Sample Quantiles in Statistical Packages." &lt;em&gt;The American Statistician&lt;/em&gt;, 50(4), 361–365.&lt;/li&gt;
&lt;li&gt;Tukey, J. W. (1977). &lt;em&gt;Exploratory Data Analysis.&lt;/em&gt; Addison-Wesley.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>statistics</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>How I Built an AI Pet Video Generator with TanStack Start and ByteDance Seedance</title>
      <dc:creator>TianYi Zhang</dc:creator>
      <pubDate>Tue, 21 Jul 2026 16:54:34 +0000</pubDate>
      <link>https://dev.to/tianyizhang/how-i-built-an-ai-pet-video-generator-with-tanstack-start-and-bytedance-seedance-25h1</link>
      <guid>https://dev.to/tianyizhang/how-i-built-an-ai-pet-video-generator-with-tanstack-start-and-bytedance-seedance-25h1</guid>
      <description>&lt;h1&gt;
  
  
  How I Built an AI Pet Video Generator with TanStack Start and ByteDance Seedance
&lt;/h1&gt;

&lt;p&gt;A few months ago I set out to build something that felt genuinely magical. Not another CRUD dashboard, not another productivity tracker. I wanted to build something my non-technical friends would actually use and enjoy.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;&lt;a href="https://petvideogenerator.com" rel="noopener noreferrer"&gt;PetVideo Generator&lt;/a&gt;&lt;/strong&gt; — you upload a photo of your cat or dog, describe what you want them to do ("my corgi surfing a giant wave at sunset"), and the app uses AI to turn that into a short cinematic video. No editing skills, no prompt engineering — just upload, describe, and download.&lt;/p&gt;

&lt;p&gt;I'll walk through the stack, why I went all-in on TanStack, how I integrated ByteDance's Seedance 2.5 for video generation, and a few sharp edges I hit along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going All-In on the TanStack Ecosystem
&lt;/h2&gt;

&lt;p&gt;I have a rule for side projects: pick a stack and commit. Half-measures — like mixing Next.js pages router with React Query and a separate form library you don't fully understand — are how projects die in week three. This time, I committed to TanStack end to end.&lt;/p&gt;

&lt;p&gt;Here's the full lineup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;TanStack piece&lt;/th&gt;
&lt;th&gt;What it replaced for me&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework &amp;amp; routing&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TanStack Start&lt;/strong&gt; (Vite 8 + Nitro)&lt;/td&gt;
&lt;td&gt;Next.js App Router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server state&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt; v5&lt;/td&gt;
&lt;td&gt;useEffect + fetch / SWR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forms&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TanStack Form&lt;/strong&gt; v1 + Zod v4&lt;/td&gt;
&lt;td&gt;React Hook Form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tables&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;TanStack Table&lt;/strong&gt; v8&lt;/td&gt;
&lt;td&gt;Hand-rolled table logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI primitives&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;shadcn/ui v4&lt;/strong&gt; + Tailwind CSS 4&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;better-auth (Drizzle adapter)&lt;/td&gt;
&lt;td&gt;NextAuth / Clerk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Drizzle ORM on PostgreSQL&lt;/td&gt;
&lt;td&gt;Prisma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i18n&lt;/td&gt;
&lt;td&gt;Paraglide JS&lt;/td&gt;
&lt;td&gt;next-intl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me go through each piece and explain why it earned its spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  TanStack Start — the Foundation
&lt;/h3&gt;

&lt;p&gt;TanStack Start is still in RC, which worried me at first. But after a weekend of building, the worry evaporated. Here's what I liked:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;File-based routing without the magic.&lt;/strong&gt; Routes live under &lt;code&gt;src/routes/&lt;/code&gt; as flat files and folders. A page at &lt;code&gt;/pricing&lt;/code&gt; is literally &lt;code&gt;src/routes/pricing.tsx&lt;/code&gt;. Route groups use parentheses like &lt;code&gt;(auth)/sign-in.tsx&lt;/code&gt; — same convention as Next.js, but without the &lt;code&gt;"use client"&lt;/code&gt; directives or RSC boundary confusion. Every component is a regular React component. If it runs on the server, you write a &lt;code&gt;loader&lt;/code&gt;. If it runs on the client, you write a component. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loaders are server-side, not magical.&lt;/strong&gt; Need to fetch data before rendering a page? Export a &lt;code&gt;loader&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPublicPlans&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;plans&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loader runs on the server during SSR and can access your database directly — no API route indirection, no &lt;code&gt;getServerSideProps&lt;/code&gt; ceremony. The returned data is serialized and available via &lt;code&gt;Route.useLoaderData()&lt;/code&gt; in your component, fully typed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nitro under the hood.&lt;/strong&gt; TanStack Start bundles with Nitro, which means your server endpoints live in &lt;code&gt;src/routes/api/&lt;/code&gt; and are just exported functions. Need a webhook handler? &lt;code&gt;src/routes/api/webhook.ts&lt;/code&gt; exports a &lt;code&gt;POST&lt;/code&gt; function. It deploys to Node.js, Cloudflare Workers, or anywhere Nitro supports — no lock-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metadata without the boilerplate.&lt;/strong&gt; Each route has a &lt;code&gt;head&lt;/code&gt; export that receives loader data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pricing — PetVideo Generator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;loaderData&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loaderData&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;loaderData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PricingPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern stayed consistent across all 35+ routes in the app. Once you learn it once, you know it everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  TanStack Query — Server State Done Right
&lt;/h3&gt;

&lt;p&gt;I used to manually manage server state with &lt;code&gt;useEffect&lt;/code&gt; + &lt;code&gt;fetch&lt;/code&gt; + &lt;code&gt;useState&lt;/code&gt; for loading/error/data. I'd eventually add a cache layer that I maintained poorly. TanStack Query eliminates all of that.&lt;/p&gt;

&lt;p&gt;For the admin panel's user list, a paginated query looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;usersQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apiGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/admin/users?page=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;search=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;placeholderData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keepPreviousData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keepPreviousData&lt;/code&gt; is quietly one of the best features — when the user clicks to page 2, the table keeps showing page 1's data until page 2 loads. No flash of empty rows, no loading spinner where data should be. It feels fast because it &lt;em&gt;is&lt;/em&gt; fast.&lt;/p&gt;

&lt;p&gt;Mutations follow the same pattern across the entire app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grantCredits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;apiPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/admin/users/credits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin-users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Credits granted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invalidate on success, toast on success, toast on error. Every mutation in the codebase follows this shape, which made the app predictable to work on even as it grew.&lt;/p&gt;

&lt;h3&gt;
  
  
  TanStack Form + Zod
&lt;/h3&gt;

&lt;p&gt;Forms are where most React apps accumulate technical debt. You start with controlled inputs, then add validation, then need field-level errors, then realize your form state is 200 lines of &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;TanStack Form takes a different approach. You define the form shape and validation schema once, and the library manages all the state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;defaultValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paired with a thin &lt;code&gt;TextField&lt;/code&gt; wrapper component that reads &lt;code&gt;form.Field&lt;/code&gt; state and renders a labeled input, every form in the app — sign in, sign up, settings, admin dialogs — follows the same structure. No per-form state management, no duplicated validation logic between client and server.&lt;/p&gt;

&lt;h3&gt;
  
  
  TanStack Table for Admin Panels
&lt;/h3&gt;

&lt;p&gt;The admin panel has several data-heavy tables: users, subscriptions, payments, credits, posts. Each one needs server-side pagination, search, and sortable columns. TanStack Table handles the headless logic — you provide the data shape, define columns, and it gives you back everything you need for rendering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;table.getRowModel().rows&lt;/code&gt; for the visible rows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;table.getHeaderGroups()&lt;/code&gt; for column headers with sort indicators&lt;/li&gt;
&lt;li&gt;Built-in pagination state that plugs into your query parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;loading&lt;/code&gt; prop on the DataTable component (which wraps TanStack Table) shows a subtle shimmer overlay without unmounting the current rows — again, no jarring loading states.&lt;/p&gt;

&lt;p&gt;The consistency across TanStack libraries is the real win. They share the same mental model — define a configuration object, get back reactive state, plug it into your UI. After learning Query, picking up Form and Table took hours, not days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with ByteDance's Seedance 2.5
&lt;/h2&gt;

&lt;p&gt;Now for the fun part — the AI that actually makes the videos.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is Seedance?
&lt;/h3&gt;

&lt;p&gt;Seedance is ByteDance's image-to-video model. If you've been following the AI video space, you've probably heard of Runway, Pika, or Kling. Seedance sits in the same category but has a few characteristics that made it a good fit for this project.&lt;/p&gt;

&lt;p&gt;Seedance 2.0 was already solid — it produced clean motion, handled pet fur and anatomy reasonably well, and didn't introduce too many visual artifacts. Seedance 2.5, which released earlier this year, brought some meaningful improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Better motion coherence.&lt;/strong&gt; Earlier image-to-video models would sometimes warp the subject — a dog's leg might stretch weirdly, or the background would shimmer. Seedance 2.5 handles motion much more naturally. Pets running, jumping, or turning their heads look like actual pets doing those things, not glitchy approximations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher temporal consistency at longer durations.&lt;/strong&gt; It supports up to 15 seconds of output at 720p. The frame-to-frame consistency at 10+ seconds is where it pulls ahead of earlier versions — fewer morphing artifacts and smoother transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt adherence.&lt;/strong&gt; The model respects camera direction cues — "slow push-in," "dolly left," "bird's eye view" — more reliably than its predecessor. This matters because cinematic camera motion is what makes the output feel like a real video rather than a animated still.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How I Integrated It
&lt;/h3&gt;

&lt;p&gt;The generation pipeline has three stages, and Seedance handles the third:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1 — Upload.&lt;/strong&gt; Users drop or paste an image. It uploads to Cloudflare R2 (S3-compatible object storage). Before anything else, a quick vision-model check confirms the image actually contains a pet — saves people from burning credits on generation attempts that'll fail or produce nonsense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2 — Prompt optimization.&lt;/strong&gt; Most people write terrible prompts. "Make my cat dance" isn't going to give Seedance enough to work with. So the app runs the user's casual description through an LLM that rewrites it into cinematic language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User types: "my golden retriever as a wizard"
System generates: "A majestic golden retriever wearing flowing wizard robes,
holding a wooden staff with a glowing crystal, standing in a candlelit stone
chamber with floating embers and magical particles, slow camera push-in,
cinematic lighting, shallow depth of field"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step takes under a second and runs server-side. The difference in output quality is night and day — Seedance responds dramatically better to detailed, cinematography-aware prompts than to casual descriptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3 — Video generation with Seedance.&lt;/strong&gt; The original photo and the optimized prompt get sent to the Seedance 2.5 endpoint. The model takes the static image and the text description and generates a video clip — typically 5 to 15 seconds, at either 480p or 720p.&lt;/p&gt;

&lt;p&gt;The generation itself takes 30–90 seconds depending on duration and resolution. The app polls for completion and saves the resulting video URL to the user's gallery. Users can preview, download, or share their videos from there.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Seedance Excels At (and Where It Struggles)
&lt;/h3&gt;

&lt;p&gt;After generating thousands of videos during development and testing, I've developed a sense for what works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pet close-ups are excellent.&lt;/strong&gt; Seedance handles fur texture, ear movement, and facial expressions well. A close-up of a cat slowly blinking and turning its head looks genuinely good — the kind of result that makes people say "how is this AI?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action at a distance is weaker.&lt;/strong&gt; If the pet is small in the frame — say, a dog running across a field — the motion can get mushy. The model has fewer pixels to work with for the subject, and the quality drops noticeably. My rule of thumb: if you can't clearly see the pet's eyes in the source photo, the result won't be great.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple backgrounds work better than complex ones.&lt;/strong&gt; A pet on grass or a plain wall generates cleaner motion than a pet in a busy living room. The model sometimes confuses background elements with the subject. When I prompt users to "use a photo with a clean, uncluttered background," the success rate jumps noticeably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Camera motion cues are genuinely useful.&lt;/strong&gt; Adding "slow zoom out," "gentle pan right," or "static camera, subject moves toward lens" to the prompt changes the output. This feels less like a gimmick and more like a fundamental capability — you can direct the shot, not just describe the scene.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Other Models?
&lt;/h3&gt;

&lt;p&gt;I evaluated a few alternatives. Runway Gen-3 produces great results but the API pricing doesn't work for a consumer SaaS with a low price point. Kling is strong on human faces but less consistent with animals. Pika is fast but resolution-limited.&lt;/p&gt;

&lt;p&gt;Seedance hit the sweet spot for this use case: good enough quality for pet content (where slight imperfections are actually charming rather than off-putting), reasonable generation speed, and a cost structure that let me offer a free tier without losing money on every user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Credit System
&lt;/h2&gt;

&lt;p&gt;This is the boring-but-critical part nobody blogs about. When you charge users in "credits" and those credits get consumed by variable-cost API calls, you need a real accounting system.&lt;/p&gt;

&lt;p&gt;A few things I learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FIFO consumption matters.&lt;/strong&gt; Users accumulate credits from multiple sources — signup bonus, subscription renewal, credit pack purchases. Each batch has its own expiration date. When a generation costs 22 credits, the system needs to consume from the batch that expires soonest. Implementing this correctly with Drizzle ORM meant writing window functions and atomic update queries, not just a &lt;code&gt;credits -= amount&lt;/code&gt; decrement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reserve-then-consume, not consume-then-refund.&lt;/strong&gt; When a user kicks off a generation, you reserve credits. If the generation succeeds, you consume them. If it fails, you release them. If you consume first and refund on failure, you create a small window where users can overdraft by submitting multiple requests simultaneously. The reserve pattern (with optimistic UI showing "pending" credit usage) prevents this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expiration is a feature, not just a cost-control mechanism.&lt;/strong&gt; Credits that expire encourage regular usage, which increases the chance users will upgrade. But be transparent about it — the dashboard shows exactly when each credit batch expires, and the reminder email goes out a week before.&lt;/p&gt;

&lt;p&gt;The whole credit module — FIFO logic, expiration, partial consumption, automated top-ups — ended up being about 400 lines of TypeScript. Not trivial, but self-contained. It imports nothing from the rest of the app except the database client and Drizzle schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monetization Without Feeling Scummy
&lt;/h2&gt;

&lt;p&gt;Pricing took more iteration than any technical decision. I studied the dominant player in this space ($15–129/month) and set a straightforward goal: every plan cheaper than the equivalent, with a genuinely usable free tier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly&lt;/th&gt;
&lt;th&gt;Credits/mo&lt;/th&gt;
&lt;th&gt;Highlights&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;1 gen + download/week&lt;/td&gt;
&lt;td&gt;No watermark, no credit card&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;$12.99&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;All modes, up to 720p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$24.99&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;Priority queue, longer durations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ultra&lt;/td&gt;
&lt;td&gt;$109.99&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;Top priority, dedicated support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The free tier gives one generation and download per week — enough to try it, share something, and decide if you want more. No card, no trial expiration countdown. If someone uses it once a week forever, that's fine — word of mouth from free users is its own kind of revenue.&lt;/p&gt;

&lt;p&gt;The paid tiers run at a thin margin on short/low-res generations but make it up on the longer, higher-resolution videos that paying users naturally gravitate toward. I also added credit packs (small one-time purchases, no subscription) and lifetime plans (one payment, permanent access) for people who prefer those models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stuff I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with webhooks, not polling.&lt;/strong&gt; I poll Seedance's job status endpoint every few seconds to check if a generation is done. For low volume this is fine. At scale, switching to webhook callbacks would cut wasteful API calls and reduce perceived latency. This is on the roadmap but would have been easier to build from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The credit system needed more upfront modeling.&lt;/strong&gt; FIFO ordering, partial consumption across multiple pools, expiration, auto-grant on signup, revocation on refunds — it's a deeper domain than it looks. Next time I'd sketch the full state machine before writing a single service function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;i18n from the start was the right call.&lt;/strong&gt; I wanted English and Chinese from day one, so I wired up Paraglide JS early. The Inlang compiler tree-shakes unused messages at build time, which is nice, but keeping 675+ translation keys in sync during rapid development is genuinely tedious. There's no great tooling for this yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building a consumer AI product is a different beast from building a dev tool or a B2B SaaS. The quality bar is higher (people compare it to Hollywood, not to a spreadsheet), the margin math is tighter (you're paying per API call, not per server-hour), and the "wow factor" is the entire product — there's no enterprise feature checklist to fall back on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://petvideogenerator.com" rel="noopener noreferrer"&gt;PetVideo Generator&lt;/a&gt;&lt;/strong&gt; is live and free to try. Upload a photo of your pet, give it a prompt, and see what Seedance 2.5 can do. No credit card, no time-limited trial — just one generation a week you can actually use and download.&lt;/p&gt;

&lt;p&gt;If you're curious about the underlying SaaS architecture — auth, payments, admin panel, the credit engine — the backend runs on &lt;strong&gt;ShipAny&lt;/strong&gt;, a headless SaaS boilerplate. Their source is on &lt;a href="https://github.com/hardhackerlabs/shipany" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and their docs are at &lt;a href="https://shipany.ai" rel="noopener noreferrer"&gt;shipany.ai&lt;/a&gt;. I mention it because a lot of indie devs burn weeks on infrastructure before they ever write a line of product code. Having that layer done meant I went from idea to working prototype in about a week.&lt;/p&gt;

&lt;p&gt;Would love to hear what you're building — or how you'd approach this differently. Drop a comment.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>tanstack</category>
      <category>ai</category>
    </item>
    <item>
      <title>Build a Box Plot Calculator in Pure JavaScript — No Libraries Needed</title>
      <dc:creator>TianYi Zhang</dc:creator>
      <pubDate>Tue, 30 Jun 2026 18:00:50 +0000</pubDate>
      <link>https://dev.to/tianyizhang/build-a-box-plot-calculator-in-pure-javascript-no-libraries-needed-3n3f</link>
      <guid>https://dev.to/tianyizhang/build-a-box-plot-calculator-in-pure-javascript-no-libraries-needed-3n3f</guid>
      <description>&lt;h1&gt;
  
  
  Build a Box Plot Calculator in Pure JavaScript — No Libraries Needed
&lt;/h1&gt;

&lt;p&gt;A box plot (or box-and-whisker plot) is one of the most powerful tools in exploratory data analysis. In one compact graphic, it reveals the median, spread, skewness, and outliers of a dataset — all from just five numbers.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll walk through building a complete box plot calculator from scratch using &lt;strong&gt;vanilla JavaScript and SVG&lt;/strong&gt;. No D3, no Chart.js, no dependencies. By the end, you'll understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to compute &lt;strong&gt;quartiles&lt;/strong&gt; the right way&lt;/li&gt;
&lt;li&gt;How to detect &lt;strong&gt;outliers&lt;/strong&gt; with Tukey's fences&lt;/li&gt;
&lt;li&gt;How to render a box plot as &lt;strong&gt;SVG&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Why all of this matters for real-world data analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to skip the code and use a ready-made tool, I built &lt;strong&gt;&lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt;&lt;/strong&gt; — a free, no-sign-up platform that does all of this plus AI analysis, multi-dataset comparison, and one-click export to PNG/SVG/CSV. The rendering engine is &lt;strong&gt;&lt;a href="https://github.com" rel="noopener noreferrer"&gt;open source&lt;/a&gt;&lt;/strong&gt; too.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it now → &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Five-Number Summary
&lt;/h2&gt;

&lt;p&gt;Every box plot is built on five values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Minimum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The smallest value that's not an outlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Q1&lt;/strong&gt; (first quartile)&lt;/td&gt;
&lt;td&gt;25th percentile — 25% of data falls below this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Median&lt;/strong&gt; (Q2)&lt;/td&gt;
&lt;td&gt;50th percentile — the middle value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Q3&lt;/strong&gt; (third quartile)&lt;/td&gt;
&lt;td&gt;75th percentile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maximum&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The largest value that's not an outlier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus one more: the &lt;strong&gt;Interquartile Range (IQR) = Q3 − Q1&lt;/strong&gt;. This measures the spread of the middle 50% and is the key to detecting outliers.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Computing Quartiles Correctly
&lt;/h2&gt;

&lt;p&gt;The most common mistake in box plot calculators is using the wrong quartile method. There are at least 9 different ways to compute percentiles (see Hyndman &amp;amp; Fan, 1996). I use &lt;strong&gt;linear interpolation&lt;/strong&gt; (method 7), which is the default in Python's NumPy and pandas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Compute a percentile value using linear interpolation.
 * @param {number[]} sorted - Sorted array of numbers
 * @param {number} p - Percentile as a fraction (0.25 = Q1, 0.5 = median, 0.75 = Q3)
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// Real-valued index&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// Linear interpolation between lo and hi&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters: for a dataset of &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt;, the median is 3. But for &lt;code&gt;[1, 2, 3, 4]&lt;/code&gt;, using simple rounding gives 2, while linear interpolation correctly gives 2.5. These small differences compound when you're analyzing real data.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Detecting Outliers with Tukey's Fences
&lt;/h2&gt;

&lt;p&gt;John Tukey's fences method is elegant, simple, and still the industry standard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;median&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Tukey's fences&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;iqrMultiplier&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Values outside fences are potential outliers&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Non-outlier range defines the whiskers&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;nonOutliers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lowerFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upperFence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;1.5&lt;/code&gt; multiplier is the standard. Use &lt;code&gt;2.0&lt;/code&gt; for a more conservative detection, or &lt;code&gt;3.0&lt;/code&gt; for extreme outliers only. At &lt;strong&gt;&lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt;&lt;/strong&gt;, you can switch between these thresholds in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Rendering as SVG
&lt;/h2&gt;

&lt;p&gt;Now for the fun part — turning numbers into a chart. Here's the core SVG rendering logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderBoxPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outliers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Scale setup&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;padL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plotW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;padL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;padR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;padB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dataMin&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paddedMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataMin&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paddedMax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataMax&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paddedRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paddedMax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;paddedMin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Map data value → SVG x-coordinate&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;padL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotW&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;paddedMin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;paddedRange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boxY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#2563eb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;svg viewBox="0 0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" xmlns="http://www.w3.org/2000/svg"&amp;gt;
      &amp;lt;!-- Axis --&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;padL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;padL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotW&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;padT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;plotH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="#d1d5db" stroke-width="0.5"/&amp;gt;

      &amp;lt;!-- Left whisker --&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="1.5" stroke-linecap="round"/&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="0.5" stroke-dasharray="3 3"/&amp;gt;

      &amp;lt;!-- Box (Q1 to Q3) --&amp;gt;
      &amp;lt;rect x="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boxY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            width="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" height="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boxH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            fill="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" opacity="0.15" stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="1.5" rx="2"/&amp;gt;

      &amp;lt;!-- Median line --&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boxY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;boxY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="2" stroke-linecap="round"/&amp;gt;

      &amp;lt;!-- Right whisker --&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="0.5" stroke-dasharray="3 3"/&amp;gt;
      &amp;lt;line x1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y1="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            x2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" y2="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;boxH&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" 
            stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="1.5" stroke-linecap="round"/&amp;gt;

      &amp;lt;!-- Outlier dots --&amp;gt;
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;circle cx="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;sx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;" cy="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;yc&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" r="4" 
            fill="none" stroke="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" stroke-width="1.5"/&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
    &amp;lt;/svg&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;strong&gt;SVG is just XML&lt;/strong&gt;. A box plot is just &lt;code&gt;&amp;lt;line&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;rect&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt; elements — no canvas, no external libraries.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Putting It All Together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example: exam scores from two classes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;74&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;83&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;77&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;69&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;74&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statsA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statsB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Class A — Median:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IQR:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Class B — Median:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;median&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IQR:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iqr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Outliers in A:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Outliers in B:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;statsB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outliers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart-a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderBoxPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statsA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart-b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderBoxPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statsB&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Side-by-side comparison instantly reveals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Class A has a higher median and wider spread&lt;/li&gt;
&lt;li&gt;No outliers in either class&lt;/li&gt;
&lt;li&gt;Class B scores are more tightly clustered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of insight takes seconds with a box plot — and pages of text to describe otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Demo to Production
&lt;/h2&gt;

&lt;p&gt;Building a working box plot is straightforward. Building a &lt;strong&gt;great&lt;/strong&gt; one takes more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-dataset comparison&lt;/strong&gt; — stacking boxes vertically with independent colors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notched boxes&lt;/strong&gt; — 95% confidence intervals around the median (McGill-Tukey-Kramer method)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter scatter overlays&lt;/strong&gt; — showing every raw data point without overlap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive scaling&lt;/strong&gt; — charts that look sharp at any screen size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export to PNG/SVG&lt;/strong&gt; — for papers, presentations, and reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered analysis&lt;/strong&gt; — automatic insights about distribution shape and skewness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt;&lt;/strong&gt; to solve all of this — with 8 chart types, AI chat, and zero sign-up required. The rendering engine behind it is &lt;strong&gt;&lt;a href="https://github.com" rel="noopener noreferrer"&gt;open source&lt;/a&gt;&lt;/strong&gt; so you can see exactly how every calculation and SVG element is constructed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Make your first box plot → &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quartiles matter&lt;/strong&gt; — use linear interpolation for correct results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tukey's fences&lt;/strong&gt; are the standard for outlier detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SVG is underrated&lt;/strong&gt; for data visualization — pure XML, no build step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Box plots compress&lt;/strong&gt; thousands of data points into one clear graphic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All computation can be client-side&lt;/strong&gt; — your data never needs to leave the browser&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Did this help? Let me know in the comments, or show me what you build! And if you want a ready-made solution for your next data project, give &lt;a href="https://aiboxplot.com" rel="noopener noreferrer"&gt;aiboxplot.com&lt;/a&gt; a try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>statistics</category>
      <category>dataviz</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
