Skip to content

Commit

Permalink
D3-Statistics (#801)
Browse files Browse the repository at this point in the history
* Changes to intro page - embedded from observable

Signed-off-by: Orhun Ucak <[email protected]>

* Prettier website and enabled iframe in config

Signed-off-by: Orhun Ucak <[email protected]>

* New line + Signed-off-by test

Signed-off-by: Orhun Ucak <[email protected]>

* Second Signoff test

Signed-off-by: Orhun Ucak <[email protected]>

* Third Signoff test

Signed-off-by: Orhun Ucak <[email protected]>

* Image was changed

Signed-off-by: Orhun Ucak <[email protected]>

* Image paths corrected

Signed-off-by: Orhun Ucak <[email protected]>

* Observable abandoned for 3 plots

Signed-off-by: Orhun Ucak <[email protected]>

* Added newlines to html

Signed-off-by: Orhun Ucak <[email protected]>

* Added newlines to html

Signed-off-by: Orhun Ucak <[email protected]>

* Added newlines at the ends of the data files

Signed-off-by: Orhun Ucak <[email protected]>

* Ran pre-commit

Signed-off-by: Orhun Ucak <[email protected]>

---------

Signed-off-by: Orhun Ucak <[email protected]>
  • Loading branch information
Giaccomole authored Jan 22, 2025
1 parent 3088fb0 commit 147467e
Show file tree
Hide file tree
Showing 12 changed files with 7,060 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs-gen/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ weight = 12
# Generation of JSON index to allow search
[outputs]
home = ['html', 'rss', 'search', 'searchpage']

# So that raw HTML content is enabled in Hugo project

[markup.goldmark.renderer]
unsafe = true
23 changes: 22 additions & 1 deletion docs-gen/content/introduction/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,25 @@ weight: 10
chapter: false
---

Discover what the Vehicle Signal Specification is all about.

## VSS Tree Model

The diagram below shows a generated tree of the VSS. Here you can see a mapping for the current catalog of signals related to vehicles. Click a Branch Node to expand or collapse the tree. (Slower animation while pressing the altKey ("option" key for Mac OS-X).)


{{< radialcluster >}}

---
## Current Population of the model - in respect to VSS Types and Datatypes


Click on the nodes to highlight the links, or hover over the links to see their exact population.

{{< parallelsets >}}

---
## Releases and types

How the model changed in major releases.

{{< donut_chart >}}
168 changes: 168 additions & 0 deletions docs-gen/layouts/shortcodes/donut_chart.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<div id="chart-container" style="height: 550px; ">
<div id="dataset-selector" style="margin-bottom: 20px;"></div>
<div id="donut"></div>
</div>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/[email protected]/+esm";

console.log("Plot.js script loaded");

console.log("Attempting to load CSV data from /data");
d3.csv("/vehicle_signal_specification/data/piechartnotexpanded.csv").then(data => {
console.log("CSV Data loaded successfully:", data);

const width = 800; // Assuming a fixed width for the chart
const height = Math.min(500, width / 2);
const outerRadius = height / 2 - 10;
const innerRadius = outerRadius * 0.70;

const colorMap = {
"Attribute": "#06D6A0", // Green for attributes
"Branches": "#26547C", // Blue for nodes with children
"Actuators": "#EF476F", // Red for actuators
"Sensors": "#FFD166", // Yellow for sensors

};

const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height]);

svg.append("title")
.text("Pie Chart Showing Distribution of Data Categories");

const arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);

const pie = d3.pie().sort(null).value(d => d.value);

const path = svg.append("g")
.selectAll("path");

// Update chart function
function updateChart(dataset) {
console.log(`Updating chart with dataset: ${dataset}`);
const dataTransformed = data.map(d => ({ type: d.Type, value: d[dataset] }));
const pieData = pie(dataTransformed);

const totalValue = d3.sum(dataTransformed, d => d.value);

path.data(pieData)
.join(
enter => enter.append("path")
.attr("fill", d => colorMap[d.data.type] || "#ccc")
.attr("d", arc)
.each(function(d) { this._current = d; }),
update => update
.transition().duration(750)
.attrTween("d", arcTween),
exit => exit.remove()
);

// Add text labels, excluding those with a value of 0
svg.selectAll("text").remove(); // Remove previous text elements

svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.attr("fill", "white")
.selectAll("text")
.data(pieData.filter(d => d.data.value > 0)) // Filter out segments with value 0
.join("text")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.call(text => text.append("tspan")
.attr("y", "-0.4em")
.text(d => d.data.type))
.call(text => text.filter(d => (d.endAngle - d.startAngle) > 0.25).append("tspan")
.attr("x", 0)
.attr("y", "0.7em")
.attr("fill-opacity", 0.7)
.text(d => d.data.value.toLocaleString("en-US")));

// Add total value at the center of the chart
svg.append("text")
.attr("font-family", "sans-serif")
.attr("font-size", 50)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.attr("fill", "#333333")
.attr("dy", "0.35em")
.text(totalValue);
svg.append("text")
.attr("font-family", "sans-serif")
.attr("font-size", 16)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.attr("fill", "gray")
.attr("dy", "3.2em")
.text("total");
}

// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
const i = d3.interpolate(this._current, a);
this._current = i(0);
return t => arc(i(t));
}

// Initialize the chart with the default dataset "V5"
console.log("Initializing chart with dataset: V5");
updateChart("V5");

console.log("Appending plot to #donut");
document.getElementById("donut").appendChild(svg.node());
console.log("Plot appended successfully");

// Create dataset selector
console.log("Creating dataset selector");
const selectorContainer = document.getElementById("dataset-selector");
const datasets = new Map([["V2.0", "V2"], ["V3.0", "V3"], ["V4.0", "V4"], ["V5.0", "V5"]]);

const radioForm = document.createElement("form");
radioForm.style.display = "inline-block"; // Ensure form is inline-block for center alignment

// Add a label before the radio buttons
const formLabel = document.createElement("span");
formLabel.textContent = "Release: ";
formLabel.style.fontWeight = "500";
formLabel.style.marginTop = "20px";
formLabel.style.marginRight = "10px";
radioForm.appendChild(formLabel);

datasets.forEach((value, key) => {
const label = document.createElement("label");
label.style.marginLeft = "15px"; // Add space between options
label.style.fontWeight = "500"; // Semibold text style
label.style.display = "inline-block"; // Ensure radio buttons are aligned horizontally

const input = document.createElement("input");
input.type = "radio";
input.name = "dataset";
input.value = value;

if (value === "V5") input.checked = true; // Default selection
input.addEventListener("change", () => {
console.log(`Dataset changed to: ${value}`);
updateChart(value); // Trigger chart change based on the selected value
});

label.appendChild(input);
label.appendChild(document.createTextNode(key));

radioForm.appendChild(label);
});

selectorContainer.appendChild(radioForm);
console.log("Dataset selector created successfully");
}).catch(error => {
console.error("Error loading CSV data:", error);
document.getElementById("donut").innerText = "Error loading data. Please check the console for details.";
});

console.log("Script execution complete");
</script>
181 changes: 181 additions & 0 deletions docs-gen/layouts/shortcodes/parallelsets.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<div id="sankey-chart-container" style="width: 100%; height: 720px;"></div>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
import { csv } from "https://cdn.jsdelivr.net/npm/d3-fetch@3/+esm";
import { sankey as d3Sankey, sankeyLinkHorizontal } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";

const margin = { top: 10, right: 10, bottom: 10, left: 10 };
const width = 928 - margin.left - margin.right;
const height = 720 - margin.top - margin.bottom;

const sankey = d3Sankey()
.nodeSort((a, b) => b.value - a.value)
.linkSort((a, b) => b.value - a.value)
.nodeWidth(7)
.nodePadding(20)
.extent([[0, 5], [width, height - 5]]);

const color = d3.scaleOrdinal(["static"], ["#ccc"]).unknown("#ccc");

const colorMap = {
attribute: "#06D6A0", // Green for attributes
actuator: "#EF476F", // Red for actuators
sensor: "#FFD166" // Yellow for sensors
};

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

