diff --git a/README.md b/README.md
index 869bb71..f99b311 100644
--- a/README.md
+++ b/README.md
@@ -23,31 +23,47 @@ composer require maantje/charts
Below are some examples of the types of charts you can create using this library. Click on the links to view the source code for each example.
-### Simple line chart
-
-[View source](./examples/simple-line-chart.php)
-
-### Curved line chart
-
-[View source](./examples/simple-curved-chart.php)
+- [Simple Line Chart](#simple-line-chart)
+- [Curved Line Chart](#curved-line-chart)
+- [Step Line Chart](#step-line-chart)
+- [Bar Chart](#bar-chart)
+- [Stacked Bar Chart](#stacked-bar-chart)
+- [Grouped Bar Chart](#grouped-bar-chart)
+- [Advanced Line Chart](#advanced-line-chart)
+- [Advanced Bar Chart](#advanced-bar-chart)
+- [Mixed Chart](#mixed-chart)
+- [Pie Chart](#pie-chart)
+
+
+### Simple Line Chart
+
+[View source](./examples/line-chart.php)
+
+### Curved Line Chart
+
+[View source](./examples/curved-line-chart.php)
### Step line chart
-
-[View source](./examples/simple-step-chart.php)
+
+[View source](./examples/step-line-chart.php)
+
+### Bar Chart
+
+[View source](./examples/bar-chart.php)
-### Simple bar chart
-
-[View source](./examples/simple-bar-chart.php)
+### Stacked Bar Chart
+
+[View source](./examples/stacked-bar-chart.php)
-### Simple stacked chart
-
-[View source](./examples/simple-stacked-bar-chart.php)
+### Grouped Bar Chart
+
+[View source](./examples/grouped-bar-chart.php)
-### Advanced line charts
+### Advanced Line Chart

[View source](./examples/advanced-line-chart.php)
-### Advanced bar chart
+### Advanced Bar Chart

[View source](./examples/advanced-bar-chart.php)
diff --git a/examples/simple-bar-chart.php b/examples/bar-chart.php
similarity index 100%
rename from examples/simple-bar-chart.php
rename to examples/bar-chart.php
diff --git a/examples/simple-curved-chart.php b/examples/curved-line-chart.php
similarity index 100%
rename from examples/simple-curved-chart.php
rename to examples/curved-line-chart.php
diff --git a/examples/grouped-bar-chart.php b/examples/grouped-bar-chart.php
new file mode 100644
index 0000000..c7afd25
--- /dev/null
+++ b/examples/grouped-bar-chart.php
@@ -0,0 +1,140 @@
+render();
diff --git a/examples/simple-line-chart.php b/examples/line-chart.php
similarity index 100%
rename from examples/simple-line-chart.php
rename to examples/line-chart.php
diff --git a/examples/output/simple-bar-chart.svg b/examples/output/bar-chart.svg
similarity index 100%
rename from examples/output/simple-bar-chart.svg
rename to examples/output/bar-chart.svg
diff --git a/examples/output/simple-curved-chart.svg b/examples/output/curved-line-chart.svg
similarity index 100%
rename from examples/output/simple-curved-chart.svg
rename to examples/output/curved-line-chart.svg
diff --git a/examples/output/grouped-bar-chart.svg b/examples/output/grouped-bar-chart.svg
new file mode 100644
index 0000000..1a39ec8
--- /dev/null
+++ b/examples/output/grouped-bar-chart.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/examples/output/simple-line-chart.svg b/examples/output/line-chart.svg
similarity index 100%
rename from examples/output/simple-line-chart.svg
rename to examples/output/line-chart.svg
diff --git a/examples/output/simple-stacked-bar-chart.svg b/examples/output/stacked-bar-chart.svg
similarity index 100%
rename from examples/output/simple-stacked-bar-chart.svg
rename to examples/output/stacked-bar-chart.svg
diff --git a/examples/output/simple-step-chart.svg b/examples/output/step-line-chart.svg
similarity index 100%
rename from examples/output/simple-step-chart.svg
rename to examples/output/step-line-chart.svg
diff --git a/examples/simple-stacked-bar-chart.php b/examples/stacked-bar-chart.php
similarity index 100%
rename from examples/simple-stacked-bar-chart.php
rename to examples/stacked-bar-chart.php
diff --git a/examples/simple-step-chart.php b/examples/step-line-chart.php
similarity index 100%
rename from examples/simple-step-chart.php
rename to examples/step-line-chart.php
diff --git a/src/Bar/Bar.php b/src/Bar/Bar.php
index 0310a1c..9b7c712 100644
--- a/src/Bar/Bar.php
+++ b/src/Bar/Bar.php
@@ -10,13 +10,14 @@
class Bar implements BarContract
{
public function __construct(
- public string $name,
- public float $value,
+ public ?string $name = null,
+ public float $value = 0,
public ?string $yAxis = null,
public string $color = '#3498db',
public ?float $width = 100,
public string $labelColor = '#333',
- public int $labelMarginY = 30
+ public int $labelMarginY = 30,
+ public ?int $radius = null,
) {}
public function render(Chart $chart, float $x, float $maxBarWidth): string
@@ -37,9 +38,11 @@ public function render(Chart $chart, float $x, float $maxBarWidth): string
width: $width,
height: $chart->bottom() - $y,
fill: $this->color,
- title: $this->value
+ rx: $this->radius ?? 0,
+ ry: $this->radius ?? 0,
+ title: $this->value,
),
- new Text(
+ $this->name ? new Text(
content: $this->name,
x: $labelX,
y: $chart->bottom() + $this->labelMarginY,
@@ -47,7 +50,7 @@ public function render(Chart $chart, float $x, float $maxBarWidth): string
fontSize: $chart->fontSize,
fill: $this->labelColor,
textAnchor: 'middle'
- ),
+ ) : null,
]);
}
diff --git a/src/Bar/BarGroup.php b/src/Bar/BarGroup.php
new file mode 100644
index 0000000..12be59c
--- /dev/null
+++ b/src/Bar/BarGroup.php
@@ -0,0 +1,102 @@
+radius)) {
+ return;
+ }
+
+ foreach ($this->bars as $bar) {
+ if (is_null($bar->radius)) {
+ $bar->radius = $this->radius;
+ }
+ }
+ }
+
+ public function maxValue(): float
+ {
+ if (count($this->bars) === 0) {
+ return 0;
+ }
+
+ return max(array_map(fn (BarContract $bar) => $bar->value(), $this->bars));
+ }
+
+ public function minValue(): float
+ {
+ if (count($this->bars) === 0) {
+ return 0;
+ }
+
+ return min(array_map(fn (BarContract $bar) => $bar->value(), $this->bars));
+ }
+
+ public function render(Chart $chart, float $x, float $maxGroupWidth): string
+ {
+ $numBars = count($this->bars);
+ $barWidth = 0;
+
+ if ($numBars > 0) {
+ $barWidth = min($maxGroupWidth, $this->width) / $numBars;
+ }
+
+ $labelX = $x + ($maxGroupWidth / 2);
+ $startX = $x;
+ $x += ($maxGroupWidth / 2) - (($barWidth + $this->margin) * $numBars / 2);
+
+ return new Fragment([
+ new Line(
+ x1: $startX,
+ y1: $chart->bottom(),
+ x2: $startX,
+ y2: $chart->bottom() + 10,
+ ),
+ ...array_map(function (BarContract $bar) use ($barWidth, &$x, $chart) {
+ $svg = $bar->render($chart, $x + $this->margin, $barWidth);
+ $x += $this->margin + $barWidth;
+
+ return $svg;
+ }, $this->bars),
+ new Line(
+ x1: $startX + $maxGroupWidth,
+ y1: $chart->bottom(),
+ x2: $startX + $maxGroupWidth,
+ y2: $chart->bottom() + 10,
+ ),
+ new Text(
+ content: $this->name,
+ x: $labelX,
+ y: $chart->bottom() + $this->labelMarginY,
+ fontFamily: $chart->fontFamily,
+ fontSize: $this->fontSize ?? $chart->fontSize,
+ fill: $this->labelColor,
+ textAnchor: 'middle'
+ ),
+ ]);
+ }
+
+ public function value(): float
+ {
+ return $this->maxValue();
+ }
+}
diff --git a/src/SVG/Fragment.php b/src/SVG/Fragment.php
index 2387e0c..a2e5017 100644
--- a/src/SVG/Fragment.php
+++ b/src/SVG/Fragment.php
@@ -7,7 +7,7 @@
readonly class Fragment implements Stringable
{
/**
- * @param string[] $children
+ * @param (string|null)[] $children
*/
public function __construct(public array $children)
{
@@ -16,6 +16,6 @@ public function __construct(public array $children)
public function __toString(): string
{
- return implode(PHP_EOL, $this->children);
+ return implode(PHP_EOL, array_filter($this->children, fn (?string $item) => $item !== null));
}
}
diff --git a/tests/Unit/GroupedBarChartTest.php b/tests/Unit/GroupedBarChartTest.php
new file mode 100644
index 0000000..baf8071
--- /dev/null
+++ b/tests/Unit/GroupedBarChartTest.php
@@ -0,0 +1,234 @@
+render();
+
+ expect(pretty($chart->render()))->toBe(<<<'SVG'
+
+SVG
+ );
+});
+
+it('renders empty grouped bar chart', function () {
+ $chart = new Chart(
+ yAxis: new YAxis(
+ minValue: 0,
+ maxValue: 1000,
+ ),
+ series: [
+ new Bars(
+ bars: [
+ new BarGroup(
+ name: 'January',
+ bars: [],
+ ),
+ ],
+ ),
+ ],
+ );
+
+ expect(pretty($chart->render()))->toBe(<<<'SVG'
+
+SVG
+ );
+});