diff --git a/resources/views/app/bubble.blade.php b/resources/views/app/bubble.blade.php
new file mode 100644
index 0000000000000000000000000000000000000000..5a5ef5b406033938e00eaa882f0fca31c41d6876
--- /dev/null
+++ b/resources/views/app/bubble.blade.php
@@ -0,0 +1,63 @@
+@extends('layouts.app')
+
+@section('content')
+
+<svg width="1200" height="800"></svg>
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.1.1/d3.min.js" integrity="sha512-COTaPOlz12cG4fSfcBsxZsjauBAyldqp+8FQUM/dZHm+ts/jR4AFoJhCqxy8K10Jrf3pojfsbq7fAPTb1XaVkg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+<script>
+    var url = "/app/ranking/detection.example/json";
+    d3.json(url).then( data => {
+        console.log(data);
+
+        var colors = d3.scaleLinear().domain([0, 1]).range(["blue", "orange"]);
+
+        var svg = d3.select("svg");
+        var width = +svg.attr("width"),
+        height = +svg.attr("height");
+
+        svg.attr("font-size", 11)
+            .attr("font-family", "sans-serif")
+            .attr("text-anchor", "middle");
+
+
+        pack = data => d3.pack()
+            .size([width - 2, height - 2])
+            .padding(3)
+          (d3.hierarchy({children: data})
+            .sum(d => d.score))
+
+        const root = pack(data);
+        console.log(root);
+
+        const leaf = svg.selectAll("g")
+            .data(root.leaves())
+            .join("g")
+            .attr("transform", d => `translate(${d.x + 1},${d.y + 1})`);
+
+        leaf.append("circle")
+            .attr("id", d => (d.leafUid = d.data.id))
+            .attr("r", d => d.r)
+            .attr("fill-opacity", 0.7)
+            .attr("fill", d => colors(d.data.score));
+
+        leaf.append("clipPath")
+            .attr("id", d => (d.clipUid = "clip-" + d.data.id))
+            .append("use")
+            .attr("xlink:href", d => d.leafUid.href);
+
+        leaf.append("text")
+            .attr("clip-path", d => d.clipUid)
+            .selectAll("tspan")
+            .data(d => d.data.subject.name.split(/(?=[A-Z][a-z])|\s+/g))
+            .join("tspan")
+                .attr("x", 0)
+                .attr("y", (d, i, nodes) => `${i - nodes.length / 2 + 0.8}em`)
+                .text(d => d);
+
+        leaf.append("title")
+            .text(d => d.score);
+
+    });
+</script>
+@endsection
\ No newline at end of file
diff --git a/routes/web.php b/routes/web.php
index d0098e8b79754b232890fa38a94fbe5cf930c589..c26ae68d5ef5f067610dcc48209d69862d3d37cc 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -41,6 +41,7 @@ Route::prefix('app')
             Route::get('ranking/{label}', 'MarkController@ranking');
             Route::get('ranking/{label}/csv', 'MarkController@rankingCSV');
             Route::get('ranking/{label}/json', 'MarkController@rankingJSON');
+            Route::get('ranking/{label}/bubble', function() {return view('app.bubble'); });
 
             Route::get('evidence/{id}/data/{data_id}', 'MarkController@evidenceData');
             Route::get('evidence/{id}', 'MarkController@evidence');