From 46bff050bbb7d2b44a515780926ba1ccb1e96211 Mon Sep 17 00:00:00 2001 From: Michele Cereda Date: Sat, 25 May 2024 14:38:53 +0200 Subject: [PATCH] chore(techradar): save experimentation with zalando's techradar --- containers/techradar/zalando/README.md | 13 + .../techradar/zalando/docker-compose.yml | 13 + containers/techradar/zalando/index.html | 62 +++ containers/techradar/zalando/radar.css | 23 + containers/techradar/zalando/radar.js | 474 ++++++++++++++++++ containers/techradar/zalando/radar.json | 41 ++ 6 files changed, 626 insertions(+) create mode 100644 containers/techradar/zalando/README.md create mode 100644 containers/techradar/zalando/docker-compose.yml create mode 100644 containers/techradar/zalando/index.html create mode 100644 containers/techradar/zalando/radar.css create mode 100644 containers/techradar/zalando/radar.js create mode 100644 containers/techradar/zalando/radar.json diff --git a/containers/techradar/zalando/README.md b/containers/techradar/zalando/README.md new file mode 100644 index 0000000..95bfcaa --- /dev/null +++ b/containers/techradar/zalando/README.md @@ -0,0 +1,13 @@ +# Tech radar + +It **is** [Zalando's tech radar], just customized. + +Serves a static page. + +There are **4** quadrants.
+Quadrants start from the **bottom right** and go clockwise: `0` is _bottom right_, `1` is _bottom left_, `2` is +_top left_, `3` is _top right_. + +Moved: `-1` is _down_, `0` is _stationary_, `1` is _up_. + +[zalando's tech radar]: https://github.com/zalando/tech-radar diff --git a/containers/techradar/zalando/docker-compose.yml b/containers/techradar/zalando/docker-compose.yml new file mode 100644 index 0000000..f7ee735 --- /dev/null +++ b/containers/techradar/zalando/docker-compose.yml @@ -0,0 +1,13 @@ +--- +version: '3' +services: + radar: + container_name: radar + image: nginxinc/nginx-unprivileged:1.25.5-bookworm-perl + volumes: + - ${PWD}:/usr/share/nginx/html:ro + ports: + - 8080:8080 + environment: + NGINX_HOST: localhost + NGINX_PORT: '8080' diff --git a/containers/techradar/zalando/index.html b/containers/techradar/zalando/index.html new file mode 100644 index 0000000..f3e3586 --- /dev/null +++ b/containers/techradar/zalando/index.html @@ -0,0 +1,62 @@ + + + + + + + + Tech Radar + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/containers/techradar/zalando/radar.css b/containers/techradar/zalando/radar.css new file mode 100644 index 0000000..662f808 --- /dev/null +++ b/containers/techradar/zalando/radar.css @@ -0,0 +1,23 @@ +body { + font-family: 'Source Sans Pro', arial, helvetica, sans-serif; + padding-bottom: 50px; +} + +h3 { + margin-top: 50px; +} + +li { + margin: 25px 50px 0 0; +} + +table { + width: 1400px; + margin: 0 50px 0 50px; +} + +td { + width: 50%; + vertical-align: top; + padding-right: 60px; +} diff --git a/containers/techradar/zalando/radar.js b/containers/techradar/zalando/radar.js new file mode 100644 index 0000000..72879c6 --- /dev/null +++ b/containers/techradar/zalando/radar.js @@ -0,0 +1,474 @@ +// The MIT License (MIT) + +// Copyright (c) 2017-2024 Zalando SE + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +function radar_visualization(config) { + + // custom random number generator, to make random sequence reproducible + // source: https://stackoverflow.com/questions/521295 + var seed = 42; + function random() { + var x = Math.sin(seed++) * 10000; + return x - Math.floor(x); + } + + function random_between(min, max) { + return min + random() * (max - min); + } + + function normal_between(min, max) { + return min + (random() + random()) * 0.5 * (max - min); + } + + // radial_min / radial_max are multiples of PI + const quadrants = [ + { radial_min: 0, radial_max: 0.5, factor_x: 1, factor_y: 1 }, + { radial_min: 0.5, radial_max: 1, factor_x: -1, factor_y: 1 }, + { radial_min: -1, radial_max: -0.5, factor_x: -1, factor_y: -1 }, + { radial_min: -0.5, radial_max: 0, factor_x: 1, factor_y: -1 } + ]; + + const rings = [ + { radius: 130 }, + { radius: 220 }, + { radius: 310 }, + { radius: 400 } + ]; + + const title_offset = + { x: -675, y: -420 }; + + const footer_offset = + { x: -675, y: 420 }; + + const legend_offset = [ + { x: 450, y: 90 }, + { x: -675, y: 90 }, + { x: -675, y: -310 }, + { x: 450, y: -310 } + ]; + + function polar(cartesian) { + var x = cartesian.x; + var y = cartesian.y; + return { + t: Math.atan2(y, x), + r: Math.sqrt(x * x + y * y) + } + } + + function cartesian(polar) { + return { + x: polar.r * Math.cos(polar.t), + y: polar.r * Math.sin(polar.t) + } + } + + function bounded_interval(value, min, max) { + var low = Math.min(min, max); + var high = Math.max(min, max); + return Math.min(Math.max(value, low), high); + } + + function bounded_ring(polar, r_min, r_max) { + return { + t: polar.t, + r: bounded_interval(polar.r, r_min, r_max) + } + } + + function bounded_box(point, min, max) { + return { + x: bounded_interval(point.x, min.x, max.x), + y: bounded_interval(point.y, min.y, max.y) + } + } + + function segment(quadrant, ring) { + var polar_min = { + t: quadrants[quadrant].radial_min * Math.PI, + r: ring === 0 ? 30 : rings[ring - 1].radius + }; + var polar_max = { + t: quadrants[quadrant].radial_max * Math.PI, + r: rings[ring].radius + }; + var cartesian_min = { + x: 15 * quadrants[quadrant].factor_x, + y: 15 * quadrants[quadrant].factor_y + }; + var cartesian_max = { + x: rings[3].radius * quadrants[quadrant].factor_x, + y: rings[3].radius * quadrants[quadrant].factor_y + }; + return { + clipx: function(d) { + var c = bounded_box(d, cartesian_min, cartesian_max); + var p = bounded_ring(polar(c), polar_min.r + 15, polar_max.r - 15); + d.x = cartesian(p).x; // adjust data too! + return d.x; + }, + clipy: function(d) { + var c = bounded_box(d, cartesian_min, cartesian_max); + var p = bounded_ring(polar(c), polar_min.r + 15, polar_max.r - 15); + d.y = cartesian(p).y; // adjust data too! + return d.y; + }, + random: function() { + return cartesian({ + t: random_between(polar_min.t, polar_max.t), + r: normal_between(polar_min.r, polar_max.r) + }); + } + } + } + + // position each entry randomly in its segment + for (var i = 0; i < config.entries.length; i++) { + var entry = config.entries[i]; + entry.segment = segment(entry.quadrant, entry.ring); + var point = entry.segment.random(); + entry.x = point.x; + entry.y = point.y; + entry.color = entry.active || config.print_layout ? + config.rings[entry.ring].color : config.colors.inactive; + } + + // partition entries according to segments + var segmented = new Array(4); + for (var quadrant = 0; quadrant < 4; quadrant++) { + segmented[quadrant] = new Array(4); + for (var ring = 0; ring < 4; ring++) { + segmented[quadrant][ring] = []; + } + } + for (var i=0; i 0) { + blip.append("path") + .attr("d", "M -11,5 11,5 0,-13 z") // triangle pointing up + .style("fill", d.color); + } else if (d.moved < 0) { + blip.append("path") + .attr("d", "M -11,-5 11,-5 0,13 z") // triangle pointing down + .style("fill", d.color); + } else { + blip.append("circle") + .attr("r", 9) + .attr("fill", d.color); + } + + // blip text + if (d.active || config.print_layout) { + var blip_text = config.print_layout ? d.id : d.label.match(/[a-z]/i); + blip.append("text") + .text(blip_text) + .attr("y", 3) + .attr("text-anchor", "middle") + .style("fill", "#fff") + .style("font-family", "Arial, Helvetica") + .style("font-size", function(d) { return blip_text.length > 2 ? "8px" : "9px"; }) + .style("pointer-events", "none") + .style("user-select", "none"); + } + }); + + // make sure that blips stay inside their segment + function ticked() { + blips.attr("transform", function(d) { + return translate(d.segment.clipx(d), d.segment.clipy(d)); + }) + } + + // distribute blips, while avoiding collisions + d3.forceSimulation() + .nodes(config.entries) + .velocityDecay(0.19) // magic number (found by experimentation) + .force("collision", d3.forceCollide().radius(12).strength(0.85)) + .on("tick", ticked); +} diff --git a/containers/techradar/zalando/radar.json b/containers/techradar/zalando/radar.json new file mode 100644 index 0000000..f293519 --- /dev/null +++ b/containers/techradar/zalando/radar.json @@ -0,0 +1,41 @@ +{ + "date": "2024.04.25", + "entries": [ + { + "quadrant": 2, + "ring": 0, + "label": "node.js", + "active": true, + "moved": 0 + }, + { + "quadrant": 3, + "ring": 0, + "label": "pulumi", + "link": "https://www.pulumi.com/", + "active": true, + "moved": 0 + }, + { + "quadrant": 2, + "ring": 1, + "label": "python", + "active": true, + "moved": 0 + }, + { + "quadrant": 2, + "ring": 2, + "label": "golang", + "active": true, + "moved": -1 + }, + { + "quadrant": 1, + "ring": 3, + "label": "gitea", + "active": false, + "moved": 1 + } + ] +} \ No newline at end of file