From a14141883d56fe123a629e0056d250a162bfae63 Mon Sep 17 00:00:00 2001 From: Bartosz Ocytko Date: Fri, 22 Nov 2024 01:19:46 +0100 Subject: [PATCH] Release 0.10 New configuration parameters: - legend_column_width: allowing to increase width of legend columns to make space for longer technology names (#169 by @chicio) - legend_offset: allowing to move legend further away from the radar to accommodate for longer technology names (#170 by @chicio) - title_offset: allowing to align title with legend (#170 by @chicio) Signed-off-by: Bartosz Ocytko --- docs/release/radar-0.10.js | 544 +++++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 docs/release/radar-0.10.js diff --git a/docs/release/radar-0.10.js b/docs/release/radar-0.10.js new file mode 100644 index 00000000..65f40a80 --- /dev/null +++ b/docs/release/radar-0.10.js @@ -0,0 +1,544 @@ +// 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) { + + config.svg_id = config.svg || "radar"; + config.width = config.width || 1450; + config.height = config.height || 1000; + config.colors = ("colors" in config) ? config.colors : { + background: "#fff", + grid: '#dddde0', + inactive: "#ddd" + }; + config.print_layout = ("print_layout" in config) ? config.print_layout : true; + config.links_in_new_tabs = ("links_in_new_tabs" in config) ? config.links_in_new_tabs : true; + config.repo_url = config.repo_url || '#'; + config.print_ring_descriptions_table = ("print_ring_descriptions_table" in config) ? config.print_ring_descriptions_table : false; + config.legend_offset = config.legend_offset || [ + { x: 450, y: 90 }, + { x: -675, y: 90 }, + { x: -675, y: -310 }, + { x: 450, y: -310 } + ] + config.title_offset = config.title_offset || { x: -675, y: -420 }; + config.footer_offset = config.footer_offset || { x: -155, y: 450 }; + config.legend_column_width = config.legend_column_width || 140 + + // 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 } + ]; + + 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 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); + + function ringDescriptionsTable() { + var table = d3.select("body").append("table") + .attr("class", "radar-table") + .style("border-collapse", "collapse") + .style("position", "relative") + .style("top", "-70px") // Adjust this value to move the table closer vertically + .style("margin-left", "50px") + .style("margin-right", "50px") + .style("font-family", config.font_family) + .style("font-size", "13px") + .style("text-align", "left"); + + var thead = table.append("thead"); + var tbody = table.append("tbody"); + + // define fixed width for each column + var columnWidth = `${100 / config.rings.length}%`; + + // create table header row with ring names + var headerRow = thead.append("tr") + .style("border", "1px solid #ddd"); + + headerRow.selectAll("th") + .data(config.rings) + .enter() + .append("th") + .style("padding", "8px") + .style("border", "1px solid #ddd") + .style("background-color", d => d.color) + .style("color", "#fff") + .style("width", columnWidth) + .text(d => d.name); + + // create table body row with descriptions + var descriptionRow = tbody.append("tr") + .style("border", "1px solid #ddd"); + + descriptionRow.selectAll("td") + .data(config.rings) + .enter() + .append("td") + .style("padding", "8px") + .style("border", "1px solid #ddd") + .style("width", columnWidth) + .text(d => d.description); + } + + if (config.print_ring_descriptions_table) { + ringDescriptionsTable(); + } +}