完成画图

master
yutent 2023-06-08 12:45:35 +08:00
parent a00d013597
commit d1e708cb80
2 changed files with 101 additions and 10 deletions

View File

@ -15,7 +15,6 @@ export default class Github extends Controller {
async toplangsAction() { async toplangsAction() {
let { username, count = 6 } = this.request.get() let { username, count = 6 } = this.request.get()
let token = this.context.get('token') let token = this.context.get('token')
let { data } = await fetch('https://api.github.com/graphql', { let { data } = await fetch('https://api.github.com/graphql', {
headers: { headers: {
Authorization: 'Bearer ' + token, Authorization: 'Bearer ' + token,
@ -50,6 +49,12 @@ query {
let dict = {} let dict = {}
let langs = [] let langs = []
if (count > 16) {
count = 16
} else if (count < 2) {
count = 2
}
nodes.forEach(it => { nodes.forEach(it => {
for (let lang of it.languages.edges) { for (let lang of it.languages.edges) {
if (dict[lang.node.name]) { if (dict[lang.node.name]) {
@ -67,8 +72,6 @@ query {
langs.sort((a, b) => b.size - a.size) langs.sort((a, b) => b.size - a.size)
this.response.set('Content-Type', 'image/svg+xml') this.response.set('Content-Type', 'image/svg+xml')
this.response.end(render({})) this.response.end(render({ langs: langs.slice(0, count) }))
// this.response.send(200, { langs: langs.slice(0, count) })
} }
} }

View File

@ -7,29 +7,117 @@
function html(strs, ...vals) { function html(strs, ...vals) {
let output = '' let output = ''
for (let it of strs) { for (let it of strs) {
output += it + (vals.shift() || '') output += it + (vals.shift() ?? '')
} }
return output return output
} }
export function render({ title = 'Most Used Languages' }) { function text({ color, name, pc, idx }) {
let y = 32 + ~~(idx / 2 + 1) * 36
let x1 = idx % 2 === 0 ? 27 : 227
let x2 = idx % 2 === 0 ? 44 : 244
return html`
<g class="lang">
<circle cx="${x1}" cy="${y}" r="5" fill="${color}" />
<text x="${x2}" y="${y + 4}">${name} ${pc}%</text>
</g>
`
}
function pie(langs = [], sum = 0, height) {
// 圆心坐标
let cx = 540
let radius = ~~((height - 64) / 2) // 最小半径
let cy = radius + 48
let per = 0.25
let deg = per * 2 * Math.PI // 从90度开始计算
return langs
.map((it, idx) => {
// 扇形起始坐标
let r = +radius.toFixed(3)
let lx = +(cx + Math.sin(deg) * r).toFixed(3)
let ly = +(cy - Math.cos(deg) * r).toFixed(3)
let nx, ny
let _per = +(it.size / sum).toFixed(4)
radius *= 1.05
per += _per
deg = per * 2 * Math.PI
nx = +(cx + Math.sin(deg) * r * (per > 0.5 ? 1 : -1)).toFixed(3)
ny = +(cy - Math.cos(deg) * r).toFixed(3)
return html`
<path
class="pie pie-${idx}"
d="M${cx} ${cy} L${lx} ${ly} A${r} ${r} 0 ${_per > 0.5
? 1
: 0} 1 ${nx} ${ny} Z"
fill="${it.color}"
/>
`
})
.join('')
}
export function render({ title = 'Most Used Languages', langs = [] } = {}) {
let sum = langs.reduce((n, it) => n + it.size, 0)
let height = ~~(langs.length / 2 + 1) * 36 + 32
if (height < 140) {
height = 140
}
return html` return html`
<svg <svg
width="600" width="800"
height="400" height="${height}"
viewBox="0 0 600 400" viewBox="0 0 800 ${height}"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<style> <style>
.header { .header {
font: bold 20px 'Segoe UI', Ubuntu, Sans-Serif; font: bold 22px 'Segoe UI', Ubuntu, Sans-Serif;
fill: #64b5f6; fill: #64b5f6;
} }
.lang {
font: 14px Menlo, Monaco, Consolas, 'Courier New', monospace;
fill: #64748b;
}
.pie {
opacity: 0;
animation: fade 0.5s ease-in-out forwards;
}
${langs
.map((it, i) => `.pie-${i} {animation-delay: ${i * 100}ms}`)
.join('\n')}
@keyframes
fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style> </style>
<g transform="translate(24, 32)"> <g transform="translate(24, 32)">
<text class="header">${title}</text> <text class="header">${title}</text>
</g> </g>
<g class="lang-list">
${langs
.map((it, idx) =>
text({
color: it.color,
name: it.name,
pc: +((100 * it.size) / sum).toFixed(2),
idx
})
)
.join('')}
</g>
<g class="lang-pie">${pie(langs, sum, height)}</g>
</svg> </svg>
` `
} }