weatherChart = {
const minDayWidth = 100;
const maxEntriesToShow = Math.floor(width / minDayWidth);
const height = 200;
const margin = { top: 15, right: 5, bottom: 15, left: 35 };
const barHeight = 6;
const minTemperature = temperatureInPreferredUnits(20, "F", preferredUnit);
const maxTemperature = temperatureInPreferredUnits(90, "F", preferredUnit);
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, height])
.style(
"font",
"10px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
);
const temperatureUnit = forecast.properties.periods[0].temperatureUnit || "F";
const forecasts = forecast.properties.periods
.sort((a, b) => d3.ascending(a.number, b.number))
.map(d => ({
...d,
temperature: temperatureInPreferredUnits(
d.temperature,
temperatureUnit,
preferredUnit
),
startTime: new Date(d.startTime),
endTime: new Date(d.endTime)
}))
.filter(d => d3.timeHour.offset(d.startTime, 2) <= d.endTime)
.slice(0, maxEntriesToShow);
const startTime = d3.min(forecasts, d => d.startTime);
const endTime = d3.max(forecasts, d => d.endTime);
const xScale = d3
.scaleTime()
.domain([startTime, endTime])
.range([margin.left, width - margin.left - margin.right]);
const yScale = d3
.scaleLinear()
.domain([
Math.min(minTemperature, d3.min(forecasts, d => d.temperature)),
Math.max(maxTemperature, d3.max(forecasts, d => d.temperature))
])
.nice()
.range([height - margin.bottom, margin.top]);
const label = d => {
if (d.number === 1) {
return d3.timeHour.offset(d.startTime, 6) > d.endTime ? "" : "Now";
} else {
return d.name;
}
};
const conditions = [
{ name: "Snow", color: "#74ccfa", emoji: "❄️" },
{ name: "Rain", color: "#6786db", emoji: "☔" },
{ name: "Sunny", color: "#fcd92c", emoji: "☀️" }
];
const findPrimaryWeather = forecastText => {
const positions = conditions.map(d =>
forecastText.includes(d.name) ? forecastText.indexOf(d.name) : null
);
const minIndex = d3.minIndex(positions);
return minIndex >= 0 ? conditions[minIndex] : null;
};
const color = d => {
const primaryWeather = findPrimaryWeather(d.shortForecast);
return primaryWeather ? primaryWeather.color : "#ccc";
};
const emoji = d => {
const primaryWeather = findPrimaryWeather(d.shortForecast);
return primaryWeather ? primaryWeather.emoji : "";
};
const g = svg
.selectAll("g")
.data(forecasts)
.join("g")
.attr(
"transform",
d => `translate(${xScale(d.startTime)}, ${height - margin.bottom})`
);
g.append("rect")
.attr("width", d => xScale(d.endTime) - xScale(d.startTime) - 2)
.attr("height", barHeight)
.attr("rx", 3)
.attr("ry", 3)
.attr("fill", "#ccc");
g.append("text")
.attr("y", -barHeight - 3)
.attr("dy", "0.35em")
.text(d => `${label(d)} ${emoji(d)}`);
g.append("title").text(
d =>
`${d.shortForecast}, ${d.isDaytime ? "high" : "low"} of ${
d.temperature
}°${preferredUnit}`
);
const yAxis = svg
.append("g")
.attr("transform", `translate(${margin.left - 5}, 0)`)
.call(
d3
.axisLeft(yScale)
.ticks(7)
.tickFormat(d => d + `°${preferredUnit}`)
.tickSize(3)
);
yAxis.select(".domain").remove();
yAxis
.selectAll("text")
.style("fill", "#666")
.style(
"font",
"10px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif"
);
yield svg.node();
const observer = new IntersectionObserver(entries => {
const entry = entries.pop();
if (entry.isIntersecting) {
const t = g
.transition()
.duration(2000)
.ease(d3.easeCubic);
t.attr(
"transform",
d => `translate(${xScale(d.startTime)}, ${yScale(d.temperature)})`
);
t.select("rect").attr("fill", color);
} else {
g.attr(
"transform",
d => `translate(${xScale(d.startTime)}, ${height - margin.bottom})`
);
g.select("rect").attr("fill", "#ccc");
}
});
observer.observe(svg.node());
invalidation.then(() => observer.disconnect());
}