packages/graph-view/src/index.ts
/*
* Copyright (C) 2022 SensibleMetrics, Inc. (http://sensiblemetrics.io/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {max, min} from "d3-array";
import {axisLeft, axisTop} from "d3-axis";
import {scaleBand, scaleTime} from "d3-scale";
import {select} from "d3-selection";
import {utcYear} from "d3-time";
import {utcFormat} from "d3-time-format";
import {JSDOM} from "jsdom";
import serialize from "w3c-xmlserializer";
import data from "../support-window.json"
const width = 640;
const height = 250;
const margin = {top: 30, right: 0, bottom: 0, left: 0};
const formatDate = utcFormat("%Y-%m");
const now = new Date();
const supported = Object.entries(data)
.map(([version, value]) => {
// @ts-ignore
const releaseDate = new Date(value);
const endDate = utcYear.offset(releaseDate, 2);
return {version, releaseDate, endDate};
})
.filter((d) => d.endDate >= now);
const x = scaleTime()
.domain([
min(
supported,
(d) =>
// Clip 1/4 of the earliest supported version. Cuts off the
// release date (unimportant?) but gives the visual impression
// of additional, unsupported versions?
new Date(
Number(d.releaseDate) +
((d.endDate as never) - (d.releaseDate as never)) / 4
)
)!,
max(supported, (d) => d.endDate)!,
])
.nice()
.range([margin.left, width - margin.right]);
const y = scaleBand()
.domain(supported.map((d) => d.version))
.range([margin.top, height - margin.bottom])
.padding(0.2);
// https://github.com/d3/d3/wiki#supported-environments
const dom = new JSDOM();
const svg = select(dom.window.document.body)
.append("svg")
.attr("id", "gh-dark-mode-only")
.attr("viewBox", [0, 0, width, height])
.attr("font-family", "sans-serif");
// White axes in dark mode.
svg.append("style").text(`
#gh-dark-mode-only:target {
color: #ffffff;
}
`);
const axes = svg
.append("g")
.attr("stroke-dasharray", 2)
.attr("stroke-opacity", 0.5)
.attr("stroke-width", 0.5);
const gx = axes.append("g").attr("transform", `translate(0,${margin.top})`);
const xAxis = axisTop(x)
.ticks(5)
.tickSize(margin.top - height - margin.bottom);
xAxis(gx);
gx.selectAll(".domain").remove();
const gy = axes.append("g");
const yAxis = axisLeft(y).tickSize(margin.left - width - margin.right);
yAxis(gy);
gy.selectAll(".domain").remove();
svg
.append("g")
.attr("shape-rendering", "crispEdges")
.selectAll("rect")
.data(supported)
.join("rect")
.attr("x", (d) => x(d.releaseDate))
.attr("y", (d) => y(d.version)!)
.attr("width", (d) => x(d.endDate) - x(d.releaseDate))
.attr("height", y.bandwidth())
.attr("fill", (_, i) => (i % 2 === 0 ? "#3178c6" : "#235a97"));
const texts = svg
.append("g")
.attr("fill", "#ffffff")
.attr("text-anchor", "middle")
.attr("transform", `translate(0,${y.bandwidth() / 2})`);
texts
.append("g")
.attr("font-weight", "bold")
.selectAll("a")
.data(supported)
.join("a")
.attr(
"href",
(d) =>
`https://www.typescriptlang.org/docs/handbook/release-notes/typescript-${d.version.replace(
/[^0-9]+/,
"-"
)}.html`
)
.attr("fill", "#ffffff")
.append("text")
.attr("x", (d) => x(d.releaseDate) + (x(d.endDate) - x(d.releaseDate)) / 2)
.attr("y", (d) => y(d.version)!)
.attr("dy", "0.35em")
.text((d) => d.version);
texts
.append("g")
.attr("font-size", "smaller")
.selectAll("text")
.data(supported)
.join("text")
.attr("x", (d) => x(d.releaseDate) + (x(d.endDate) - x(d.releaseDate)) / 4)
.attr("y", (d) => y(d.version)!)
.attr("dy", "0.35em")
.text((d) => formatDate(d.releaseDate));
texts
.append("g")
.attr("font-size", "smaller")
.selectAll("text")
.data(supported)
.join("text")
.attr(
"x",
(d) => x(d.releaseDate) + ((x(d.endDate) - x(d.releaseDate)) * 3) / 4
)
.attr("y", (d) => y(d.version)!)
.attr("dy", "0.35em")
.text((d) => formatDate(d.endDate));
process.stdout.write(serialize(svg.node()!));