const container = document.getElementById("sankey-chart-container");
container.appendChild(svg.node());

function drawChart(data) {
const graph = _graph(data, d3);

const { nodes, links } = sankey({
nodes: graph.nodes.map(d => Object.create(d)),
links: graph.links.map(d => Object.create(d))
});

let highlightedNode = null;

function highlight(node) {
const relatedLinks = links.filter(link => link.source === node || link.target === node);
const relatedNodes = new Set(relatedLinks.flatMap(link => [link.source, link.target]));

resetHighlight();

// Highlight related links
svg.selectAll("path").attr("stroke", d =>
relatedLinks.includes(d) ? "#ff8800" : color(d.names[0]) // Use a distinct highlight color for links
).attr("opacity", d =>
relatedLinks.includes(d) ? 0.7 : 1 // Set a moderate opacity for highlighted links
);
}

function resetHighlight() {
// Reset all links to their original color and full opacity
svg.selectAll("path")
.attr("stroke", d => color(d.names[0]))
.attr("opacity", 1);
}

function handleClick(event, d) {
if (highlightedNode === d) {
// If clicking the same node again, reset the highlight
highlightedNode = null;
resetHighlight();
} else {
// Highlight the clicked node's related links
highlightedNode = d;
highlight(d);
}
}

// Draw nodes
svg.append("g")
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", d => {
// Check node type and apply appropriate color
if (d.name === "attribute") return "#06D6A0"; // Green for attribute
if (d.name === "actuator") return "#EF476F"; // Red for actuator
if (d.name === "sensor") return "#FFD166"; // Yellow for sensor
return "#cc8800"; // Default gray color
})
.style("cursor", "pointer")
.on("click", handleClick) // Add click event listener
.append("title")
.text(d => `${d.name}\n${d.value.toLocaleString()}`);

// Draw links
svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(links)
.join("path")
.attr("d", sankeyLinkHorizontal())
.attr("stroke", d => color(d.names[0]))
.attr("stroke-width", d => d.width)
.style("mix-blend-mode", "multiply")
.on("mouseover", function(event, d) {
// Show the tooltip
d3.select(this).attr("stroke", "#ab5b00").attr("opacity", 0.7);
})
.on("mouseout", function(event, d) {
// Hide the tooltip
d3.select(this).attr("stroke", color(d.names[0])).attr("opacity", 1);
})
.append("title")
.text(d => `${d.names.join(" → ")}\n${d.value.toLocaleString()}`);

// Add node labels
svg.append("g")
.style("font", "18px sans-serif")
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => d.name)
.append("tspan")
.attr("fill-opacity", 0.7)
.text(d => ` ${d.value.toLocaleString()}`);
}

function _graph(data, d3) {
const keys = data.columns.slice(0, -1);
let index = -1;
const nodes = [];
const nodeByKey = new d3.InternMap([], JSON.stringify);
const indexByKey = new d3.InternMap([], JSON.stringify);
const links = [];

for (const k of keys) {
for (const d of data) {
const key = [k, d[k]];
if (nodeByKey.has(key)) continue;
const node = { name: d[k] };
nodes.push(node);
nodeByKey.set(key, node);
indexByKey.set(key, ++index);
}
}

for (let i = 1; i < keys.length; ++i) {
const a = keys[i - 1];
const b = keys[i];
const prefix = keys.slice(0, i + 1);
const linkByKey = new d3.InternMap([], JSON.stringify);
for (const d of data) {
const names = prefix.map(k => d[k]);
const value = d.value || 1;
let link = linkByKey.get(names);
if (link) { link.value += value; continue; }
link = {
source: indexByKey.get([a, d[a]]),
target: indexByKey.get([b, d[b]]),
names,
value
};
links.push(link);
linkByKey.set(names, link);
}
}

return { nodes, links };
}

const dataUrl = "/vehicle_signal_specification/data/modified_vss_compact_metadata copy.csv";
csv(dataUrl).then(data => drawChart(data));
</script>
Loading

0 comments on commit 147467e

Please sign in to comment.