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.
- checksums.yaml +4 -4
- data/.codespellignore +0 -0
- data/.yamllint +16 -0
- data/CHANGELOG.md +16 -1
- data/LICENSE.txt +1 -1
- data/README.md +43 -67
- data/bench/inkplot.rb +12 -3
- data/docs/index.html +31 -0
- data/examples/gallery/01-line.svg +1 -1
- data/examples/gallery/02-line-gaps.svg +1 -1
- data/examples/gallery/03-series.svg +1 -1
- data/examples/gallery/09-histogram.svg +1 -0
- data/examples/gallery/12-time.svg +1 -0
- data/examples/gallery/13-annotations.svg +1 -0
- data/examples/gallery/14-dark-theme.svg +1 -0
- data/examples/gallery/15-category-labels.svg +1 -0
- data/examples/gallery/16-custom-bins.svg +1 -0
- data/examples/gallery/17-connected-gaps.svg +1 -0
- data/examples/gallery/18-step-series.svg +1 -0
- data/examples/gallery/19-combined-marks.svg +1 -0
- data/examples/gallery/20-scatter-sizes.svg +1 -0
- data/examples/gallery/README.md +7 -7
- data/examples/gallery.rb +26 -44
- data/lib/inkplot/core.rb +351 -146
- data/lib/inkplot/renderers/png.rb +390 -0
- data/lib/inkplot/renderers/svg.rb +3 -1
- data/lib/inkplot/version.rb +1 -1
- data/lib/inkplot.rb +3 -3
- data/sig/inkplot.rbs +7 -6
- data/test/snapshots/inkplot/01-line.png +0 -0
- data/test/snapshots/inkplot/02-line-gaps.png +0 -0
- data/test/snapshots/inkplot/03-series.png +0 -0
- data/test/snapshots/inkplot/04-scatter.png +0 -0
- data/test/snapshots/inkplot/05-bars.png +0 -0
- data/test/snapshots/inkplot/06-horizontal-bars.png +0 -0
- data/test/snapshots/inkplot/07-grouped-bars.png +0 -0
- data/test/snapshots/inkplot/08-stacked-bars.png +0 -0
- data/test/snapshots/inkplot/09-histogram.png +0 -0
- data/test/snapshots/inkplot/10-area.png +0 -0
- data/test/snapshots/inkplot/11-step.png +0 -0
- data/test/snapshots/inkplot/12-time.png +0 -0
- data/test/snapshots/inkplot/13-annotations.png +0 -0
- data/test/snapshots/inkplot/14-dark-theme.png +0 -0
- data/test/snapshots/inkplot/15-category-labels.png +0 -0
- data/test/snapshots/inkplot/16-custom-bins.png +0 -0
- data/test/snapshots/inkplot/17-connected-gaps.png +0 -0
- data/test/snapshots/inkplot/18-step-series.png +0 -0
- data/test/snapshots/inkplot/19-combined-marks.png +0 -0
- data/test/snapshots/inkplot/20-scatter-sizes.png +0 -0
- metadata +41 -18
- data/examples/gallery/09-signed-stack.svg +0 -1
- data/examples/gallery/12-histogram.svg +0 -1
- data/examples/gallery/13-log-scale.svg +0 -1
- data/examples/gallery/14-time-axis.svg +0 -1
- data/examples/gallery/15-category-order.svg +0 -1
- data/examples/gallery/16-dark-theme.svg +0 -1
- data/examples/gallery/17-annotations.svg +0 -1
- data/examples/gallery/18-combo.svg +0 -1
- data/examples/gallery/19-legend-position.svg +0 -1
- data/examples/gallery/20-long-categories.svg +0 -1
- 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
|