inkplot 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.codespellignore +0 -0
  3. data/.yamllint +16 -0
  4. data/CHANGELOG.md +16 -1
  5. data/LICENSE.txt +1 -1
  6. data/README.md +43 -67
  7. data/bench/inkplot.rb +12 -3
  8. data/docs/index.html +31 -0
  9. data/examples/gallery/01-line.svg +1 -1
  10. data/examples/gallery/02-line-gaps.svg +1 -1
  11. data/examples/gallery/03-series.svg +1 -1
  12. data/examples/gallery/09-histogram.svg +1 -0
  13. data/examples/gallery/12-time.svg +1 -0
  14. data/examples/gallery/13-annotations.svg +1 -0
  15. data/examples/gallery/14-dark-theme.svg +1 -0
  16. data/examples/gallery/15-category-labels.svg +1 -0
  17. data/examples/gallery/16-custom-bins.svg +1 -0
  18. data/examples/gallery/17-connected-gaps.svg +1 -0
  19. data/examples/gallery/18-step-series.svg +1 -0
  20. data/examples/gallery/19-combined-marks.svg +1 -0
  21. data/examples/gallery/20-scatter-sizes.svg +1 -0
  22. data/examples/gallery/README.md +7 -7
  23. data/examples/gallery.rb +26 -44
  24. data/lib/inkplot/core.rb +351 -146
  25. data/lib/inkplot/renderers/png.rb +390 -0
  26. data/lib/inkplot/renderers/svg.rb +3 -1
  27. data/lib/inkplot/version.rb +1 -1
  28. data/lib/inkplot.rb +3 -3
  29. data/sig/inkplot.rbs +7 -6
  30. data/test/snapshots/inkplot/01-line.png +0 -0
  31. data/test/snapshots/inkplot/02-line-gaps.png +0 -0
  32. data/test/snapshots/inkplot/03-series.png +0 -0
  33. data/test/snapshots/inkplot/04-scatter.png +0 -0
  34. data/test/snapshots/inkplot/05-bars.png +0 -0
  35. data/test/snapshots/inkplot/06-horizontal-bars.png +0 -0
  36. data/test/snapshots/inkplot/07-grouped-bars.png +0 -0
  37. data/test/snapshots/inkplot/08-stacked-bars.png +0 -0
  38. data/test/snapshots/inkplot/09-histogram.png +0 -0
  39. data/test/snapshots/inkplot/10-area.png +0 -0
  40. data/test/snapshots/inkplot/11-step.png +0 -0
  41. data/test/snapshots/inkplot/12-time.png +0 -0
  42. data/test/snapshots/inkplot/13-annotations.png +0 -0
  43. data/test/snapshots/inkplot/14-dark-theme.png +0 -0
  44. data/test/snapshots/inkplot/15-category-labels.png +0 -0
  45. data/test/snapshots/inkplot/16-custom-bins.png +0 -0
  46. data/test/snapshots/inkplot/17-connected-gaps.png +0 -0
  47. data/test/snapshots/inkplot/18-step-series.png +0 -0
  48. data/test/snapshots/inkplot/19-combined-marks.png +0 -0
  49. data/test/snapshots/inkplot/20-scatter-sizes.png +0 -0
  50. metadata +41 -18
  51. data/examples/gallery/09-signed-stack.svg +0 -1
  52. data/examples/gallery/12-histogram.svg +0 -1
  53. data/examples/gallery/13-log-scale.svg +0 -1
  54. data/examples/gallery/14-time-axis.svg +0 -1
  55. data/examples/gallery/15-category-order.svg +0 -1
  56. data/examples/gallery/16-dark-theme.svg +0 -1
  57. data/examples/gallery/17-annotations.svg +0 -1
  58. data/examples/gallery/18-combo.svg +0 -1
  59. data/examples/gallery/19-legend-position.svg +0 -1
  60. data/examples/gallery/20-long-categories.svg +0 -1
  61. data/lib/inkplot/renderers/raster.rb +0 -322
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="60" y="40" width="562" height="266"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 60 306 L 622 306" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="52" y="310" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">0</text><path class="grid-line" d="M 60 173 L 622 173" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="52" y="177" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">5</text><path class="grid-line" d="M 60 40 L 622 40" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="52" y="44" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">10</text><text x="200.5" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">10</text><text x="341" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">20</text><text x="481.5" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">30</text><path class="axis-line" d="M 60 40 L 60 306 L 622 306" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Response time</text><text x="341" y="355" fill="#20242B" text-anchor="middle" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Milliseconds</text><text x="14" y="180" fill="#20242B" text-anchor="middle" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'" transform="rotate(-90 14 180)">Count</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><rect class="bar-mark" x="172.4" y="40" width="116.08" height="266" fill="#0072B2"/><rect class="bar-mark" x="289.48" y="226.2" width="116.08" height="79.8" fill="#0072B2"/><rect class="bar-mark" x="406.57" y="226.2" width="116.08" height="79.8" fill="#0072B2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 326 L 622 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="330" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">1</text><path class="grid-line" d="M 42 206.11 L 622 206.11" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="210.11" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">10</text><path class="grid-line" d="M 42 86.23 L 622 86.23" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="90.23" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">100</text><text x="42" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">0</text><text x="235.33" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2</text><text x="428.67" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">4</text><path class="axis-line" d="M 42 40 L 42 326 L 622 326" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Growth on a log scale</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 42 326 L 138.67 268.8 L 235.33 211.6 L 332 154.4 L 428.67 97.2 L 525.33 40" fill="none" stroke="#0072B2" stroke-width="2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="266"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 217.33 L 622 217.33" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="221.33" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">20</text><path class="grid-line" d="M 42 128.67 L 622 128.67" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="132.67" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">30</text><text x="42" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2026-09-24</text><text x="187" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2026-09-24</text><text x="332" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2026-09-24</text><text x="477" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2026-09-24</text><text x="622" y="324" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2026-09-25</text><path class="axis-line" d="M 42 40 L 42 306 L 622 306" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Hourly requests</text><text x="332" y="355" fill="#20242B" text-anchor="middle" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">UTC</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 42 288.27 L 138.67 243.93 L 235.33 261.67 L 332 181.87 L 428.67 119.8 L 525.33 155.27 L 622 57.73" fill="none" stroke="#0072B2" stroke-width="2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 326 L 622 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="330" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">98</text><path class="grid-line" d="M 42 254.5 L 622 254.5" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="258.5" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">98.5</text><path class="grid-line" d="M 42 183 L 622 183" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="187" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">99</text><path class="grid-line" d="M 42 111.5 L 622 111.5" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="115.5" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">99.5</text><path class="grid-line" d="M 42 40 L 622 40" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="44" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">100</text><path class="grid-line" d="M 138.67 40 L 138.67 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="138.67" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">dev</text><path class="grid-line" d="M 332 40 L 332 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="332" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">staging</text><path class="grid-line" d="M 525.33 40 L 525.33 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="525.33" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">production</text><path class="axis-line" d="M 42 40 L 42 326 L 622 326" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Deployment health</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 525.33 326 L 138.67 40 L 332 183" fill="none" stroke="#0072B2" stroke-width="2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#171B22"/><path class="grid-line" d="M 42 230.67 L 622 230.67" fill="none" stroke="#303744" stroke-width="1"/><text x="34" y="234.67" fill="#A9B2C0" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">20</text><path class="grid-line" d="M 42 135.33 L 622 135.33" fill="none" stroke="#303744" stroke-width="1"/><text x="34" y="139.33" fill="#A9B2C0" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">30</text><text x="187" y="344" fill="#A9B2C0" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2</text><text x="332" y="344" fill="#A9B2C0" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">4</text><text x="477" y="344" fill="#A9B2C0" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">6</text><path class="axis-line" d="M 42 40 L 42 326 L 622 326" fill="none" stroke="#A9B2C0" stroke-width="1"/><text x="320" y="22" fill="#E8ECF2" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Dark dashboard</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 114.5 306.93 L 187 249.73 L 259.5 268.8 L 332 192.53 L 404.5 211.6 L 477 135.33 L 549.5 87.67" fill="none" stroke="#0072B2" stroke-width="2"/></g><path class="rule-line" d="M 42 230.67 L 622 230.67" fill="none" stroke="orange" stroke-width="1.5" stroke-dasharray="5 4"/></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 268.8 L 622 268.8" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="272.8" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">25</text><path class="grid-line" d="M 42 211.6 L 622 211.6" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="215.6" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">30</text><path class="grid-line" d="M 42 154.4 L 622 154.4" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="158.4" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">35</text><path class="grid-line" d="M 42 97.2 L 622 97.2" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="101.2" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">40</text><text x="187" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2</text><text x="332" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">4</text><text x="477" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">6</text><path class="axis-line" d="M 42 40 L 42 326 L 622 326" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Service level objective</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 114.5 188.72 L 187 234.48 L 259.5 85.76 L 332 142.96 L 404.5 211.6 L 477 280.24 L 549.5 245.92" fill="none" stroke="#0072B2" stroke-width="2"/></g><path class="rule-line" d="M 42 154.4 L 622 154.4" fill="none" stroke="red" stroke-width="1.5" stroke-dasharray="5 4"/><path class="rule-line" d="M 332 40 L 332 326" fill="none" stroke="blue" stroke-width="1.5" stroke-dasharray="5 4"/><text x="339.25" y="74.32" fill="#626B78" text-anchor="start" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">deploy</text></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="460" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 326 L 502 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="330" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">0</text><path class="grid-line" d="M 42 230.67 L 502 230.67" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="234.67" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">20</text><path class="grid-line" d="M 42 135.33 L 502 135.33" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="139.33" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">40</text><path class="grid-line" d="M 99.5 40 L 99.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="99.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Jan</text><path class="grid-line" d="M 214.5 40 L 214.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="214.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Feb</text><path class="grid-line" d="M 329.5 40 L 329.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="329.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Mar</text><path class="grid-line" d="M 444.5 40 L 444.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="444.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Apr</text><path class="axis-line" d="M 42 40 L 42 326 L 502 326" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Sales and conversion</text><path class="mark-line" d="M 390 54 L 406 54" fill="none" stroke="#0072B2" stroke-width="2"/><text x="412" y="58" fill="#20242B" text-anchor="start" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Orders</text><path class="mark-line" d="M 390 72 L 406 72" fill="none" stroke="red" stroke-width="2"/><text x="412" y="76" fill="#20242B" text-anchor="start" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Conversion %</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><rect class="bar-mark" x="52.35" y="211.6" width="93.3" height="114.4" fill="#0072B2"/><rect class="bar-mark" x="167.35" y="183" width="93.3" height="143" fill="#0072B2"/><rect class="bar-mark" x="282.35" y="197.3" width="93.3" height="128.7" fill="#0072B2"/><rect class="bar-mark" x="397.35" y="125.8" width="93.3" height="200.2" fill="#0072B2"/></g><g class="series series-1" data-series="1"><path class="mark-line" d="M 99.5 312.65 L 214.5 311.22 L 329.5 311.7 L 444.5 307.89" fill="none" stroke="red" stroke-width="2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 380" width="640" height="380" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="306"/></clipPath></defs><rect x="0" y="0" width="640" height="380" fill="#FFFFFF"/><path class="grid-line" d="M 42 346 L 622 346" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="350" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2</text><path class="grid-line" d="M 42 244 L 622 244" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="248" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">4</text><path class="grid-line" d="M 42 142 L 622 142" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="146" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">6</text><text x="42" y="364" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">1</text><text x="187" y="364" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">2</text><text x="332" y="364" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">3</text><text x="477" y="364" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">4</text><text x="622" y="364" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">5</text><path class="axis-line" d="M 42 40 L 42 346 L 622 346" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Legend below</text><path class="mark-line" d="M 50 306 L 66 306" fill="none" stroke="#0072B2" stroke-width="2"/><text x="72" y="310" fill="#20242B" text-anchor="start" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Actual</text><path class="mark-line" d="M 50 324 L 66 324" fill="none" stroke="#E69F00" stroke-width="2"/><text x="72" y="328" fill="#20242B" text-anchor="start" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Plan</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><path class="mark-line" d="M 42 295 L 187 193 L 332 244 L 477 91 L 622 142" fill="none" stroke="#0072B2" stroke-width="2"/></g><g class="series series-1" data-series="1"><path class="mark-line" d="M 42 346 L 187 295 L 332 244 L 477 193 L 622 142" fill="none" stroke="#E69F00" stroke-width="2"/></g></g></svg>
@@ -1 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" role="img" class="inkplot"><defs><clipPath id="plot-clip"><rect x="42" y="40" width="580" height="286"/></clipPath></defs><rect x="0" y="0" width="640" height="360" fill="#FFFFFF"/><path class="grid-line" d="M 42 326 L 622 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="330" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">0</text><path class="grid-line" d="M 42 254.5 L 622 254.5" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="258.5" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">10</text><path class="grid-line" d="M 42 183 L 622 183" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="187" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">20</text><path class="grid-line" d="M 42 111.5 L 622 111.5" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="34" y="115.5" fill="#626B78" text-anchor="end" font-size="11" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">30</text><path class="grid-line" d="M 114.5 40 L 114.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="114.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">North America</text><path class="grid-line" d="M 259.5 40 L 259.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="259.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">South America</text><path class="grid-line" d="M 404.5 40 L 404.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="404.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Europe</text><path class="grid-line" d="M 549.5 40 L 549.5 326" fill="none" stroke="#E7EAF0" stroke-width="1"/><text x="549.5" y="344" fill="#626B78" text-anchor="middle" font-size="10" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Asia Pacific</text><path class="axis-line" d="M 42 40 L 42 326 L 622 326" fill="none" stroke="#626B78" stroke-width="1"/><text x="320" y="22" fill="#20242B" text-anchor="middle" font-size="15" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif, 'Noto Sans CJK JP'">Labels rotate when needed</text><g class="series-marks" clip-path="url(#plot-clip)"><g class="series series-0" data-series="0"><rect class="bar-mark" x="55.05" y="168.7" width="117.9" height="157.3" fill="#0072B2"/><rect class="bar-mark" x="200.05" y="225.9" width="117.9" height="100.1" fill="#0072B2"/><rect class="bar-mark" x="345.05" y="104.35" width="117.9" height="221.65" fill="#0072B2"/><rect class="bar-mark" x="490.05" y="132.95" width="117.9" height="193.05" fill="#0072B2"/></g></g></svg>
@@ -1,322 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Inkplot
4
- module Renderers
5
- module Raster
6
- module_function
7
-
8
- def render(scene, scale: 1)
9
- scale = Integer(scale)
10
- raise ArgumentError, "scale must be a positive integer" unless scale.positive?
11
-
12
- begin
13
- require "tessel"
14
- rescue LoadError => e
15
- raise LoadError, "PNG output needs tessel; install it with `gem install tessel` (#{e.message})"
16
- end
17
- begin
18
- require "glyphic"
19
- rescue LoadError => e
20
- raise LoadError, "PNG output needs glyphic; install it with `gem install glyphic` (#{e.message})"
21
- end
22
-
23
- factor = scale * 2 # ponytail: 2× coverage sampling; raise this if raster diffs show visible edge artifacts.
24
- width = scene.width * scale
25
- height = scene.height * scale
26
- canvas = Tessel::Image.new(width * 2, height * 2, fill: color(scene.background))
27
- scene.elements.each { |element| draw_shape(canvas, element, factor) }
28
- clip = scene.clip.map { |value| value * factor }
29
- seen = {}
30
- scene.marks.each do |mark|
31
- if mark[:type] == :circle
32
- key = [mark[:series_index], (mark[:cx] * scale).round, (mark[:cy] * scale).round]
33
- next if seen[key]
34
-
35
- seen[key] = true
36
- end
37
- draw_shape(canvas, mark, factor, clip: clip)
38
- end
39
- image = downsample(canvas, width, height)
40
- draw_labels(image, scene.elements + scene.marks.select { |mark| mark[:type] == :text }, scale)
41
- image
42
- end
43
-
44
- def draw_shape(image, shape, factor, clip: nil)
45
- case shape[:type]
46
- when :rect
47
- rectangle(image, shape, factor, clip)
48
- when :line
49
- Stroker.stroke(image, shape[:points], color(shape[:stroke]), factor, (shape[:width] || 1) * factor, dash: shape[:dash], clip: clip)
50
- when :polygon
51
- rgba = color(shape[:fill])
52
- rgba[3] = (rgba[3] * (shape[:opacity] || 1)).round
53
- PathFiller.fill(image, shape[:points].map { |x, y| [x * factor, y * factor] }, rgba, clip: clip)
54
- when :circle
55
- cx, cy, radius = shape.values_at(:cx, :cy, :r).map { |value| value * factor }
56
- Circle.fill(image, cx, cy, radius, color(shape[:fill]), clip: clip)
57
- Circle.stroke(image, cx, cy, radius, color(shape[:stroke]), [shape[:stroke_width].to_f * factor, 1].max, clip: clip) if shape[:stroke]
58
- end
59
- end
60
- private_class_method :draw_shape
61
-
62
- def rectangle(image, shape, factor, clip)
63
- x, y, width, height = shape.values_at(:x, :y, :width, :height).map { |value| (value * factor).round }
64
- x0 = x
65
- y0 = y
66
- x1 = x + width
67
- y1 = y + height
68
- if clip
69
- x0 = [x0, clip[0].floor].max
70
- y0 = [y0, clip[1].floor].max
71
- x1 = [x1, clip[2].ceil].min
72
- y1 = [y1, clip[3].ceil].min
73
- end
74
- image.fill_rect(x0, y0, [x1 - x0, 0].max, [y1 - y0, 0].max, color(shape[:fill]), blend: :alpha)
75
- end
76
- private_class_method :rectangle
77
-
78
- def color(value)
79
- named = {
80
- "red" => [220, 40, 40], "blue" => [30, 90, 210], "green" => [0, 140, 80], "black" => [0, 0, 0], "white" => [255, 255, 255],
81
- "orange" => [230, 145, 0], "gray" => [128, 128, 128], "grey" => [128, 128, 128], "purple" => [128, 0, 128], "yellow" => [255, 255, 0],
82
- "cyan" => [0, 255, 255], "aqua" => [0, 255, 255], "magenta" => [255, 0, 255], "fuchsia" => [255, 0, 255], "pink" => [255, 192, 203],
83
- "brown" => [165, 42, 42], "navy" => [0, 0, 128], "teal" => [0, 128, 128], "lime" => [0, 255, 0], "maroon" => [128, 0, 0],
84
- "olive" => [128, 128, 0], "silver" => [192, 192, 192], "rebeccapurple" => [102, 51, 153], "transparent" => [0, 0, 0, 0]
85
- }
86
- return named.fetch(value.to_s.downcase) { [32, 36, 43] }.then { |channels| channels.length == 3 ? channels + [255] : channels } unless value.to_s.start_with?("#")
87
-
88
- hex = value.delete_prefix("#")
89
- hex = hex.chars.map { |digit| digit * 2 }.join if [3, 4].include?(hex.length)
90
- channels = hex.scan(/../).map { |channel| channel.to_i(16) }
91
- channels << 255 if channels.length == 3
92
- channels
93
- end
94
- private_class_method :color
95
-
96
- def downsample(source, width, height)
97
- input = source.bytes
98
- output = String.new(capacity: width * height * 4, encoding: Encoding::BINARY)
99
- height.times do |y|
100
- width.times do |x|
101
- 4.times do |channel|
102
- total = 0
103
- 2.times do |dy|
104
- 2.times do |dx|
105
- total += input.getbyte((((((y * 2) + dy) * source.width) + (x * 2) + dx) * 4) + channel)
106
- end
107
- end
108
- output << (total / 4).round
109
- end
110
- end
111
- end
112
- Tessel::Image.from_rgba(width, height, output)
113
- end
114
- private_class_method :downsample
115
-
116
- def draw_labels(image, elements, scale)
117
- font = TextMetrics.font_for(12 * scale)
118
- bitmap = Inkplot.config.font.nil?
119
- warned = false
120
- elements.each do |element|
121
- next unless element[:type] == :text
122
-
123
- text = element[:text].to_s
124
- if bitmap && !text.ascii_only? && !warned
125
- warn "Inkplot's built-in bitmap font is ASCII-only; set Inkplot.config.font to a TrueType font for Unicode labels."
126
- warned = true
127
- end
128
- label = text.encode("ASCII", invalid: :replace, undef: :replace, replace: "?") if bitmap
129
- label ||= text
130
- draw_text(image, font, label, element, scale)
131
- end
132
- end
133
- private_class_method :draw_labels
134
-
135
- def draw_text(image, font, text, element, scale)
136
- fill = color(element[:fill])
137
- anchor_x = (element[:x] * scale).round
138
- baseline_y = (element[:y] * scale).round
139
- glyph = font.render(text, color: fill)
140
- bitmap = Inkplot.config.font.nil?
141
- glyph = glyph.scale_nearest(glyph.width * scale, glyph.height * scale) if bitmap && scale > 1
142
- ascent = font.ascent * (bitmap ? scale : 1)
143
- pivot_x = case element[:anchor]
144
- when :middle then glyph.width / 2.0
145
- when :end then glyph.width.to_f
146
- else 0.0
147
- end
148
- if element[:rotate]
149
- pivot_y = ascent.to_f
150
- radians = element[:rotate] * Math::PI / 180
151
- source = glyph.bytes
152
- glyph.height.times do |gy|
153
- glyph.width.times do |gx|
154
- offset = ((gy * glyph.width) + gx) * 4
155
- alpha = source.getbyte(offset + 3)
156
- next if alpha.zero?
157
-
158
- dx = gx - pivot_x
159
- dy = gy - pivot_y
160
- px = (anchor_x + (dx * Math.cos(radians)) - (dy * Math.sin(radians))).round
161
- py = (baseline_y + (dx * Math.sin(radians)) + (dy * Math.cos(radians))).round
162
- blend_pixel(image, px, py, [fill[0], fill[1], fill[2], alpha])
163
- end
164
- end
165
- else
166
- image.blit(glyph, anchor_x - pivot_x.round, baseline_y - ascent, blend: :alpha)
167
- end
168
- end
169
- private_class_method :draw_text
170
-
171
- def blend_pixel(image, x, y, rgba)
172
- return unless x.between?(0, image.width - 1) && y.between?(0, image.height - 1)
173
-
174
- image.fill_rect(x, y, 1, 1, rgba, blend: :alpha)
175
- end
176
- private_class_method :blend_pixel
177
-
178
- module PathFiller
179
- module_function
180
-
181
- def fill(image, points, rgba, clip: nil)
182
- return if points.length < 3
183
-
184
- low = [points.map { |point| point[1] }.min.floor, clip ? clip[1].floor : 0, 0].max
185
- high = [points.map { |point| point[1] }.max.ceil, clip ? clip[3].ceil : image.height, image.height].min
186
- (low...high).each do |y|
187
- scan = y + 0.5
188
- crossings = points.each_with_index.filter_map do |point, index|
189
- next_point = points[(index + 1) % points.length]
190
- x1, y1 = point
191
- x2, y2 = next_point
192
- next if (y1 <= scan && y2 <= scan) || (y1 > scan && y2 > scan)
193
-
194
- [(x1 + ((scan - y1) * (x2 - x1) / (y2 - y1))), y2 > y1 ? 1 : -1]
195
- end.sort_by(&:first)
196
- winding = 0
197
- previous_x = nil
198
- crossings.each do |crossing_x, direction|
199
- fill_span(image, previous_x, crossing_x, y, rgba, clip) if winding != 0 && previous_x
200
- winding += direction
201
- previous_x = crossing_x
202
- end
203
- end
204
- end
205
-
206
- def fill_span(image, x1, x2, y, rgba, clip)
207
- left, right = [x1, x2].minmax
208
- left = [left.ceil, clip ? clip[0].ceil : 0].max
209
- right = [right.floor, clip ? clip[2].floor : image.width].min
210
- (left...right).each { |x| image[x, y] = rgba if rgba[3] == 255 }
211
- return if rgba[3] == 255
212
-
213
- (left...right).each { |x| image.fill_rect(x, y, 1, 1, rgba, blend: :alpha) }
214
- end
215
- private_class_method :fill_span
216
- end
217
-
218
- module Stroker
219
- module_function
220
-
221
- def stroke(image, points, rgba, factor, width, dash: false, clip: nil)
222
- return if points.length < 2
223
-
224
- return stroke_dashed(image, points, rgba, factor, width, clip) if dash
225
-
226
- scaled_points = points.map { |x, y| [(x * factor).round, (y * factor).round] }
227
- scaled_points.each_cons(2) do |(x0, y0), (x1, y1)|
228
- dx = x1 - x0
229
- dy = y1 - y0
230
- length = Math.hypot(dx, dy)
231
- next if length.zero?
232
-
233
- offset_x = -dy * width / (2 * length)
234
- offset_y = dx * width / (2 * length)
235
- polygon = [[x0 + offset_x, y0 + offset_y], [x1 + offset_x, y1 + offset_y], [x1 - offset_x, y1 - offset_y], [x0 - offset_x, y0 - offset_y]]
236
- PathFiller.fill(image, polygon, rgba, clip: clip)
237
- end
238
- scaled_points.each { |x, y| Circle.fill(image, x, y, width / 2.0, rgba, clip: clip) }
239
- end
240
-
241
- def stroke_dashed(image, points, rgba, factor, width, clip)
242
- dash_length = [width.round * 3, 6].max
243
- distance = 0
244
- points.each_cons(2) do |start_point, end_point|
245
- x0, y0 = start_point.map { |value| (value * factor).round }
246
- x1, y1 = end_point.map { |value| (value * factor).round }
247
- dx = (x1 - x0).abs
248
- dy = -(y1 - y0).abs
249
- sx = x0 < x1 ? 1 : -1
250
- sy = y0 < y1 ? 1 : -1
251
- error = dx + dy
252
- loop do
253
- draw_brush(image, x0, y0, width, rgba, clip) unless (distance / dash_length).odd?
254
- break if x0 == x1 && y0 == y1
255
-
256
- twice = 2 * error
257
- if twice >= dy
258
- error += dy
259
- x0 += sx
260
- end
261
- if twice <= dx
262
- error += dx
263
- y0 += sy
264
- end
265
- distance += 1
266
- end
267
- end
268
- end
269
-
270
- def draw_brush(image, x, y, width, rgba, clip)
271
- radius = [width / 2.0, 0.5].max
272
- (y - radius).floor.upto((y + radius).ceil) do |py|
273
- (x - radius).floor.upto((x + radius).ceil) do |px|
274
- next if ((px - x)**2) + ((py - y)**2) > radius**2
275
- next unless px.between?(0, image.width - 1) && py.between?(0, image.height - 1)
276
- next if clip && (px < clip[0] || px > clip[2] || py < clip[1] || py > clip[3])
277
-
278
- image.fill_rect(px, py, 1, 1, rgba, blend: :alpha)
279
- end
280
- end
281
- end
282
- private_class_method :draw_brush
283
- end
284
-
285
- module Circle
286
- module_function
287
-
288
- def fill(image, cx, cy, radius, rgba, clip: nil)
289
- low_y = [(cy - radius).floor, clip ? clip[1].floor : 0, 0].max
290
- high_y = [(cy + radius).ceil, clip ? clip[3].ceil : image.height - 1, image.height - 1].min
291
- (low_y..high_y).each do |y|
292
- span = Math.sqrt([(radius**2) - ((y - cy)**2), 0].max)
293
- left = [(cx - span).floor, clip ? clip[0].floor : 0, 0].max
294
- right = [(cx + span).ceil, clip ? clip[2].ceil : image.width - 1, image.width - 1].min
295
- image.fill_rect(left, y, right - left + 1, 1, rgba, blend: :alpha) if left <= right
296
- end
297
- end
298
-
299
- def stroke(image, cx, cy, radius, rgba, width, clip: nil)
300
- outer = radius + (width / 2.0)
301
- inner = [radius - (width / 2.0), 0].max
302
- low_y = [(cy - outer).floor, clip ? clip[1].floor : 0, 0].max
303
- high_y = [(cy + outer).ceil, clip ? clip[3].ceil : image.height - 1, image.height - 1].min
304
- (low_y..high_y).each do |y|
305
- dy = y - cy
306
- outer_span = Math.sqrt([(outer**2) - (dy**2), 0].max)
307
- outer_left = [(cx - outer_span).floor, clip ? clip[0].floor : 0, 0].max
308
- outer_right = [(cx + outer_span).ceil, clip ? clip[2].ceil : image.width - 1, image.width - 1].min
309
- inner_span = inner.zero? || dy.abs >= inner ? 0 : Math.sqrt((inner**2) - (dy**2))
310
- inner_left = (cx - inner_span).ceil
311
- inner_right = (cx + inner_span).floor
312
- left_width = [inner_left - outer_left, outer_right - outer_left + 1].min
313
- image.fill_rect(outer_left, y, left_width, 1, rgba, blend: :alpha) if left_width.positive?
314
- right_start = [inner_right + 1, outer_left].max
315
- right_width = outer_right - right_start + 1
316
- image.fill_rect(right_start, y, right_width, 1, rgba, blend: :alpha) if right_width.positive?
317
- end
318
- end
319
- end
320
- end
321
- end
322
- end