100 Projects
Build, Fun, Ship.

This mark the starting point of creating 100 projects.
From zero to hero and enhacing current ones.
I am using for that matter the resource build your own x, repository, as primary source.
Focusing in rust, golang, javascript and not limited to them.
The timeline is one project one to five days
The first one:
Developer Radar


HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="description" content="Developers Readings Radar">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Developers Readings Radar</title>
<link rel="shortcut icon" href="https://pulsarforge.io/images/works/trends-substack-radar-v1.png">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="radar.js"></script>
<style>
.container-fluid {
margin-left: 100px;
}
.entry-list-container {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.entry-column {
flex: 0 0 24%;
box-sizing: border-box;
padding: 10px;
border: 1px solid #ccc;
margin-bottom: 20px;
}
.entry-group {
margin-top: 20px;
}
.entry-group h3 {
margin-bottom: 5px;
}
.entry-group ul {
list-style-type: none;
padding: 0;
}
.entry-group ul li {
padding: 5px 0;
}
.entry-group ul li button {
margin-left: 10px;
}
</style>
<link rel="stylesheet" href="radar.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<svg id="radar"></svg>
<div class="container my-4">
<h1>Customize your Radar</h1>
<form id="dataForm" class="row g-3 align-items-center">
<div class="col-md-2">
<label for="quadrant" class="form-label">Quadrant:</label>
<select id="quadrant" name="quadrant" class="form-select">
<option value="0">Books</option>
<option value="1">Videos</option>
<option value="2">Repositories</option>
<option value="3">Blogs</option>
</select>
</div>
<div class="col-md-2">
<label for="ring" class="form-label">Ring:</label>
<select id="ring" name="ring" class="form-select">
<option value="0">Core</option>
<option value="1">Specific</option>
<option value="2">Reading</option>
<option value="3">Finished</option>
</select>
</div>
<div class="col-md-2">
<label for="label" class="form-label">Label:</label>
<input type="text" id="label" name="label" class="form-control">
</div>
<div class="col-md-2">
<label for="link" class="form-label">Link:</label>
<input type="text" id="link" name="link" class="form-control">
</div>
<div class="col-md-1">
<label for="active" class="form-label">Active:</label>
<div class="form-check">
<input type="checkbox" id="active" name="active" class="form-check-input" checked>
</div>
</div>
<div class="col-md-1">
<label for="moved" class="form-label">Moved:</label>
<input type="number" id="moved" name="moved" class="form-control" value="0">
</div>
<div class="col-md-2">
<button type="button" id="addButton" class="btn btn-primary mt-3">Add Entry</button>
<button type="button" id="updateButton" class="btn btn-secondary mt-3" style="display:none;">Update Entry</button>
</div>
</form>
</div>
<ul id="entryListContainer"></ul>
<script src="radar.js"></script>
<script>
let initialData = JSON.parse(localStorage.getItem('entries')) || [
{
"quadrant": 0,
"ring": 3,
"label": "Head First Design Patterns",
"link": "#",
"active": true,
"moved": 0
},
{
"quadrant": 0,
"ring": 3,
"label": "The Pragmatic Programmer",
"link": "#",
"active": true,
"moved": 0
},
{
"quadrant": 0,
"ring": 3,
"label": "Clean Code",
"link": "#",
"active": true,
"moved": 0
},
{
"quadrant": 0,
"ring": 0,
"label": "Algorithms Illuminated",
"link": "#",
"active": true,
"moved": 0
},
];
let currentIndex = -1;
function saveToLocalStorage(data) {
localStorage.setItem('entries', JSON.stringify(data));
}
function displayRadar(entries) {
radar_visualization({
svg_id: "radar",
width: 1920,
height: 1080,
colors: {
background: "#fff",
grid: '#dddde0',
inactive: "#ddd"
},
title: "Developer Radar",
date: "2024",
quadrants: [
{ name: "Books" },
{ name: "Videos" },
{ name: "Repositories" },
{ name: "Blogs" },
],
rings: [
{ name: "Core", color: "#5ba300" },
{ name: "Specific", color: "#009eb0" },
{ name: "Reading", color: "#c7ba00" },
{ name: "Finished", color: "#e09b96" }
],
print_layout: true,
links_in_new_tabs: true,
entries: entries
});
displayEntryList(entries);
}
function displayEntryList(entries) {
const entryListContainer = document.getElementById('entryListContainer');
entryListContainer.innerHTML = '';
entryListContainer.className = 'container g-3 align-items-center';
const quadrants = [
{ name: "Books", id: 0 },
{ name: "Videos", id: 1 },
{ name: "Repositories", id: 2 },
{ name: "Blogs", id: 3 },
];
const rings = [
{ name: "Core", id: 0 },
{ name: "Specific", id: 1 },
{ name: "Reading", id: 2 },
{ name: "Finished", id: 3 },
];
const row = document.createElement('div');
row.className = 'row align-items-center';
rings.forEach(ring => {
const ringColumn = document.createElement('div');
ringColumn.className = 'col-md-3 mb-4'; // responsive column sizing
ringColumn.innerHTML = `<h2>${ring.name}</h2>`;
quadrants.forEach(quadrant => {
const groupEntries = entries.filter(entry => entry.quadrant === quadrant.id && entry.ring === ring.id);
if (groupEntries.length > 0) {
const groupDiv = document.createElement('div');
groupDiv.className = 'entry-group';
groupDiv.innerHTML = `<h3>${quadrant.name}</h3>`;
const ul = document.createElement('ul');
ul.className = 'list-unstyled'; // removes default list styling
groupEntries.forEach((entry, index) => {
const listItem = document.createElement('li');
listItem.innerHTML = `
${entry.label} -
<button class="btn btn-sm" onclick="deleteEntry(${index})">✖</button>
`;
ul.appendChild(listItem);
});
groupDiv.appendChild(ul);
ringColumn.appendChild(groupDiv);
}
});
row.appendChild(ringColumn);
});
entryListContainer.appendChild(row);
}
function addEntry() {
const newEntry = {
quadrant: parseInt(document.getElementById('quadrant').value),
ring: parseInt(document.getElementById('ring').value),
label: document.getElementById('label').value,
link: document.getElementById('link').value,
active: document.getElementById('active').checked,
moved: parseInt(document.getElementById('moved').value)
};
initialData.push(newEntry);
saveToLocalStorage(initialData);
displayRadar(initialData);
clearForm();
}
function editEntry(index) {
currentIndex = index;
const entry = initialData[index];
document.getElementById('quadrant').value = entry.quadrant;
document.getElementById('ring').value = entry.ring;
document.getElementById('label').value = entry.label;
document.getElementById('link').value = entry.link;
document.getElementById('active').checked = entry.active;
document.getElementById('moved').value = entry.moved;
document.getElementById('addButton').style.display = 'none';
document.getElementById('updateButton').style.display = 'inline';
}
function updateEntry() {
const updatedEntry = {
quadrant: parseInt(document.getElementById('quadrant').value),
ring: parseInt(document.getElementById('ring').value),
label: document.getElementById('label').value,
link: document.getElementById('link').value,
active: document.getElementById('active').checked,
moved: parseInt(document.getElementById('moved').value)
};
initialData[currentIndex] = updatedEntry;
saveToLocalStorage(initialData);
displayRadar(initialData);
clearForm();
document.getElementById('addButton').style.display = 'inline';
document.getElementById('updateButton').style.display = 'none';
}
function deleteEntry(index) {
initialData.splice(index, 1);
saveToLocalStorage(initialData);
displayRadar(initialData);
}
function clearForm() {
document.getElementById('quadrant').value = '0';
document.getElementById('ring').value = '0';
document.getElementById('label').value = '';
document.getElementById('link').value = '';
document.getElementById('active').checked = true;
document.getElementById('moved').value = '0';
currentIndex = -1;
}
document.getElementById('addButton').addEventListener('click', addEntry);
document.getElementById('updateButton').addEventListener('click', updateEntry);
// Display initial data
displayRadar(initialData);
// Handle file upload and update radar
document.getElementById('uploadButton').addEventListener('click', function() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('Please select a JSON file');
return;
}
const reader = new FileReader();
reader.onload = function(event) {
try {
const json = JSON.parse(event.target.result);
if (!Array.isArray(json)) {
alert('Invalid JSON format: expected an array of entries');
return;
}
console.log('Entries:', json);
initialData = json;
saveToLocalStorage(json);
displayRadar(json);
} catch (error) {
alert('Error parsing JSON file');
console.error('Error:', error);
}
};
reader.readAsText(file);
});
</script>
</body>
</html>
CSS
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;
}
Javascript
function radar_visualization(config) {
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(" ");
}
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 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))
.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", "10px")
.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);
}



