mirror of
https://github.com/pmret/website.git
synced 2024-11-08 12:12:27 +01:00
cool graph
This commit is contained in:
parent
8f76f0a1b5
commit
ec241dcd38
5
.postcssrc.js
Normal file
5
.postcssrc.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-preset-env": true,
|
||||
},
|
||||
}
|
12
package.json
12
package.json
@ -3,8 +3,16 @@
|
||||
"start": "parcel src/index.html",
|
||||
"build": "parcel build src/index.html"
|
||||
},
|
||||
"dependencies": {},
|
||||
"browserslist": "> 1% and last 2 years",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"recharts": "^2.0.0",
|
||||
"use-element-dimensions": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"parcel-bundler": "^1.12.4"
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"postcss-preset-env": "^6.7.0"
|
||||
}
|
||||
}
|
||||
|
162
src/ProgressPane.jsx
Normal file
162
src/ProgressPane.jsx
Normal file
@ -0,0 +1,162 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import { Area, XAxis, YAxis, AreaChart, CartesianGrid, Tooltip } from "recharts"
|
||||
import useDimensions from "use-element-dimensions"
|
||||
|
||||
const csvVersions = {
|
||||
"1": {
|
||||
timestamp: parseInt,
|
||||
commit: s => s,
|
||||
totalFuncs: parseInt,
|
||||
nonMatchingFuncs: parseInt,
|
||||
matchingFuncs: parseInt,
|
||||
totalBytes: b => parseInt(b),
|
||||
nonMatchingBytes: b => parseInt(b),
|
||||
matchingBytes: b => parseInt(b),
|
||||
},
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const csv = await fetch("https://papermar.io/reports/progress.csv")
|
||||
.then(response => response.text())
|
||||
|
||||
return csv
|
||||
.split("\n")
|
||||
.filter(row => row.length)
|
||||
.map(row => {
|
||||
const [version, ...data] = row.split(",")
|
||||
const structure = csvVersions[version]
|
||||
const obj = {}
|
||||
|
||||
for (const [key, transform] of Object.entries(structure)) {
|
||||
obj[key] = transform(data.shift())
|
||||
}
|
||||
|
||||
obj.percentBytes = Math.round((obj.matchingBytes / obj.totalBytes) * 100)
|
||||
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
export default function ProgressPane({ captionPortal }) {
|
||||
const [data, setData] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
.then(data => setData(data))
|
||||
}, [])
|
||||
|
||||
// TODO: cute spin animation when data loads
|
||||
|
||||
return <div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{data && <DataView data={data} captionPortal={captionPortal}/>}
|
||||
{!data && "Loading..."}
|
||||
</div>
|
||||
}
|
||||
|
||||
const MONTH = 2678400
|
||||
|
||||
function DataView({ data, captionPortal }) {
|
||||
const [chartDimensions, chartRef] = useDimensions()
|
||||
const latest = data[data.length - 1]
|
||||
const oldest = data[0]
|
||||
|
||||
let monthDates = []
|
||||
for (let i = new Date(1583020800000); i < new Date(latest.timestamp); i.setMonth(i.getMonth() + 1)) {
|
||||
if (i > new Date(oldest.timestamp)) {
|
||||
monthDates.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(monthDates)
|
||||
|
||||
const [selectedEntry, setSelectedEntry] = useState(null)
|
||||
|
||||
function renderTooltip(tip) {
|
||||
const entry = data.find(row => row.timestamp === tip.label)
|
||||
|
||||
setSelectedEntry(entry)
|
||||
|
||||
return <span/>
|
||||
}
|
||||
|
||||
return <>
|
||||
<table width="250" className="outline-invert">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Matched</td>
|
||||
<td className="thin align-right">{Math.round((latest.matchingBytes / latest.totalBytes) * 10000) / 100}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="shadow-box flex-grow">
|
||||
<div className="shadow-box-inner" style={{ paddingRight: ".7em", paddingTop: ".7em", "--text-outline": "transparent" }}>
|
||||
<div className="progress-chart" ref={chartRef}>
|
||||
{chartDimensions.width > 0 && <AreaChart width={chartDimensions.width} height={chartDimensions.height} data={data}>
|
||||
<XAxis dataKey="timestamp" type="number" scale="time" domain={["dataMin", "dataMax"]} ticks={monthDates} tickFormatter={formatDate}/>
|
||||
<YAxis type="number" unit="%" domain={[0, 100]} tickCount={11}/>
|
||||
|
||||
<CartesianGrid stroke="#eee" horizontalPoints={monthDates}/>
|
||||
|
||||
<Area
|
||||
type="linear"
|
||||
dataKey="percentBytes"
|
||||
unit="%"
|
||||
stroke="#e3ac34" strokeWidth={2}
|
||||
fill="#edc97e"
|
||||
dot={true}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
|
||||
<Tooltip content={renderTooltip}/>
|
||||
</AreaChart>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="shadow-box-title yellow">
|
||||
{selectedEntry ? formatDate(selectedEntry.timestamp, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
}) : ""}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{selectedEntry && captionPortal.current && createPortal(<EntryInfo entry={selectedEntry}/>, captionPortal.current)}
|
||||
</>
|
||||
}
|
||||
|
||||
function formatDate(timestamp, options={}) {
|
||||
const date = new Date(timestamp * 1000)
|
||||
|
||||
return new Intl.DateTimeFormat([], options).format(date)
|
||||
}
|
||||
|
||||
function EntryInfo({ entry }) {
|
||||
/*const [commitMessage, setCommitMessage] = useState(null)
|
||||
|
||||
useEffect(async () => {
|
||||
fetch(`https://api.github.com/repos/ethteck/papermario/commits/${entry.commit}`)
|
||||
.then(resp => resp.json())
|
||||
.then(resp => {
|
||||
setCommitMessage(resp.commit.message.split("\n")[0])
|
||||
})
|
||||
}, [entry.commit])*/
|
||||
|
||||
return <div>
|
||||
<a href={`https://github.com/ethteck/papermario/commit/${entry.commit}`}>
|
||||
{entry.commit.substr(0, 8)}
|
||||
</a>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="200">Matched</td>
|
||||
<td className="thin align-right">
|
||||
{Math.round((entry.matchingBytes / entry.totalBytes) * 10000) / 100}%
|
||||
({entry.matchingFuncs}/{entry.totalFuncs} functions)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
BIN
src/bg/caption-checker.png
Normal file
BIN
src/bg/caption-checker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 B |
BIN
src/bg/red-checker.png
Normal file
BIN
src/bg/red-checker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 B |
BIN
src/bg/yellow-checker.png
Normal file
BIN
src/bg/yellow-checker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 B |
214
src/index.css
Normal file
214
src/index.css
Normal file
@ -0,0 +1,214 @@
|
||||
@font-face {
|
||||
font-family: "Paper Mario Dialog Redesigned";
|
||||
src: url("pmdialog2.woff2") format("woff2");
|
||||
}
|
||||
|
||||
html {
|
||||
--dark: #313131;
|
||||
--light: #d6d6ce;
|
||||
|
||||
font-size: 28px;
|
||||
font-family: "Paper Mario Dialog Redesigned", sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
|
||||
color: var(--light);
|
||||
--text-outline: var(--dark);
|
||||
|
||||
background: #090942; /* TODO: dynamic star bg */
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
||||
/* https://owumaro.github.io/text-stroke-generator/ */
|
||||
text-shadow: var(--text-outline) 3px 0px 0px, var(--text-outline) 2.83487px 0.981584px 0px, var(--text-outline) 2.35766px 1.85511px 0px, var(--text-outline) 1.62091px 2.52441px 0px, var(--text-outline) 0.705713px 2.91581px 0px, var(--text-outline) -0.287171px 2.98622px 0px, var(--text-outline) -1.24844px 2.72789px 0px, var(--text-outline) -2.07227px 2.16926px 0px, var(--text-outline) -2.66798px 1.37182px 0px, var(--text-outline) -2.96998px 0.42336px 0px, var(--text-outline) -2.94502px -0.571704px 0px, var(--text-outline) -2.59586px -1.50383px 0px, var(--text-outline) -1.96093px -2.27041px 0px, var(--text-outline) -1.11013px -2.78704px 0px, var(--text-outline) -0.137119px -2.99686px 0px, var(--text-outline) 0.850987px -2.87677px 0px, var(--text-outline) 1.74541px -2.43999px 0px, var(--text-outline) 2.44769px -1.73459px 0px, var(--text-outline) 2.88051px -0.838247px 0px;
|
||||
}
|
||||
|
||||
a:any-link {
|
||||
color: #3796ff;
|
||||
--text-outline: #d3e5f9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
all: unset;
|
||||
|
||||
border-radius: 100em;
|
||||
padding: 0 .3em .2em .3em;
|
||||
cursor: pointer;
|
||||
|
||||
filter: brightness(1.0);
|
||||
will-change: filter;
|
||||
transition: filter 200ms;
|
||||
}
|
||||
|
||||
button.red {
|
||||
background: linear-gradient(#f04e54, #f04e54 10%, #b4313e 25%, #b4313e 90%, #7f1729 95%);
|
||||
}
|
||||
|
||||
button.yellow {
|
||||
background: linear-gradient(#f0c74e, #f0c74e 10%, #b48b31 25%, #b48b31 90%, #7f5617 95%);
|
||||
}
|
||||
|
||||
button.inactive {
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(1.0);
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-right: .25em;
|
||||
}
|
||||
|
||||
#container {
|
||||
/* TODO: use flex on body */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 1400px;
|
||||
max-width: calc(100vw - 10vh);
|
||||
|
||||
height: 1200px;
|
||||
max-height: calc(100vh - 10vw);
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-bottom: -.6em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
|
||||
background-repeat: repeat;
|
||||
background-size: 32px;
|
||||
background-position: center;
|
||||
image-rendering: pixelated;
|
||||
|
||||
border-radius: 1rem;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
transition: 600ms transform ease-in-out;
|
||||
}
|
||||
|
||||
main.red {
|
||||
background-color: #e2b6b3;
|
||||
background-image: url(bg/red-checker.png);
|
||||
}
|
||||
|
||||
main.yellow {
|
||||
background-color: #e2d8b3;
|
||||
background-image: url(bg/yellow-checker.png);
|
||||
}
|
||||
|
||||
main > * {
|
||||
image-rendering: initial;
|
||||
}
|
||||
|
||||
.shadow-box {
|
||||
background: #ffffff66;
|
||||
padding: 1em;
|
||||
margin: .5em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
border-radius: 16px;
|
||||
box-shadow: .5em .5em 3px -.2em #00000044;
|
||||
|
||||
border-top: 4px solid #ffffff33;
|
||||
padding-top: calc(1em - 2px);
|
||||
|
||||
border-left: 2px solid #ffffff33;
|
||||
border-right: 2px solid #00000055;
|
||||
border-bottom: 2px solid #00000055;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shadow-box-inner {
|
||||
border-radius: 12px;
|
||||
box-shadow: inset .3em .3em 3px #00000033;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
background: white;
|
||||
}
|
||||
|
||||
button.shadow-box-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -1em;
|
||||
transform: translateX(-50%);
|
||||
|
||||
width: 60%;
|
||||
|
||||
text-align: center;
|
||||
|
||||
border-radius: 1em;
|
||||
|
||||
padding-top: .2em;
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
.caption {
|
||||
background-image: url(bg/caption-checker.png);
|
||||
background-repeat: repeat;
|
||||
background-size: 32px;
|
||||
background-position: center;
|
||||
image-rendering: pixelated;
|
||||
|
||||
border-radius: 1rem;
|
||||
width: 85%;
|
||||
height: 3rem;
|
||||
|
||||
padding: 8px 16px;
|
||||
margin-top: -14px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.progress-chart {
|
||||
flex: 1;
|
||||
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.outline-invert {
|
||||
color: var(--dark);
|
||||
--text-outline: var(--light);
|
||||
}
|
||||
|
||||
.thin {
|
||||
font-weight: 400;
|
||||
text-shadow: var(--text-outline) 2px 0px 0px, var(--text-outline) 1.75517px 0.958851px 0px, var(--text-outline) 1.0806px 1.68294px 0px, var(--text-outline) 0.141474px 1.99499px 0px, var(--text-outline) -0.832294px 1.81859px 0px, var(--text-outline) -1.60229px 1.19694px 0px, var(--text-outline) -1.97998px 0.28224px 0px, var(--text-outline) -1.87291px -0.701566px 0px, var(--text-outline) -1.30729px -1.5136px 0px, var(--text-outline) -0.421592px -1.95506px 0px, var(--text-outline) 0.567324px -1.91785px 0px, var(--text-outline) 1.41734px -1.41108px 0px, var(--text-outline) 1.92034px -0.558831px 0px;
|
||||
}
|
||||
|
||||
.flex-row { display: flex; flex-direction: row }
|
||||
.flex-grow { flex: 1; height: 0 }
|
||||
.flex-spacer { width: 1em; height: 1em }
|
||||
|
||||
.align-right { text-align: right }
|
@ -1,11 +1,48 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8"/>
|
||||
<title>Paper Mario Reverse Engineering</title>
|
||||
|
||||
<link rel="stylesheet" href="index.css"/>
|
||||
<script async defer src="main.jsx"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Hello world!
|
||||
<div id="container">
|
||||
<!--
|
||||
<nav>
|
||||
<button>Info</button>
|
||||
<button>Progress</button>
|
||||
<button>Map</button>
|
||||
<button>Party</button>
|
||||
<button>Spirits</button>
|
||||
<button>Map</button>
|
||||
</nav>
|
||||
<main id="main">
|
||||
<table class="outline-invert" width="400">
|
||||
<tr>
|
||||
<td>Overall</td>
|
||||
<td class="thin align-right" id="matched-rom-percent"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Functions</td>
|
||||
<td class="thin align-right" id="functions-ratio"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Play Time</td>
|
||||
<td class="thin align-right" id="play-time"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="shadow-box flex-grow" id="progress-chart-container">
|
||||
<canvas class="shadow-box-inner" id="progress-chart"></canvas>
|
||||
<div class="shadow-box-title" id="progress-chart-tooltip-title"></div>
|
||||
</div>
|
||||
|
||||
<div id="progress-chart-tooltip-description"></div>
|
||||
</main>
|
||||
-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
14
src/index.js
Normal file
14
src/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import moment from "moment"
|
||||
|
||||
import("./progress.js").then(async ({ fetchData, functionsChart }) => {
|
||||
const data = await fetchData()
|
||||
|
||||
functionsChart(data, document.getElementById("progress-chart"))
|
||||
|
||||
const first = data[0]
|
||||
const latest = data[data.length - 1]
|
||||
|
||||
document.getElementById("matched-rom-percent").innerText = Math.round((latest.matchingBytes / latest.totalBytes) * 10000) / 100 + "% matched" // TODO: include data
|
||||
document.getElementById("functions-ratio").innerText = latest.matchingFuncs + "/" + latest.totalFuncs
|
||||
document.getElementById("play-time").innerText = moment(latest.timestamp).from(first.timestamp, true)
|
||||
}).catch(console.error)
|
94
src/main.jsx
Normal file
94
src/main.jsx
Normal file
@ -0,0 +1,94 @@
|
||||
import React, { useState, useRef, useEffect } from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import clsx from "clsx"
|
||||
|
||||
import ProgressPane from "./ProgressPane"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
slug: "/",
|
||||
name: "Info",
|
||||
color: "red",
|
||||
pane: () => <div>I am the info page</div>,
|
||||
},
|
||||
{
|
||||
slug: "/progress",
|
||||
name: "Progress",
|
||||
color: "yellow",
|
||||
pane: (props) => <ProgressPane {...props}/>
|
||||
},
|
||||
]
|
||||
|
||||
let routedTabIndex = tabs.findIndex(tab => tab.slug === document.location.pathname)
|
||||
if (routedTabIndex == -1) routedTabIndex = 0
|
||||
|
||||
function App() {
|
||||
const [paneIndex, setPaneIndex] = useState(routedTabIndex)
|
||||
const [tabIndex, setTabIndex] = useState(routedTabIndex)
|
||||
const [rotation, setRotation] = useState(0)
|
||||
const [flip, setFlip] = useState(false)
|
||||
|
||||
const pane = useRef()
|
||||
let lockTabs = false // not state
|
||||
|
||||
function switchToTab(index) {
|
||||
if (index === paneIndex || index === tabIndex) return
|
||||
|
||||
console.info("switching to tab", index)
|
||||
|
||||
setRotation(rotation - 180)
|
||||
setTabIndex(index)
|
||||
|
||||
history.pushState(null, tabs[index].name, tabs[index].slug)
|
||||
|
||||
setTimeout(() => {
|
||||
setPaneIndex(index)
|
||||
setFlip(!flip)
|
||||
}, 300) // half the animation time
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function listener() {
|
||||
switchToTab(tabs.findIndex(tab => tab.slug === document.location.pathname))
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", listener)
|
||||
return () => window.removeEventListener("popstate", listener)
|
||||
}, [rotation, flip, tabIndex, paneIndex])
|
||||
|
||||
const captionPortal = useRef()
|
||||
|
||||
return <>
|
||||
<nav>
|
||||
{tabs.map((tab, index) => {
|
||||
return <button
|
||||
key={tab.name}
|
||||
className={clsx("tab", tab.color, { "inactive": index !== tabIndex })}
|
||||
onClick={() => {
|
||||
if (lockTabs) return
|
||||
lockTabs = true
|
||||
|
||||
switchToTab(index)
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
})}
|
||||
</nav>
|
||||
<main id="main" ref={pane} className={clsx(tabs[paneIndex].color)} style={{
|
||||
transform: `perspective(4000px) rotateX(${rotation}deg)`,
|
||||
}}>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
transform: `rotateX(${flip ? '180deg' : '0deg'})`,
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
{tabs[paneIndex].pane({ captionPortal })}
|
||||
</div>
|
||||
</main>
|
||||
<div class="caption outline-invert" ref={captionPortal}></div>
|
||||
</>
|
||||
}
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById("container"))
|
BIN
src/pmdialog2.woff2
Normal file
BIN
src/pmdialog2.woff2
Normal file
Binary file not shown.
121
src/progress.js
Normal file
121
src/progress.js
Normal file
@ -0,0 +1,121 @@
|
||||
import Chart from "chart.js"
|
||||
|
||||
const csvVersions = {
|
||||
"1": {
|
||||
timestamp: s => new Date(parseInt(s) * 1000),
|
||||
commit: s => s,
|
||||
totalFuncs: parseInt,
|
||||
nonMatchingFuncs: parseInt,
|
||||
matchingFuncs: parseInt,
|
||||
totalBytes: parseInt,
|
||||
nonMatchingBytes: parseInt,
|
||||
matchingBytes: parseInt,
|
||||
},
|
||||
}
|
||||
|
||||
export async function fetchData() {
|
||||
const csv = await fetch("https://papermar.io/reports/progress.csv")
|
||||
.then(response => response.text())
|
||||
|
||||
return csv
|
||||
.split("\n")
|
||||
.filter(row => row.length)
|
||||
.map(row => {
|
||||
const [version, ...data] = row.split(",")
|
||||
const structure = csvVersions[version]
|
||||
const obj = {}
|
||||
|
||||
for (const [key, transform] of Object.entries(structure)) {
|
||||
obj[key] = transform(data.shift())
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
export async function functionsChart(data, ctx) {
|
||||
return new Chart(ctx, {
|
||||
type: "line",
|
||||
data: {
|
||||
datasets: [
|
||||
{
|
||||
label: "Matching Functions",
|
||||
borderColor: "transparent",
|
||||
backgroundColor: "#7f5617",
|
||||
pointBackgroundColor: "transparent",
|
||||
pointBorderColor: "black",
|
||||
data: data.map(row => {
|
||||
return {
|
||||
x: row.timestamp,
|
||||
y: row.matchingFuncs,
|
||||
}
|
||||
}),
|
||||
lineTension: 0,
|
||||
},
|
||||
{
|
||||
label: "Split Functions",
|
||||
borderColor: "transparent",
|
||||
backgroundColor: "#b48b31",
|
||||
pointBackgroundColor: "transparent",
|
||||
pointBorderColor: "black",
|
||||
data: data.map(row => {
|
||||
return {
|
||||
x: row.timestamp,
|
||||
y: row.totalFuncs,
|
||||
}
|
||||
}),
|
||||
lineTension: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
//distribution: "series",
|
||||
ticks: {
|
||||
max: Date.now(),
|
||||
fontFamily: "Paper Mario Dialog Redesigned",
|
||||
},
|
||||
time: {
|
||||
isoWeekday: true,
|
||||
unit: "month",
|
||||
},
|
||||
gridLines: {
|
||||
drawBorder: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
label: "Functions",
|
||||
ticks: {
|
||||
fontFamily: "Paper Mario Dialog Redesigned",
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
intersect: false,
|
||||
custom(tooltipModel) {
|
||||
const title = document.getElementById("progress-chart-tooltip-title")
|
||||
const desc = document.getElementById("rogress-chart-tooltip-description")
|
||||
|
||||
if (!tooltipModel.dataPoints) {
|
||||
title.innerText = ""
|
||||
desc.innerText = ""
|
||||
}
|
||||
|
||||
const row = data[tooltipModel.dataPoints[0].index]
|
||||
console.log(row)
|
||||
|
||||
title.innerText = row.commit
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user