You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello, I've modified the code for your radar to accommodate 5 quadrants and 3 rings. I've successfully drawn everything and adjusted the legend, but I'm struggling to position the blips correctly in the right quadrant. Could you possibly lend me a hand to understand where I'm going wrong?
`
function radar_visualization(config) {
var seed = 45;
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.4, factor_x: 1, factor_y: 1 },
{ radial_min: 0.4, radial_max: 0.8, factor_x: -1, factor_y: 1 },
{ radial_min: 0.8, radial_max: 1.2, factor_x: -1, factor_y: -1 },
{ radial_min: -1.2, radial_max: -0.8, factor_x: 1, factor_y: -1 },
{ radial_min: -0.8, radial_max: -0.4, factor_x: 1, factor_y: 1 }
];
const rings = [
{ radius: 130 },
{ radius: 220 },
{ radius: 310 },
];
const title_offset =
{ x: -675, y: -420 };
const footer_offset =
{ x: -675, y: 420 };
const legend_offset = [
{ x: 400, y: -200 }, // Quadrant 0
{ x: 400, y: 90 }, // Quadrant 1
{ x: -60, y: 400 }, // Quadrant 2
{ x: -550, y: 90 }, // Quadrant 3
{ x: -550, y: -200 } // Quadrant 4
];
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[2].radius * quadrants[quadrant].factor_x,
y: rings[2].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(5);
for (var quadrant = 0; quadrant < 5; quadrant++) {
segmented[quadrant] = new Array(3);
for (var ring = 0; ring < 3; 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, 4]) {
for (var ring = 0; ring < 3; 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(" ");
}
var svg = d3.select("svg#" + config.svg_id)
.style("background-color", config.colors.background)
.attr("width", config.width)
.attr("height", config.height);
var radar = svg.append("g");
if ("zoomed_quadrant" in config) {
svg.attr("viewBox", viewbox(config.zoomed_quadrant));
} else {
radar.attr("transform", translate(config.width / 2, config.height / 2));
}
var grid = radar.append("g");
// draw rings
for (var i = rings.length - 1; i >= 0; i--) {
const bgColor = i === 2 ? "#d5cfcf" : i === 1 ? "#989292" : "#5f5b5b"
grid.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", rings[i].radius)
.style("fill", bgColor)
.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.60)
.style("font-family", "Arial, Helvetica")
.style("font-size", "42px")
.style("font-weight", "bold")
.style("pointer-events", "none")
.style("user-select", "none");
}
}
// draw grid lines
const quadrantSuddivisionNumber = 360 / quadrants.length;
for (let q = 0; q < quadrants.length; q++) {
const angle = quadrantSuddivisionNumber * q;
const textAngle = angle + quadrantSuddivisionNumber / 2;
const xText = 0;
const yText = -310;
grid.append("line")
.attr("x1", 0).attr("y1", -310)
.attr("x2", 0).attr("y2", 0)
.style("stroke", config.colors.grid)
.attr('transform', `rotate(${angle} 0 0)`)
.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");
function legend_transform(quadrant, ring, index = null) {
var dx = ring < 2 ? 0 : 120;
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 < 5; 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 < 3; ring++) {
legend.append("text")
.attr("transform", legend_transform(quadrant, ring))
.text(config.rings[ring].name)
.style("font-family", "Arial, Helvetica")
.style("font-weight", "bold")
.style("fill", config.rings[ring].color);
legend.selectAll(".legend" + quadrant + ring)
.data(segmented[quadrant][ring])
.enter()
.append("a")
// Add an href if (and only if) there is a link
.attr("href", function (d, i) {
return d.link ? d.link : null;
})
// 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 && d.hasOwnProperty("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 -9,-9 9,-9 9,9 -9,9 Z") // quadrato
.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);
}
`
The text was updated successfully, but these errors were encountered:
I created an own version of the techradar that's very similar to this one from Zalando - but you can adjust the number of slices (quadrants) and rings dynamically. Check it out: https://github.com/chrishrb/techradar
Hello, I've modified the code for your radar to accommodate 5 quadrants and 3 rings. I've successfully drawn everything and adjusted the legend, but I'm struggling to position the blips correctly in the right quadrant. Could you possibly lend me a hand to understand where I'm going wrong?
`
function radar_visualization(config) {
}
`
The text was updated successfully, but these errors were encountered: