chore(deps): migrate from chartist to d3 (closes #1142)
This commit is contained in:
1
.github/actions/spelling/expect.txt
vendored
1
.github/actions/spelling/expect.txt
vendored
@@ -211,7 +211,6 @@ Nixinova
|
|||||||
NOASSERTION
|
NOASSERTION
|
||||||
nocase
|
nocase
|
||||||
nodeca
|
nodeca
|
||||||
nodechartist
|
|
||||||
nodejs
|
nodejs
|
||||||
notoken
|
notoken
|
||||||
octicon
|
octicon
|
||||||
|
|||||||
@@ -119,8 +119,6 @@ Below is a list of used packages.
|
|||||||
* To parse and handle emojis/[twemojis](https://github.com/twitter/twemoji)
|
* To parse and handle emojis/[twemojis](https://github.com/twitter/twemoji)
|
||||||
* [jshemas/openGraphScraper](https://github.com/jshemas/openGraphScraper)
|
* [jshemas/openGraphScraper](https://github.com/jshemas/openGraphScraper)
|
||||||
* To retrieve open graphs metadata
|
* To retrieve open graphs metadata
|
||||||
* [panosoft/node-chartist](https://github.com/panosoft/node-chartist) and [gionkunz/chartist-js](https://github.com/gionkunz/chartist-js)
|
|
||||||
* To display embed SVG charts
|
|
||||||
* [rbren/rss-parser](https://github.com/rbren/rss-parser)
|
* [rbren/rss-parser](https://github.com/rbren/rss-parser)
|
||||||
* To parse RSS streams
|
* To parse RSS streams
|
||||||
* [Nixinova/Linguist](https://github.com/Nixinova/Linguist)
|
* [Nixinova/Linguist](https://github.com/Nixinova/Linguist)
|
||||||
|
|||||||
@@ -84,7 +84,6 @@
|
|||||||
"color": "^4.2.3",
|
"color": "^4.2.3",
|
||||||
"gifencoder": "^2.0.1",
|
"gifencoder": "^2.0.1",
|
||||||
"libxmljs2": "^0.31.0",
|
"libxmljs2": "^0.31.0",
|
||||||
"node-chartist": "^1.0.5",
|
|
||||||
"rss": "^1.2.2",
|
"rss": "^1.2.2",
|
||||||
"yargs-parser": "^21.1.1"
|
"yargs-parser": "^21.1.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,14 +207,6 @@ export function stripemojis(string) {
|
|||||||
return string.replace(/[^\p{L}\p{N}\p{P}\p{Z}^$\n]/gu, "")
|
return string.replace(/[^\p{L}\p{N}\p{P}\p{Z}^$\n]/gu, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Chartist */
|
|
||||||
export async function chartist() {
|
|
||||||
const css = `<style data-optimizable="true">${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "node-chartist/dist/main.css")).catch(_ => "")}</style>`
|
|
||||||
const {default: nodechartist} = await import(url.pathToFileURL(paths.join(__module(import.meta.url), "../../../node_modules", "/node-chartist/lib/index.js")))
|
|
||||||
return (await nodechartist(...arguments))
|
|
||||||
.replace(/class="ct-chart-line">/, `class="ct-chart-line">${css}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Language analyzer (single file) */
|
/**Language analyzer (single file) */
|
||||||
export async function language({filename, patch}) {
|
export async function language({filename, patch}) {
|
||||||
console.debug(`metrics/language > ${filename}`)
|
console.debug(`metrics/language > ${filename}`)
|
||||||
@@ -816,3 +808,193 @@ export class D3node {
|
|||||||
return this.element.select("svg").node()?.outerHTML || ""
|
return this.element.select("svg").node()?.outerHTML || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Graph utilities */
|
||||||
|
export const Graph = {
|
||||||
|
/**Timeline graph */
|
||||||
|
timeline() {
|
||||||
|
return this.graph("time", ...arguments)
|
||||||
|
},
|
||||||
|
/**Line graph */
|
||||||
|
line() {
|
||||||
|
return this.graph("line", ...arguments)
|
||||||
|
},
|
||||||
|
/**Basic Graph */
|
||||||
|
graph(type, data, {area = true, points = true, text = true, low = NaN, high = NaN, match = null, labels = null, width = 480, height = 315, ticks = 0} = {}) {
|
||||||
|
//Generate SVG
|
||||||
|
const margin = {top:10, left:10, right:10, bottom:45}
|
||||||
|
const d3n = new D3node()
|
||||||
|
const svg = d3n.createSVG(width, height)
|
||||||
|
|
||||||
|
//Data
|
||||||
|
const X = data.map(({x}) => x)
|
||||||
|
const start = X.at(0)
|
||||||
|
const end = X.at(-1)
|
||||||
|
const Y = data.map(({y}) => y)
|
||||||
|
const extremum = Math.max(...Y)
|
||||||
|
high = !Number.isNaN(high) ? high : extremum
|
||||||
|
low = !Number.isNaN(low) ? low : 0
|
||||||
|
const T = data.map(({text}, i) => text ?? Y[i])
|
||||||
|
|
||||||
|
//Time range
|
||||||
|
const x = (type === "time" ? d3.scaleTime() : d3.scaleLinear())
|
||||||
|
.domain([start, end])
|
||||||
|
.range([margin.top, width - margin.left - margin.right])
|
||||||
|
let xticks = d3.axisBottom(x).tickSizeOuter(0)
|
||||||
|
if (labels)
|
||||||
|
xticks = xticks.tickFormat((_, i) => labels[i])
|
||||||
|
if (ticks)
|
||||||
|
xticks = xticks.ticks(ticks)
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", `translate(${margin.left},${height - margin.bottom})`)
|
||||||
|
.call(xticks)
|
||||||
|
.call(g => g.select(".domain").attr("stroke", "rgba(127, 127, 127, .8)"))
|
||||||
|
.call(g => g.selectAll(".tick line").attr("stroke-opacity", 0.5))
|
||||||
|
.selectAll("text")
|
||||||
|
.attr("transform", "translate(-5,5) rotate(-45)")
|
||||||
|
.style("text-anchor", "end")
|
||||||
|
.style("font-size", 20)
|
||||||
|
.attr("fill", "rgba(127, 127, 127, .8)")
|
||||||
|
|
||||||
|
//Data range
|
||||||
|
const y = d3.scaleLinear()
|
||||||
|
.domain([high, low])
|
||||||
|
.range([margin.left, height - margin.top - margin.bottom])
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top})`)
|
||||||
|
.call(d3.axisRight(y).ticks(Math.round(height/50)).tickSize(width - margin.left - margin.right))
|
||||||
|
.call(g => g.select(".domain").remove())
|
||||||
|
.call(g => g.selectAll(".tick line").attr("stroke-opacity", 0.5).attr("stroke-dasharray", "2,2"))
|
||||||
|
.call(g => g.selectAll(".tick text").attr("x", 0).attr("dy", -4))
|
||||||
|
.selectAll("text")
|
||||||
|
.style("font-size", 20)
|
||||||
|
.attr("fill", "rgba(127, 127, 127, .8)")
|
||||||
|
|
||||||
|
//Generate graph line
|
||||||
|
const datum = Y.map((y, i) => [X.at(i), y])
|
||||||
|
const tdatum = Y.map((y, i) => [X.at(i), y, T[i]])
|
||||||
|
const xticked = xticks.scale().ticks(xticks.ticks()[0])
|
||||||
|
const yticked = match?.(tdatum, xticked) ?? tdatum
|
||||||
|
svg.append("path")
|
||||||
|
.datum(datum)
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top})`)
|
||||||
|
.attr(
|
||||||
|
"d",
|
||||||
|
d3.line()
|
||||||
|
.curve(d3.curveLinear)
|
||||||
|
.x(d => x(d[0]))
|
||||||
|
.y(d => y(d[1]))
|
||||||
|
)
|
||||||
|
.attr("fill", "transparent")
|
||||||
|
.attr("stroke", "#87ceeb")
|
||||||
|
.attr("stroke-width", 2)
|
||||||
|
|
||||||
|
//Generate graph area
|
||||||
|
if (area) {
|
||||||
|
svg.append("path")
|
||||||
|
.datum(datum)
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top})`)
|
||||||
|
.attr(
|
||||||
|
"d",
|
||||||
|
d3.area()
|
||||||
|
.curve(d3.curveLinear)
|
||||||
|
.x(d => x(d[0]))
|
||||||
|
.y0(d => y(d[1]))
|
||||||
|
.y1(() => y(low)),
|
||||||
|
)
|
||||||
|
.attr("fill", "rgba(88, 166, 255, .1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate graph points
|
||||||
|
if (points) {
|
||||||
|
svg.append("g")
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(yticked)
|
||||||
|
.join("circle")
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top})`)
|
||||||
|
.attr("cx", d => x(d[0]))
|
||||||
|
.attr("cy", d => y(d[1]))
|
||||||
|
.attr("r", 2)
|
||||||
|
.attr("fill", "#106cbc")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate graph text
|
||||||
|
if (text) {
|
||||||
|
svg.append("g")
|
||||||
|
.attr("fill", "currentColor")
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("font-family", "sans-serif")
|
||||||
|
.attr("font-size", 10)
|
||||||
|
.attr("stroke", "white")
|
||||||
|
.attr("stroke-linejoin", "round")
|
||||||
|
.attr("stroke-width", 4)
|
||||||
|
.attr("paint-order", "stroke fill")
|
||||||
|
.selectAll("text")
|
||||||
|
.data(yticked)
|
||||||
|
.join("text")
|
||||||
|
.attr("transform", `translate(${margin.left},${margin.top-4})`)
|
||||||
|
.attr("x", d => x(d[0]))
|
||||||
|
.attr("y", d => y(d[1]))
|
||||||
|
.text(d => d[2] ? d[2] : "")
|
||||||
|
.attr("fill", "rgba(127, 127, 127, .8)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return d3n.svgString()
|
||||||
|
},
|
||||||
|
/**Pie Graph */
|
||||||
|
pie(data, {colors, width = 480, height = 315} = {}) {
|
||||||
|
//Generate SVG
|
||||||
|
const radius = Math.min(width, height) / 2
|
||||||
|
const d3n = new D3node()
|
||||||
|
const svg = d3n.createSVG(width, height)
|
||||||
|
|
||||||
|
//Data
|
||||||
|
const K = Object.keys(data)
|
||||||
|
const V = Object.values(data)
|
||||||
|
const I = d3.range(K.length).filter(i => !Number.isNaN(V[i]))
|
||||||
|
|
||||||
|
//Construct arcs
|
||||||
|
const color = d3.scaleOrdinal(K, d3.schemeSpectral[K.length])
|
||||||
|
const arcs = d3.pie().padAngle(1/radius).sort(null).value(i => V[i])(I)
|
||||||
|
const arc = d3.arc().innerRadius(0).outerRadius(radius)
|
||||||
|
const labels = d3.arc().innerRadius(radius/2).outerRadius(radius/2)
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", `translate(${width/2},${height/2})`)
|
||||||
|
.attr("stroke", "white")
|
||||||
|
.attr("stroke-width", 1)
|
||||||
|
.attr("stroke-linejoin", "round")
|
||||||
|
.selectAll("path")
|
||||||
|
.data(arcs)
|
||||||
|
.join("path")
|
||||||
|
.attr("fill", d => colors?.[K[d.data]] ?? color(K[d.data]))
|
||||||
|
.attr("d", arc)
|
||||||
|
.append("title")
|
||||||
|
.text(d => `${K[d.data]}\n${V[d.data]}`)
|
||||||
|
|
||||||
|
svg.append("g")
|
||||||
|
.attr("transform", `translate(${width/2},${height/2})`)
|
||||||
|
.attr("font-family", "sans-serif")
|
||||||
|
.attr("font-size", 12)
|
||||||
|
.attr("text-anchor", "middle")
|
||||||
|
.attr("fill", "white")
|
||||||
|
.attr("stroke", "rbga(0,0,0,.9)")
|
||||||
|
.attr("paint-order", "stroke fill")
|
||||||
|
.selectAll("text")
|
||||||
|
.data(arcs)
|
||||||
|
.join("text")
|
||||||
|
.attr("transform", d => `translate(${labels.centroid(d)})`)
|
||||||
|
.selectAll("tspan")
|
||||||
|
.data(d => {
|
||||||
|
const lines = `${K[d.data]}\n${V[d.data]}`.split(/\n/)
|
||||||
|
return (d.endAngle - d.startAngle) > 0.25 ? lines : lines.slice(0, 1)
|
||||||
|
})
|
||||||
|
.join("tspan")
|
||||||
|
.attr("x", 0)
|
||||||
|
.attr("y", (_, i) => `${i * 1.1}em`)
|
||||||
|
.attr("font-weight", (_, i) => i ? null : "bold")
|
||||||
|
.text(d => d)
|
||||||
|
|
||||||
|
return d3n.svgString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user