mirror of
https://gitea.com/mcereda/oam.git
synced 2026-02-15 16:24:24 +00:00
chore(techradar): save experimentation with zalando's techradar
This commit is contained in:
13
containers/techradar/zalando/README.md
Normal file
13
containers/techradar/zalando/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Tech radar
|
||||
|
||||
It **is** [Zalando's tech radar], just customized.
|
||||
|
||||
Serves a static page.
|
||||
|
||||
There are **4** quadrants.<br/>
|
||||
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
|
||||
13
containers/techradar/zalando/docker-compose.yml
Normal file
13
containers/techradar/zalando/docker-compose.yml
Normal file
@@ -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'
|
||||
62
containers/techradar/zalando/index.html
Normal file
62
containers/techradar/zalando/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta name="description"
|
||||
content="Tech Radar: a tool to visualize technology choices, inspire and support Engineering teams at Apollo Agriculture to pick the best technologies for new projects">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Tech Radar</title>
|
||||
<link rel="shortcut icon"
|
||||
href="view-source:https://images.squarespace-cdn.com/content/v1/6414395791ffc20447907be9/94d4e41d-9d48-4068-89a1-52f0a7e36890/favicon.ico?format=100w">
|
||||
|
||||
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||
<script src="radar.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="radar.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg id="radar"></svg>
|
||||
|
||||
<script>
|
||||
fetch('./radar.json').then(function (response) {
|
||||
return response.json();
|
||||
}).then(function (data) {
|
||||
radar_visualization({
|
||||
svg_id: "radar",
|
||||
width: 1450,
|
||||
height: 950,
|
||||
colors: {
|
||||
background: "#fff",
|
||||
grid: '#dddde0',
|
||||
inactive: "#ddd"
|
||||
},
|
||||
title: "Tech Radar",
|
||||
date: data.date,
|
||||
quadrants: [
|
||||
{ name: "Data Management" }, // BR, #0
|
||||
{ name: "Datastores" }, // BL, #1
|
||||
{ name: "Languages" }, // TR, #2
|
||||
{ name: "Infrastructure" }, // TL, #3
|
||||
],
|
||||
rings: [
|
||||
{ name: "ADOPT", color: "#5ba300" },
|
||||
{ name: "TRIAL", color: "#009eb0" },
|
||||
{ name: "ASSESS", color: "#c7ba00" },
|
||||
{ name: "HOLD", color: "#e09b96" }
|
||||
],
|
||||
print_layout: true,
|
||||
links_in_new_tabs: true,
|
||||
// zoomed_quadrant: 0,
|
||||
entries: data.entries
|
||||
});
|
||||
}).catch(function (err) {
|
||||
console.log('Error loading config.json', err);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
23
containers/techradar/zalando/radar.css
Normal file
23
containers/techradar/zalando/radar.css
Normal file
@@ -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;
|
||||
}
|
||||
474
containers/techradar/zalando/radar.js
Normal file
474
containers/techradar/zalando/radar.js
Normal file
@@ -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<config.entries.length; i++) {
|
||||
var entry = config.entries[i];
|
||||
segmented[entry.quadrant][entry.ring].push(entry);
|
||||
}
|
||||
|
||||
// assign unique sequential id to each entry
|
||||
var id = 1;
|
||||
for (var quadrant of [2,3,1,0]) {
|
||||
for (var ring = 0; ring < 4; ring++) {
|
||||
var entries = segmented[quadrant][ring];
|
||||
entries.sort(function(a,b) { return a.label.localeCompare(b.label); })
|
||||
for (var i=0; i<entries.length; i++) {
|
||||
entries[i].id = "" + id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function translate(x, y) {
|
||||
return "translate(" + x + "," + y + ")";
|
||||
}
|
||||
|
||||
function viewbox(quadrant) {
|
||||
return [
|
||||
Math.max(0, quadrants[quadrant].factor_x * 400) - 420,
|
||||
Math.max(0, quadrants[quadrant].factor_y * 400) - 420,
|
||||
440,
|
||||
440
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
// adjust with config.scale.
|
||||
config.scale = config.scale || 1;
|
||||
var scaled_width = config.width * config.scale;
|
||||
var scaled_height = config.height * config.scale;
|
||||
|
||||
var svg = d3.select("svg#" + config.svg_id)
|
||||
.style("background-color", config.colors.background)
|
||||
.attr("width", scaled_width)
|
||||
.attr("height", scaled_height);
|
||||
|
||||
var radar = svg.append("g");
|
||||
if ("zoomed_quadrant" in config) {
|
||||
svg.attr("viewBox", viewbox(config.zoomed_quadrant));
|
||||
} else {
|
||||
radar.attr("transform", translate(scaled_width / 2, scaled_height / 2).concat(`scale(${config.scale})`));
|
||||
}
|
||||
|
||||
var grid = radar.append("g");
|
||||
|
||||
// draw grid lines
|
||||
grid.append("line")
|
||||
.attr("x1", 0).attr("y1", -400)
|
||||
.attr("x2", 0).attr("y2", 400)
|
||||
.style("stroke", config.colors.grid)
|
||||
.style("stroke-width", 1);
|
||||
grid.append("line")
|
||||
.attr("x1", -400).attr("y1", 0)
|
||||
.attr("x2", 400).attr("y2", 0)
|
||||
.style("stroke", config.colors.grid)
|
||||
.style("stroke-width", 1);
|
||||
|
||||
// background color. Usage `.attr("filter", "url(#solid)")`
|
||||
// SOURCE: https://stackoverflow.com/a/31013492/2609980
|
||||
var defs = grid.append("defs");
|
||||
var filter = defs.append("filter")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", 1)
|
||||
.attr("height", 1)
|
||||
.attr("id", "solid");
|
||||
filter.append("feFlood")
|
||||
.attr("flood-color", "rgb(0, 0, 0, 0.8)");
|
||||
filter.append("feComposite")
|
||||
.attr("in", "SourceGraphic");
|
||||
|
||||
// draw rings
|
||||
for (var i = 0; i < rings.length; i++) {
|
||||
grid.append("circle")
|
||||
.attr("cx", 0)
|
||||
.attr("cy", 0)
|
||||
.attr("r", rings[i].radius)
|
||||
.style("fill", "none")
|
||||
.style("stroke", config.colors.grid)
|
||||
.style("stroke-width", 1);
|
||||
if (config.print_layout) {
|
||||
grid.append("text")
|
||||
.text(config.rings[i].name)
|
||||
.attr("y", -rings[i].radius + 62)
|
||||
.attr("text-anchor", "middle")
|
||||
.style("fill", config.rings[i].color)
|
||||
.style("opacity", 0.35)
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "42px")
|
||||
.style("font-weight", "bold")
|
||||
.style("pointer-events", "none")
|
||||
.style("user-select", "none");
|
||||
}
|
||||
}
|
||||
|
||||
function legend_transform(quadrant, ring, index=null) {
|
||||
var dx = ring < 2 ? 0 : 140;
|
||||
var dy = (index == null ? -16 : index * 12);
|
||||
if (ring % 2 === 1) {
|
||||
dy = dy + 36 + segmented[quadrant][ring-1].length * 12;
|
||||
}
|
||||
return translate(
|
||||
legend_offset[quadrant].x + dx,
|
||||
legend_offset[quadrant].y + dy
|
||||
);
|
||||
}
|
||||
|
||||
// draw title and legend (only in print layout)
|
||||
if (config.print_layout) {
|
||||
|
||||
// title
|
||||
radar.append("text")
|
||||
.attr("transform", translate(title_offset.x, title_offset.y))
|
||||
.text(config.title)
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "30")
|
||||
.style("font-weight", "bold")
|
||||
|
||||
// date
|
||||
radar
|
||||
.append("text")
|
||||
.attr("transform", translate(title_offset.x, title_offset.y + 20))
|
||||
.text(config.date || "")
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "14")
|
||||
.style("fill", "#999")
|
||||
|
||||
// footer
|
||||
radar.append("text")
|
||||
.attr("transform", translate(footer_offset.x, footer_offset.y))
|
||||
.text("▲ moved up ▼ moved down")
|
||||
.attr("xml:space", "preserve")
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "10px");
|
||||
|
||||
// legend
|
||||
var legend = radar.append("g");
|
||||
for (var quadrant = 0; quadrant < 4; quadrant++) {
|
||||
legend.append("text")
|
||||
.attr("transform", translate(
|
||||
legend_offset[quadrant].x,
|
||||
legend_offset[quadrant].y - 45
|
||||
))
|
||||
.text(config.quadrants[quadrant].name)
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "18px")
|
||||
.style("font-weight", "bold");
|
||||
for (var ring = 0; ring < 4; ring++) {
|
||||
legend.append("text")
|
||||
.attr("transform", legend_transform(quadrant, ring))
|
||||
.text(config.rings[ring].name)
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "12px")
|
||||
.style("font-weight", "bold")
|
||||
.style("fill", config.rings[ring].color);
|
||||
legend.selectAll(".legend" + quadrant + ring)
|
||||
.data(segmented[quadrant][ring])
|
||||
.enter()
|
||||
.append("a")
|
||||
.attr("href", function (d, i) {
|
||||
return d.link ? d.link : "#"; // stay on same page if no link was provided
|
||||
})
|
||||
// Add a target if (and only if) there is a link and we want new tabs
|
||||
.attr("target", function (d, i) {
|
||||
return (d.link && config.links_in_new_tabs) ? "_blank" : null;
|
||||
})
|
||||
.append("text")
|
||||
.attr("transform", function(d, i) { return legend_transform(quadrant, ring, i); })
|
||||
.attr("class", "legend" + quadrant + ring)
|
||||
.attr("id", function(d, i) { return "legendItem" + d.id; })
|
||||
.text(function(d, i) { return d.id + ". " + d.label; })
|
||||
.style("font-family", "Arial, Helvetica")
|
||||
.style("font-size", "11px")
|
||||
.on("mouseover", function(d) { showBubble(d); highlightLegendItem(d); })
|
||||
.on("mouseout", function(d) { hideBubble(d); unhighlightLegendItem(d); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layer for entries
|
||||
var rink = radar.append("g")
|
||||
.attr("id", "rink");
|
||||
|
||||
// rollover bubble (on top of everything else)
|
||||
var bubble = radar.append("g")
|
||||
.attr("id", "bubble")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.style("opacity", 0)
|
||||
.style("pointer-events", "none")
|
||||
.style("user-select", "none");
|
||||
bubble.append("rect")
|
||||
.attr("rx", 4)
|
||||
.attr("ry", 4)
|
||||
.style("fill", "#333");
|
||||
bubble.append("text")
|
||||
.style("font-family", "sans-serif")
|
||||
.style("font-size", "10px")
|
||||
.style("fill", "#fff");
|
||||
bubble.append("path")
|
||||
.attr("d", "M 0,0 10,0 5,8 z")
|
||||
.style("fill", "#333");
|
||||
|
||||
function showBubble(d) {
|
||||
if (d.active || config.print_layout) {
|
||||
var tooltip = d3.select("#bubble text")
|
||||
.text(d.label);
|
||||
var bbox = tooltip.node().getBBox();
|
||||
d3.select("#bubble")
|
||||
.attr("transform", translate(d.x - bbox.width / 2, d.y - 16))
|
||||
.style("opacity", 0.8);
|
||||
d3.select("#bubble rect")
|
||||
.attr("x", -5)
|
||||
.attr("y", -bbox.height)
|
||||
.attr("width", bbox.width + 10)
|
||||
.attr("height", bbox.height + 4);
|
||||
d3.select("#bubble path")
|
||||
.attr("transform", translate(bbox.width / 2 - 5, 3));
|
||||
}
|
||||
}
|
||||
|
||||
function hideBubble(d) {
|
||||
var bubble = d3.select("#bubble")
|
||||
.attr("transform", translate(0,0))
|
||||
.style("opacity", 0);
|
||||
}
|
||||
|
||||
function highlightLegendItem(d) {
|
||||
var legendItem = document.getElementById("legendItem" + d.id);
|
||||
legendItem.setAttribute("filter", "url(#solid)");
|
||||
legendItem.setAttribute("fill", "white");
|
||||
}
|
||||
|
||||
function unhighlightLegendItem(d) {
|
||||
var legendItem = document.getElementById("legendItem" + d.id);
|
||||
legendItem.removeAttribute("filter");
|
||||
legendItem.removeAttribute("fill");
|
||||
}
|
||||
|
||||
// draw blips on radar
|
||||
var blips = rink.selectAll(".blip")
|
||||
.data(config.entries)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "blip")
|
||||
.attr("transform", function(d, i) { return legend_transform(d.quadrant, d.ring, i); })
|
||||
.on("mouseover", function(d) { showBubble(d); highlightLegendItem(d); })
|
||||
.on("mouseout", function(d) { hideBubble(d); unhighlightLegendItem(d); });
|
||||
|
||||
// configure each blip
|
||||
blips.each(function(d) {
|
||||
var blip = d3.select(this);
|
||||
|
||||
// blip link
|
||||
if (d.active && Object.prototype.hasOwnProperty.call(d, "link") && d.link) {
|
||||
blip = blip.append("a")
|
||||
.attr("xlink:href", d.link);
|
||||
|
||||
if (config.links_in_new_tabs) {
|
||||
blip.attr("target", "_blank");
|
||||
}
|
||||
}
|
||||
|
||||
// blip shape
|
||||
if (d.moved > 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);
|
||||
}
|
||||
41
containers/techradar/zalando/radar.json
Normal file
41
containers/techradar/zalando/radar.json
Normal file
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user