Skip to content

Commit

Permalink
reorganize view settings
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-vorobiov committed Dec 10, 2024
1 parent 5b74789 commit 087ece5
Show file tree
Hide file tree
Showing 21 changed files with 495 additions and 111 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Evolve Analytics

Inspired by Kewne's work on tracking milestones during runs, this userscript adds an "Analytics" tab to the evolve UI where you can see a breakdown of your evolve run times.
Inspired by Kewne's work on tracking milestones during runs, this userscript adds the "Analytics" tab to the evolve UI where you can see the breakdown of your evolve runs.

It does not require any other scripts to function.

Expand All @@ -22,6 +22,6 @@ You can click on the names of the milestones on the legend above the graph to to

## View modes

The `Total` mode shows all milestones stacked on top of each other as parts of a single run. It also shows last run's times for each milestone on the right side of the graph.
The `Timestamp` mode shows all milestones stacked on top of each other as parts of a single run. It also shows last run's times for each milestone on the right side of the graph.

Alternatively, the `Segmented` mode shows how many days it took to reach a milestone since the last one.
Alternatively, the `Duration` mode shows how many days it took to reach a milestone since the last one.
2 changes: 1 addition & 1 deletion evolve_analytics.meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Evolve Analytics
// @namespace http://tampermonkey.net/
// @version 0.8.0
// @version 0.9.0
// @description Track and see detailed information about your runs
// @author Sneed
// @match https://pmotschmann.github.io/Evolve/
Expand Down
13 changes: 10 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export type ViewConfig = {
resetType: keyof typeof resets,
universe?: keyof typeof universes,
mode: keyof typeof viewModes,
daysScale?: number,
smoothness: number,
showBars: boolean,
showLines: boolean,
fillArea: boolean,
numRuns?: number,
milestones: Record<string, boolean>,
additionalInfo: Array<keyof typeof additionalInformation>
Expand Down Expand Up @@ -137,7 +140,11 @@ export class ConfigManager extends Subscribable {
const view: ViewConfig = {
resetType: "ascend",
universe: this.game.universe,
mode: "bars",
mode: "timestamp",
showBars: true,
showLines: false,
fillArea: false,
smoothness: 0,
milestones: { "reset:ascend": true },
additionalInfo: []
};
Expand Down Expand Up @@ -173,6 +180,6 @@ export class ConfigManager extends Subscribable {
}

export function getConfig(game: Game) {
const config = loadConfig() ?? { version: 7, recordRuns: true, views: [] };
const config = loadConfig() ?? { version: 8, recordRuns: true, views: [] };
return new ConfigManager(game, config);
}
7 changes: 2 additions & 5 deletions src/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ export const universes = {
};

export const viewModes = {
"total": "Total",
"filled": "Total (filled)",
"bars": "Total (bars)",
"segmented": "Segmented",
"barsSegmented": "Segmented (bars)"
"timestamp": "Timestamp",
"duration": "Duration"
};

export const additionalInformation = {
Expand Down
5 changes: 3 additions & 2 deletions src/migration/3.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { buildings, resets, techs, universes, viewModes } from "../enums";
import { buildings, resets, techs, universes } from "../enums";
import { milestoneName } from "../milestones";
import { rotateMap, transformMap, lazyLoad } from "../utils";
import { viewModes7 as viewModes4 } from "./7";
import type { HistoryEntry, RunHistory } from "../history";
import type { Config4 } from "./4";
import type { LatestRun6 as LatestRun4 } from "./6";
Expand Down Expand Up @@ -28,7 +29,7 @@ export type Config3 = {

export function migrateConfig(config: Config3): Config4 {
const resetIDs = rotateMap(resets);
const viewModeIDs = rotateMap(viewModes);
const viewModeIDs = rotateMap(viewModes4);

function convertReset(resetName: string) {
return resetName === "Vacuum Collapse" ? "blackhole" : resetIDs[resetName];
Expand Down
6 changes: 3 additions & 3 deletions src/migration/4.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { resets, universes, viewModes } from "../enums";
import type { Config as Config6 } from "../config";
import type { resets, universes } from "../enums";
import type { Config7 as Config6, viewModes7 as viewModes6 } from "./7";

export type ViewConfig4 = {
resetType: keyof typeof resets,
universe?: keyof typeof universes,
mode: keyof typeof viewModes,
mode: keyof typeof viewModes6,
daysScale?: number,
numRuns?: number,
milestones: Record<string, boolean>
Expand Down
50 changes: 50 additions & 0 deletions src/migration/7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { resets, universes, additionalInformation } from "../enums";
import type { ViewConfig as ViewConfig8, Config as Config8 } from "../config";

export const viewModes7 = {
"total": "Total",
"filled": "Total (filled)",
"bars": "Total (bars)",
"segmented": "Segmented",
"barsSegmented": "Segmented (bars)"
};

export type ViewConfig7 = {
resetType: keyof typeof resets,
universe?: keyof typeof universes,
mode: keyof typeof viewModes7,
daysScale?: number,
numRuns?: number,
milestones: Record<string, boolean>,
additionalInfo: Array<keyof typeof additionalInformation>
}

export type Config7 = {
version: number,
recordRuns: boolean,
lastOpenViewIndex?: number,
views: ViewConfig7[]
}

function migrateView(view: ViewConfig7): ViewConfig8 {
const newView: ViewConfig8 = {
...view,
mode: ["segmented", "barsSegmented"].includes(view.mode) ? "duration" : "timestamp",
smoothness: 0,
showBars: ["bars", "barsSegmented"].includes(view.mode),
showLines: ["total", "filled", "segmented"].includes(view.mode),
fillArea: view.mode === "filled"
};

delete (newView as any).daysScale;

return newView;
}

export function migrate7(config: Config7): Config8 {
return {
...config,
version: 8,
views: config.views.map(migrateView)
};
}
6 changes: 6 additions & 0 deletions src/migration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as DB from "../database";
import { migrate3 } from "./3";
import { migrate4 } from "./4";
import { migrate6 } from "./6";
import { migrate7 } from "./7";

export function migrate() {
let config: any = DB.loadConfig();
Expand Down Expand Up @@ -29,6 +30,11 @@ export function migrate() {
migrated = true;
}

if (config.version === 7) {
config = migrate7(config);
migrated = true;
}

if (migrated) {
DB.saveConfig(config);
history !== null && DB.saveHistory(history);
Expand Down
119 changes: 72 additions & 47 deletions src/ui/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ import type { default as PlotType } from "@observablehq/plot";

declare const Plot: typeof PlotType;

function calculateYScale(plotPoints: PlotPoint[], view: View): [number, number] | undefined {
if (view.daysScale) {
return [0, view.daysScale];
}
else if (plotPoints.length === 0) {
// Default scale with empty history
return [0, 1000];
}
}

function lastRunEntries(plotPoints: PlotPoint[]): PlotPoint[] {
const timestamps: PlotPoint[] = [];

Expand All @@ -35,6 +25,29 @@ function lastRunEntries(plotPoints: PlotPoint[]): PlotPoint[] {
return timestamps.reverse();
}

function smooth(smoothness: number, history: HistoryEntry[], params: any) {
let avgWindowSize;
switch (smoothness) {
case 0:
avgWindowSize = 1;
break;

case 100:
avgWindowSize = history.length;
break;

default:
// Make the transformation from the smoothness % into the number of runs exponential
// because the average window has decreasingly less impact on the lines as it grows
const curveSteepness = 5;
const value = Math.exp(smoothness / 100 * curveSteepness - curveSteepness) * history.length;
avgWindowSize = Math.round(value) || 1;
break;
}

return Plot.windowY({ k: avgWindowSize }, params);
}

function* timestamps(plotPoints: PlotPoint[], key: "day" | "segment") {
const lastRunTimestamps = lastRunEntries(plotPoints).map(e => e[key]);

Expand All @@ -44,25 +57,25 @@ function* timestamps(plotPoints: PlotPoint[], key: "day" | "segment") {
});
}

function* areaMarks(plotPoints: PlotPoint[]) {
yield Plot.areaY(plotPoints, {
function* areaMarks(plotPoints: PlotPoint[], history: HistoryEntry[], smoothness: number) {
yield Plot.areaY(plotPoints, smooth(smoothness, history, {
x: "run",
y: "dayDiff",
z: "milestone",
fill: "milestone",
fillOpacity: 0.5
});
}));
}

function* lineMarks(plotPoints: PlotPoint[], key: "day" | "segment") {
yield Plot.line(plotPoints, {
function* lineMarks(plotPoints: PlotPoint[], history: HistoryEntry[], key: "day" | "segment", smoothness: number) {
yield Plot.lineY(plotPoints, smooth(smoothness, history, {
x: "run",
y: key,
z: "milestone",
stroke: "milestone",
// Draw the event lines on top of the other ones
sort: (entry: PlotPoint) => entry.dayDiff === undefined ? 1 : 0
});
}));
}

function* barMarks(plotPoints: PlotPoint[], key: "dayDiff" | "segment") {
Expand Down Expand Up @@ -108,7 +121,7 @@ function tipText(point: PlotPoint, key: "day" | "dayDiff" | "segment", history:
return `${prefix}: ${point.milestone} in ${point[key]} day(s)`;
}

function* linePointerMarks(plotPoints: PlotPoint[], key: "day" | "segment", history: HistoryEntry[]) {
function* linePointerMarks(plotPoints: PlotPoint[], history: HistoryEntry[], key: "day" | "segment") {
yield Plot.text(plotPoints, Plot.pointerX({
px: "run",
py: key,
Expand All @@ -130,7 +143,7 @@ function* linePointerMarks(plotPoints: PlotPoint[], key: "day" | "segment", hist
}));
}

function* rectPointerMarks(plotPoints: PlotPoint[], segmentKey: "dayDiff" | "segment", tipKey: "day" | "segment", history: HistoryEntry[]) {
function* rectPointerMarks(plotPoints: PlotPoint[], history: HistoryEntry[], segmentKey: "dayDiff" | "segment", tipKey: "day" | "segment") {
plotPoints = plotPoints.filter((entry: PlotPoint) => entry.dayDiff !== undefined);

// Transform pointer position from the point to the segment
Expand Down Expand Up @@ -164,38 +177,49 @@ export function makeGraph(history: HistoryManager, view: View, onSelect: (run: H
Plot.ruleY([0])
];

switch (view.mode) {
// Same as "barsSegmented" but milestones become a part of the segment on top when hidden
case "bars":
marks.push(...barMarks(plotPoints, "dayDiff"));
marks.push(...timestamps(plotPoints, "day"));
marks.push(...rectPointerMarks(plotPoints, "dayDiff", "day", filteredRuns));
break;
if (view.showBars) {
switch (view.mode) {
case "timestamp":
marks.push(...barMarks(plotPoints, "dayDiff"));
marks.push(...timestamps(plotPoints, "day"));
marks.push(...rectPointerMarks(plotPoints, filteredRuns, "dayDiff", "day"));
break;

case "duration":
marks.push(...barMarks(plotPoints, "segment"));
marks.push(...rectPointerMarks(plotPoints, filteredRuns, "segment", "segment"));
break;

default:
break;
}
}

// Vertical bars composed of individual segments stacked on top of each other
case "barsSegmented":
marks.push(...barMarks(plotPoints, "segment"));
marks.push(...rectPointerMarks(plotPoints, "segment", "segment", filteredRuns));
break;
if (view.showLines) {
switch (view.mode) {
case "timestamp":
if (view.fillArea) {
marks.push(...areaMarks(plotPoints, filteredRuns, view.smoothness));
}

// Same as "total" but with the areas between the lines filled
case "filled":
marks.push(...areaMarks(plotPoints));
// fall-through
marks.push(...lineMarks(plotPoints, filteredRuns, "day", view.smoothness));
marks.push(...timestamps(plotPoints, "day"));
break;

// Horizontal lines for each milestone timestamp
case "total":
marks.push(...lineMarks(plotPoints, "day"));
marks.push(...timestamps(plotPoints, "day"));
marks.push(...linePointerMarks(plotPoints, "day", filteredRuns));
break;
case "duration":
marks.push(...lineMarks(plotPoints, filteredRuns, "segment", view.smoothness));
marks.push(...timestamps(plotPoints, "segment"));
break;

// Horizontal lines for each milestone duration
case "segmented":
marks.push(...lineMarks(plotPoints, "segment"));
marks.push(...timestamps(plotPoints, "segment"));
marks.push(...linePointerMarks(plotPoints, "segment", filteredRuns));
break;
default:
break;
}

// Don't show the lines' pointer if the bars' one is shown or if the lines are smoothed
if (!view.showBars && view.smoothness === 0) {
const key = view.mode === "timestamp" ? "day" : "segment";
marks.push(...linePointerMarks(plotPoints, filteredRuns, key));
}
}

const milestones: string[] = Object.keys(view.milestones);
Expand All @@ -210,7 +234,8 @@ export function makeGraph(history: HistoryManager, view: View, onSelect: (run: H
});
}

const yScale = calculateYScale(plotPoints, view);
// Default scale with empty history
const yScale = plotPoints.length === 0 ? [0, 1000] : undefined;

const plot = Plot.plot({
width: 800,
Expand Down
2 changes: 1 addition & 1 deletion src/ui/milestoneSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function makeMilestoneSettings(view: View) {

const researchedTargetOptions = makeAutocompleteInput("Tech", Object.entries(techs).map(([id, name]) => ({ value: id, label: name })));

const eventTargetOptions = makeSelect(Object.entries(events)).css("width", "200px");
const eventTargetOptions = makeSelect(Object.entries(events));

function selectOptions(type: string) {
builtTargetOptions.toggle(type === "built");
Expand Down
10 changes: 10 additions & 0 deletions src/ui/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ html.dracula .bg-dark {
width: fit-content
}

.w-full {
width: 100%
}

.crossed {
text-decoration: line-through
}

.flex-container {
display: flex;
flex-wrap: wrap;
gap: 8px
}
Loading

0 comments on commit 087ece5

Please sign in to comment.