Public
Edited
Apr 21
Insert cell
Insert cell
workbook = FileAttachment("Types of EVs@1.xlsx").xlsx()
Insert cell
workbook.sheetNames
Insert cell
data = workbook.sheet(0, {
headers: false,
// range: "A1:J10"
})
Insert cell
data
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
(async () => {
// Set dimensions
const width = 975;
const height = 620;
const margin = { top: 80, right: 30, bottom: 160, left: 80 };

// Inline data for Washington State EVs by county
const evData = [
{ County: "Adams", BEV: 64, PHEV: 19, Total: 83 },
{ County: "Asotin", BEV: 65, PHEV: 32, Total: 97 },
{ County: "Benton", BEV: 2231, PHEV: 871, Total: 3102 },
{ County: "Chelan", BEV: 1159, PHEV: 303, Total: 1462 },
{ County: "Clallam", BEV: 1006, PHEV: 433, Total: 1439 },
{ County: "Clark", BEV: 10814, PHEV: 3604, Total: 14418 },
{ County: "Columbia", BEV: 18, PHEV: 2, Total: 20 },
{ County: "Cowlitz", BEV: 910, PHEV: 330, Total: 1240 },
{ County: "Douglas", BEV: 420, PHEV: 108, Total: 528 },
{ County: "Ferry", BEV: 28, PHEV: 8, Total: 36 },
{ County: "Franklin", BEV: 690, PHEV: 214, Total: 904 },
{ County: "Garfield", BEV: 0, PHEV: 3, Total: 3 },
{ County: "Grant", BEV: 620, PHEV: 261, Total: 881 },
{ County: "Grays Harbor", BEV: 601, PHEV: 282, Total: 883 },
{ County: "Island", BEV: 1936, PHEV: 651, Total: 2587 },
{ County: "Jefferson", BEV: 930, PHEV: 330, Total: 1260 },
{ County: "King", BEV: 98004, PHEV: 22379, Total: 120383 },
{ County: "Kitsap", BEV: 6100, PHEV: 1971, Total: 8071 },
{ County: "Kittitas", BEV: 717, PHEV: 168, Total: 885 },
{ County: "Klickitat", BEV: 299, PHEV: 119, Total: 418 },
{ County: "Lewis", BEV: 633, PHEV: 421, Total: 1054 },
{ County: "Lincoln", BEV: 36, PHEV: 34, Total: 70 },
{ County: "Mason", BEV: 855, PHEV: 304, Total: 1159 },
{ County: "Okanogan", BEV: 257, PHEV: 106, Total: 363 },
{ County: "Pacific", BEV: 166, PHEV: 125, Total: 291 },
{ County: "Pend Oreille", BEV: 58, PHEV: 18, Total: 76 },
{ County: "Pierce", BEV: 15370, PHEV: 4302, Total: 19672 },
{ County: "San Juan", BEV: 883, PHEV: 257, Total: 1140 },
{ County: "Skagit", BEV: 2072, PHEV: 658, Total: 2730 },
{ County: "Skamania", BEV: 184, PHEV: 64, Total: 248 },
{ County: "Snohomish", BEV: 24640, PHEV: 4795, Total: 29435 },
{ County: "Spokane", BEV: 4724, PHEV: 1930, Total: 6654 },
{ County: "Stevens", BEV: 193, PHEV: 94, Total: 287 },
{ County: "Thurston", BEV: 6675, PHEV: 2084, Total: 8759 },
{ County: "Wahkiakum", BEV: 56, PHEV: 28, Total: 84 },
{ County: "Walla Walla", BEV: 442, PHEV: 203, Total: 645 },
{ County: "Whatcom", BEV: 4376, PHEV: 1448, Total: 5824 },
{ County: "Whitman", BEV: 321, PHEV: 156, Total: 477 },
{ County: "Yakima", BEV: 1125, PHEV: 439, Total: 1564 }
];

// Create container for chart and controls
const container = DOM.element("div");
// Create dropdown for selecting number of counties
const select = DOM.element("select", {
style: "margin-bottom: 20px; font-size: 16px; padding: 8px;"
});
[5, 10, 15, 20].forEach(n => {
const option = DOM.element("option", { value: n });
option.textContent = `Top ${n} Counties`;
select.appendChild(option);
});
container.appendChild(select);

// Function to get top N counties by total number of EVs
const getTopCounties = (data, topN) => {
return data.sort((a, b) => b.Total - a.Total).slice(0, topN);
};

// Function to create tooltip
const createTooltip = () => {
const tooltip = DOM.element("div", {
style: `
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px;
border-radius: 4px;
pointer-events: none;
font-size: 12px;
z-index: 1000;
display: none;
`
});
document.body.appendChild(tooltip);
return tooltip;
};

// Function to create the bar chart
const createBarChart = (data, topN, tooltip) => {
const topData = getTopCounties(data, topN);
return Plot.plot({
width: width,
height: height,
marginTop: margin.top,
marginBottom: margin.bottom,
marginLeft: margin.left,
marginRight: margin.right,
marks: [
Plot.barY(topData, {
x: "County",
y: "BEV",
fill: "#2ecc71",
title: (d) => `${d.County}: ${d.BEV.toLocaleString()} BEVs`,
dx: -10,
transition: { duration: 500 } // Animation
}),
Plot.barY(topData, {
x: "County",
y: "PHEV",
fill: "#3498db",
title: (d) => `${d.County}: ${d.PHEV.toLocaleString()} PHEVs`,
dx: 10,
transition: { duration: 500 } // Animation
}),
Plot.text(
[`Electric Vehicle Distribution in Washington State`],
{ frameAnchor: "top", fontSize: 24, fontWeight: "bold", dy: -40 }
),
Plot.text(
[`Battery Electric Vehicles (BEV) and Plug-in Hybrid Electric Vehicles (PHEV) by County`],
{ frameAnchor: "top", fontSize: 16, dy: -15 }
)
],
x: {
label: "County",
labelOffset: 100,
tickRotate: -45,
tickSize: 10,
fontSize: 14,
fontWeight: "bold"
},
y: {
label: "Number of Electric Vehicles",
labelOffset: 50,
grid: true,
tickFormat: ",d",
domain: [0, Math.max(...topData.map(d => Math.max(d.BEV, d.PHEV))) * 1.1]
},
color: {
legend: true,
domain: ["BEV", "PHEV"],
range: ["#2ecc71", "#3498db"],
label: "Vehicle Type"
},
style: {
fontFamily: "Arial, sans-serif",
fontSize: 14,
background: "#ffffff",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
borderRadius: "8px"
},
on: {
pointerenter: (event, d) => {
if (d) {
tooltip.style.display = "block";
tooltip.textContent = `${d.County}\nBEV: ${d.BEV.toLocaleString()}\nPHEV: ${d.PHEV.toLocaleString()}\nTotal: ${d.Total.toLocaleString()}`;
}
},
pointermove: (event) => {
tooltip.style.left = `${event.pageX + 10}px`;
tooltip.style.top = `${event.pageY + 10}px`;
},
pointerleave: () => {
tooltip.style.display = "none";
}
}
});
};

// Initialize tooltip
const tooltip = createTooltip();

// Initial chart
container.appendChild(createBarChart(evData, 10, tooltip));

// Update chart when dropdown changes
select.addEventListener("change", () => {
const topN = +select.value;
const newChart = createBarChart(evData, topN, tooltip);
container.replaceChild(newChart, container.lastChild);
});

return container;
})();
Insert cell
(async () => {
const width = 1175;
const height = 450; // Increased height for better spacing
const margin = { top: 40, right: 120, bottom: 120, left: 180 }; // Increased bottom margin

const data = [
{ model: "Tesla Model Y", count: 47833 },
{ model: "Tesla Model 3", count: 34736 },
{ model: "Nissan Leaf", count: 12770 },
{ model: "Chevrolet Bolt EV", count: 6699 },
{ model: "Tesla Model S", count: 6284 },
{ model: "Tesla Model X", count: 5859 },
{ model: "Ford Mustang Mach-E", count: 4897 },
{ model: "Chevrolet Volt", count: 4070 },
{ model: "Chevrolet Bolt EUV", count: 2664 },
{ model: "Nissan Ariya", count: 1671 }
];

const totalEVs = data.reduce((sum, d) => sum + d.count, 0);
data.forEach(d => {
d.percentage = (d.count / totalEVs * 100).toFixed(1);
});

const maxCount = d3.max(data, d => d.count);

const colorScale = d3.scaleSequential()
.domain([0, maxCount])
.interpolator(d3.interpolateBlues);

const plot = Plot.plot({
width,
height,
marginTop: margin.top,
marginRight: margin.right,
marginBottom: margin.bottom,
marginLeft: margin.left,
x: {
grid: true,
tickFormat: d3.format(".2s"),
label: null
},
y: {
label: "EV Model",
labelOffset: 150
},
marks: [
Plot.barX(data, {
x: "count",
y: "model",
fill: d => colorScale(d.count),
sort: { y: "x", reverse: true },
ariaLabel: d => `${d.model}: ${d.count.toLocaleString()} registrations (${d.percentage}%)`,
title: d => `${d.model}\n${d.count.toLocaleString()} registrations\n${d.percentage}% of total`
}),
Plot.text(data, {
x: "count",
y: "model",
text: d => `${d.count.toLocaleString()} (${d.percentage}%)`,
dx: 10,
dy: 4,
fill: d =>
["Tesla Model Y", "Tesla Model 3"].includes(d.model)
? "black"
: d.count > 30000
? "white"
: "black",
fontSize: 13,
fontFamily: "Arial, sans-serif",
textAnchor: "start"
})
],
style: {
fontFamily: "Arial, sans-serif",
background: "#dee2e6", // darker background
padding: "12px",
borderRadius: "10px"
}
});

const svg = d3.select(plot);

// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.style("font", "bold 24px Arial, sans-serif")
.text("Top 10 Electric Vehicle Models by Registrations: Numbers and Percentages");

// Add centered x-axis label
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 70) // Adjusted y position
.attr("text-anchor", "middle")
.style("font", "15px Arial, sans-serif")
.text("Number of Registrations");

// Add color legend below x-axis label
const legendWidth = 200;
const legendHeight = 20;

const defs = svg.append("defs");
const gradient = defs.append("linearGradient")
.attr("id", "legend-gradient")
.attr("x1", "0%")
.attr("x2", "100%");

gradient.append("stop")
.attr("offset", "0%")
.attr("stop-color", colorScale(0));

gradient.append("stop")
.attr("offset", "100%")
.attr("stop-color", colorScale(maxCount));

const legendGroup = svg.append("g")
.attr("transform", `translate(${width / 2 - legendWidth / 2}, ${height - 40})`); // Adjusted y position

legendGroup.append("rect")
.attr("width", legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#legend-gradient)")
.attr("stroke", "#333")
.attr("stroke-width", 0.5)
.attr("rx", 3)
.attr("ry", 3);

legendGroup.append("text")
.attr("x", 0)
.attr("y", legendHeight + 15)
.attr("font-size", "14px")
.attr("fill", "#000")
.text("0");

legendGroup.append("text")
.attr("x", legendWidth)
.attr("y", legendHeight + 15)
.attr("font-size", "14px")
.attr("fill", "#000")
.attr("text-anchor", "end")
.text(maxCount.toLocaleString());

legendGroup.append("text")
.attr("x", legendWidth / 2)
.attr("y", -10)
.attr("font-size", "14px")
.attr("fill", "#000")
.attr("text-anchor", "middle")
.text("Registration Count");

return plot;
})();

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more