kalfa 3.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kalfa/__init__.py +19 -0
- kalfa/api.py +284 -0
- kalfa/board/__init__.py +1 -0
- kalfa/board/answers.py +419 -0
- kalfa/board/server.py +536 -0
- kalfa/board/static/api.js +176 -0
- kalfa/board/static/app.js +122 -0
- kalfa/board/static/board.css +228 -0
- kalfa/board/static/charts.css +46 -0
- kalfa/board/static/components/badge.js +14 -0
- kalfa/board/static/components/chart.js +204 -0
- kalfa/board/static/components/diagram.js +857 -0
- kalfa/board/static/components/error.js +17 -0
- kalfa/board/static/components/loading.js +7 -0
- kalfa/board/static/components/progress.js +25 -0
- kalfa/board/static/components/settings.js +418 -0
- kalfa/board/static/components/spark.js +43 -0
- kalfa/board/static/components/table.js +156 -0
- kalfa/board/static/diagram.css +76 -0
- kalfa/board/static/format.js +138 -0
- kalfa/board/static/index.html +34 -0
- kalfa/board/static/plot.js +594 -0
- kalfa/board/static/predictions.css +20 -0
- kalfa/board/static/store.js +215 -0
- kalfa/board/static/views/compare.js +185 -0
- kalfa/board/static/views/home.js +90 -0
- kalfa/board/static/views/record/config.js +81 -0
- kalfa/board/static/views/record/curves.js +137 -0
- kalfa/board/static/views/record/data.js +378 -0
- kalfa/board/static/views/record/files.js +128 -0
- kalfa/board/static/views/record/log.js +164 -0
- kalfa/board/static/views/record/model.js +286 -0
- kalfa/board/static/views/record/overview.js +216 -0
- kalfa/board/static/views/record/plots.js +103 -0
- kalfa/board/static/views/record/predictions.js +834 -0
- kalfa/board/static/views/record.js +179 -0
- kalfa/board/static/views/runs.js +124 -0
- kalfa/board/static/views/sweep.js +282 -0
- kalfa/board/watch.py +87 -0
- kalfa/cli.py +371 -0
- kalfa/commands/__init__.py +0 -0
- kalfa/commands/common.py +8 -0
- kalfa/commands/export.py +117 -0
- kalfa/commands/generate.py +31 -0
- kalfa/commands/guide/__init__.py +101 -0
- kalfa/commands/guide/about.py +93 -0
- kalfa/commands/guide/calls.py +102 -0
- kalfa/commands/guide/sections.py +186 -0
- kalfa/commands/guide/seen.py +307 -0
- kalfa/commands/guide/show.py +106 -0
- kalfa/commands/guide/training.py +333 -0
- kalfa/commands/guide/words.py +97 -0
- kalfa/commands/legos.py +508 -0
- kalfa/commands/plots.py +27 -0
- kalfa/commands/report/__init__.py +64 -0
- kalfa/commands/report/page.py +112 -0
- kalfa/commands/report/predictions.py +216 -0
- kalfa/commands/report/sections.py +356 -0
- kalfa/commands/stop.py +60 -0
- kalfa/commands/sweep/__init__.py +202 -0
- kalfa/commands/sweep/checks.py +98 -0
- kalfa/commands/sweep/plan.py +183 -0
- kalfa/commands/sweep/points.py +123 -0
- kalfa/commands/sweep/runner.py +100 -0
- kalfa/commands/sweep/space.py +67 -0
- kalfa/commands/sweep/strategies.py +228 -0
- kalfa/core/__init__.py +0 -0
- kalfa/core/aliases.py +229 -0
- kalfa/core/build.py +727 -0
- kalfa/core/check.py +550 -0
- kalfa/core/coerce.py +36 -0
- kalfa/core/context.py +149 -0
- kalfa/core/device.py +63 -0
- kalfa/core/errors.py +45 -0
- kalfa/core/graph.py +304 -0
- kalfa/core/history.py +70 -0
- kalfa/core/loop.py +710 -0
- kalfa/core/make.py +121 -0
- kalfa/core/plan.py +156 -0
- kalfa/core/predict.py +290 -0
- kalfa/core/protocols.py +122 -0
- kalfa/core/record.py +347 -0
- kalfa/core/refs.py +88 -0
- kalfa/core/rng.py +100 -0
- kalfa/core/rules.py +513 -0
- kalfa/core/run.py +568 -0
- kalfa/core/spec.py +413 -0
- kalfa/core/state.py +100 -0
- kalfa/core/wrap.py +671 -0
- kalfa/records/__init__.py +24 -0
- kalfa/records/compare.py +86 -0
- kalfa/records/reader.py +561 -0
- kalfa/records/stats/__init__.py +36 -0
- kalfa/records/stats/choices.py +282 -0
- kalfa/records/stats/classes.py +236 -0
- kalfa/records/stats/measures.py +144 -0
- kalfa/records/stats/pairs.py +225 -0
- kalfa/records/stats/tables.py +153 -0
- kalfa/records/sweep.py +244 -0
- kalfa/std/__init__.py +17 -0
- kalfa/std/actions.py +35 -0
- kalfa/std/common.py +56 -0
- kalfa/std/data/__init__.py +1 -0
- kalfa/std/data/base.py +45 -0
- kalfa/std/data/columns.py +190 -0
- kalfa/std/data/flow.py +317 -0
- kalfa/std/data/images.py +443 -0
- kalfa/std/data/options.py +147 -0
- kalfa/std/data/pre.py +84 -0
- kalfa/std/data/splits.py +140 -0
- kalfa/std/data/stream.py +294 -0
- kalfa/std/data/table.py +642 -0
- kalfa/std/data/text.py +436 -0
- kalfa/std/data/transforms.py +327 -0
- kalfa/std/data/weights.py +113 -0
- kalfa/std/data/window.py +135 -0
- kalfa/std/figure.py +37 -0
- kalfa/std/init.py +72 -0
- kalfa/std/loggers.py +105 -0
- kalfa/std/losses.py +161 -0
- kalfa/std/metrics.py +463 -0
- kalfa/std/models.py +122 -0
- kalfa/std/notes.py +1553 -0
- kalfa/std/objectives.py +360 -0
- kalfa/std/plots/__init__.py +75 -0
- kalfa/std/plots/classification.py +207 -0
- kalfa/std/plots/common.py +44 -0
- kalfa/std/plots/curves.py +56 -0
- kalfa/std/plots/images.py +188 -0
- kalfa/std/plots/pairs.py +66 -0
- kalfa/std/plots/predictions.py +154 -0
- kalfa/std/samplers.py +261 -0
- kalfa/std/samples.py +72 -0
- kalfa/std/schedules.py +74 -0
- kalfa/std/steps.py +133 -0
- kalfa/std/triggers.py +135 -0
- kalfa/summary.py +296 -0
- kalfa/tui/__init__.py +4 -0
- kalfa/tui/app.py +107 -0
- kalfa/tui/config.py +61 -0
- kalfa/tui/guide.py +100 -0
- kalfa/tui/jobs.py +97 -0
- kalfa/tui/legos.py +62 -0
- kalfa/tui/records.py +439 -0
- kalfa-3.0.0.dev0.dist-info/METADATA +261 -0
- kalfa-3.0.0.dev0.dist-info/RECORD +148 -0
- kalfa-3.0.0.dev0.dist-info/WHEEL +4 -0
- kalfa-3.0.0.dev0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { computed, createApp, onMounted, ref, watch }
|
|
2
|
+
from "https://cdn.jsdelivr.net/npm/vue@3.4.38/dist/vue.esm-browser.prod.js";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
EVERY,
|
|
6
|
+
TABS,
|
|
7
|
+
applyTheme,
|
|
8
|
+
compareLink,
|
|
9
|
+
cycleTheme,
|
|
10
|
+
isActive,
|
|
11
|
+
latestSweep,
|
|
12
|
+
refresh,
|
|
13
|
+
setEvery,
|
|
14
|
+
startLive,
|
|
15
|
+
store,
|
|
16
|
+
sweepLink,
|
|
17
|
+
watchScheme,
|
|
18
|
+
} from "./store.js";
|
|
19
|
+
import { Compare } from "./views/compare.js";
|
|
20
|
+
import { Home } from "./views/home.js";
|
|
21
|
+
import { Record } from "./views/record.js";
|
|
22
|
+
import { Runs } from "./views/runs.js";
|
|
23
|
+
import { Sweep } from "./views/sweep.js";
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
const PAGES = ["home", "runs", "compare", "sweep", "record"];
|
|
27
|
+
|
|
28
|
+
function decodePart(part) {
|
|
29
|
+
try {
|
|
30
|
+
return decodeURIComponent(part);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return part;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseHash(hash) {
|
|
37
|
+
const raw = String(hash || "").replace(/^#/, "").replace(/^\//, "");
|
|
38
|
+
const mark = raw.indexOf("?");
|
|
39
|
+
const route = mark >= 0 ? raw.slice(0, mark) : raw;
|
|
40
|
+
const query = Object.fromEntries(new URLSearchParams(mark >= 0 ? raw.slice(mark + 1) : ""));
|
|
41
|
+
const parts = route.split("/").filter(Boolean).map(decodePart);
|
|
42
|
+
const page = parts.length ? parts[0] : "home";
|
|
43
|
+
const rest = parts.slice(1);
|
|
44
|
+
if (page === "record") {
|
|
45
|
+
const named = rest.length > 1 && TABS.includes(rest[rest.length - 1]);
|
|
46
|
+
const tab = named ? rest[rest.length - 1] : "overview";
|
|
47
|
+
const path = (named ? rest.slice(0, -1) : rest).join("/");
|
|
48
|
+
return { page, path, tab, query, raw };
|
|
49
|
+
}
|
|
50
|
+
if (page === "sweep") return { page, path: rest.join("/"), tab: "", query, raw };
|
|
51
|
+
return { page: PAGES.includes(page) ? page : "missing", path: "", tab: "", query, raw };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const Board = {
|
|
55
|
+
name: "KalfaBoard",
|
|
56
|
+
components: { HomeView: Home, RunsView: Runs, CompareView: Compare, SweepView: Sweep, RecordView: Record },
|
|
57
|
+
setup() {
|
|
58
|
+
const route = ref(parseHash(window.location.hash));
|
|
59
|
+
const onHash = () => {
|
|
60
|
+
store.fault = "";
|
|
61
|
+
route.value = parseHash(window.location.hash);
|
|
62
|
+
};
|
|
63
|
+
const running = computed(() => store.records.filter(isActive).length);
|
|
64
|
+
const sweepHref = computed(() => {
|
|
65
|
+
const sweep = latestSweep();
|
|
66
|
+
return sweep ? sweepLink(sweep.path) : "";
|
|
67
|
+
});
|
|
68
|
+
const compareHref = computed(() => compareLink(store.selected));
|
|
69
|
+
const liveTitle = computed(() => (store.connected ? "live: the server reports changes every " + store.every + "s"
|
|
70
|
+
: "not connected to the live stream, retrying"));
|
|
71
|
+
const changeEvery = (event) => setEvery(event.target.value);
|
|
72
|
+
|
|
73
|
+
watch(running, (count) => {
|
|
74
|
+
document.title = count ? "kalfa board (" + count + " running)" : "kalfa board";
|
|
75
|
+
}, { immediate: true });
|
|
76
|
+
onMounted(() => {
|
|
77
|
+
window.addEventListener("hashchange", onHash);
|
|
78
|
+
watchScheme();
|
|
79
|
+
refresh();
|
|
80
|
+
startLive();
|
|
81
|
+
});
|
|
82
|
+
return { store, route, running, sweepHref, compareHref, liveTitle, changeEvery, cycleTheme, EVERY };
|
|
83
|
+
},
|
|
84
|
+
template: `
|
|
85
|
+
<header class="topbar">
|
|
86
|
+
<a class="brand" href="#/">kalfa</a>
|
|
87
|
+
<nav class="nav">
|
|
88
|
+
<a :class="{ active: route.page === 'home' }" href="#/">home</a>
|
|
89
|
+
<a :class="{ active: route.page === 'runs' }" href="#/runs">runs</a>
|
|
90
|
+
<a :class="{ active: route.page === 'compare' }" :href="compareHref">compare<span v-if="store.selected.length" class="count">{{ store.selected.length }}</span></a>
|
|
91
|
+
<a v-if="sweepHref" :class="{ active: route.page === 'sweep' }" :href="sweepHref">sweep</a>
|
|
92
|
+
<span v-else class="nav-off" title="no sweep under this root">sweep</span>
|
|
93
|
+
</nav>
|
|
94
|
+
<span class="root mono" :title="store.root">{{ store.root }}</span>
|
|
95
|
+
<span class="spacer"></span>
|
|
96
|
+
<span class="live" :class="{ on: store.connected }" :title="liveTitle">
|
|
97
|
+
<span class="dot"></span>{{ store.connected ? "live" : "offline" }}<span v-if="running" class="count">{{ running }}</span>
|
|
98
|
+
</span>
|
|
99
|
+
<select class="every" :value="store.every" @change="changeEvery" title="how often the server looks for changes">
|
|
100
|
+
<option v-for="value in EVERY" :key="value" :value="value">{{ value }}s</option>
|
|
101
|
+
</select>
|
|
102
|
+
<button type="button" class="tool" @click="cycleTheme" title="theme: auto follows the system">{{ store.theme }}</button>
|
|
103
|
+
</header>
|
|
104
|
+
<div v-if="store.fault" class="error-box page-fault">{{ store.fault }}</div>
|
|
105
|
+
<main class="main">
|
|
106
|
+
<home-view v-if="route.page === 'home'"></home-view>
|
|
107
|
+
<runs-view v-else-if="route.page === 'runs'"></runs-view>
|
|
108
|
+
<compare-view v-else-if="route.page === 'compare'" :key="'compare:' + route.raw" :query="route.query"></compare-view>
|
|
109
|
+
<sweep-view v-else-if="route.page === 'sweep'" :key="'sweep:' + route.path" :path="route.path"></sweep-view>
|
|
110
|
+
<record-view v-else-if="route.page === 'record'" :key="'record:' + route.path" :path="route.path" :tab="route.tab"></record-view>
|
|
111
|
+
<div v-else class="empty">there is no page at #/{{ route.raw }}; go <a class="link" href="#/">home</a></div>
|
|
112
|
+
</main>`,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const app = createApp(Board);
|
|
116
|
+
app.config.errorHandler = (error) => {
|
|
117
|
+
console.error(error);
|
|
118
|
+
store.fault = "the page hit an error: " + (error && error.message ? error.message : String(error));
|
|
119
|
+
};
|
|
120
|
+
applyTheme();
|
|
121
|
+
app.mount("#app");
|
|
122
|
+
window.kalfaMounted = true;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f4f4f2;
|
|
4
|
+
--surface: #fcfcfb;
|
|
5
|
+
--surface-2: #efeeeb;
|
|
6
|
+
--ink: #0b0b0b;
|
|
7
|
+
--ink-2: #52514e;
|
|
8
|
+
--muted: #74736e;
|
|
9
|
+
--line: #e2e1dc;
|
|
10
|
+
--line-2: #cbc9c2;
|
|
11
|
+
--accent: #2a78d6;
|
|
12
|
+
--accent-soft: #e3edfa;
|
|
13
|
+
--running: #2a78d6;
|
|
14
|
+
--stale: #b97a00;
|
|
15
|
+
--done: #1b8a5a;
|
|
16
|
+
--stopped: #74736e;
|
|
17
|
+
--failed: #d03b3a;
|
|
18
|
+
--series-1: #2a78d6;
|
|
19
|
+
--series-2: #eb6834;
|
|
20
|
+
--series-3: #1baf7a;
|
|
21
|
+
--series-4: #eda100;
|
|
22
|
+
--series-5: #e87ba4;
|
|
23
|
+
--series-6: #008300;
|
|
24
|
+
--series-7: #6250d6;
|
|
25
|
+
--series-8: #e34948;
|
|
26
|
+
--font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
27
|
+
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
28
|
+
--radius: 8px;
|
|
29
|
+
--shadow: 0 1px 2px rgba(20, 20, 18, 0.06);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@media (prefers-color-scheme: dark) {
|
|
33
|
+
:root:not([data-theme="light"]) {
|
|
34
|
+
color-scheme: dark;
|
|
35
|
+
--bg: #121211;
|
|
36
|
+
--surface: #1a1a19;
|
|
37
|
+
--surface-2: #232321;
|
|
38
|
+
--ink: #f0efec;
|
|
39
|
+
--ink-2: #c3c2b7;
|
|
40
|
+
--muted: #8f8e86;
|
|
41
|
+
--line: #2c2c2a;
|
|
42
|
+
--line-2: #3d3d3a;
|
|
43
|
+
--accent: #5b9cec;
|
|
44
|
+
--accent-soft: #1d2d44;
|
|
45
|
+
--running: #5b9cec;
|
|
46
|
+
--stale: #d9a227;
|
|
47
|
+
--done: #3dbb83;
|
|
48
|
+
--stopped: #8f8e86;
|
|
49
|
+
--failed: #ec6b6a;
|
|
50
|
+
--series-1: #3987e5;
|
|
51
|
+
--series-2: #d95926;
|
|
52
|
+
--series-3: #199e70;
|
|
53
|
+
--series-4: #c98500;
|
|
54
|
+
--series-5: #d55181;
|
|
55
|
+
--series-6: #008300;
|
|
56
|
+
--series-7: #9085e9;
|
|
57
|
+
--series-8: #e66767;
|
|
58
|
+
--shadow: none;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:root[data-theme="dark"] {
|
|
63
|
+
color-scheme: dark;
|
|
64
|
+
--bg: #121211;
|
|
65
|
+
--surface: #1a1a19;
|
|
66
|
+
--surface-2: #232321;
|
|
67
|
+
--ink: #f0efec;
|
|
68
|
+
--ink-2: #c3c2b7;
|
|
69
|
+
--muted: #8f8e86;
|
|
70
|
+
--line: #2c2c2a;
|
|
71
|
+
--line-2: #3d3d3a;
|
|
72
|
+
--accent: #5b9cec;
|
|
73
|
+
--accent-soft: #1d2d44;
|
|
74
|
+
--running: #5b9cec;
|
|
75
|
+
--stale: #d9a227;
|
|
76
|
+
--done: #3dbb83;
|
|
77
|
+
--stopped: #8f8e86;
|
|
78
|
+
--failed: #ec6b6a;
|
|
79
|
+
--series-1: #3987e5;
|
|
80
|
+
--series-2: #d95926;
|
|
81
|
+
--series-3: #199e70;
|
|
82
|
+
--series-4: #c98500;
|
|
83
|
+
--series-5: #d55181;
|
|
84
|
+
--series-6: #008300;
|
|
85
|
+
--series-7: #9085e9;
|
|
86
|
+
--series-8: #e66767;
|
|
87
|
+
--shadow: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
* { box-sizing: border-box; }
|
|
91
|
+
html, body { margin: 0; padding: 0; }
|
|
92
|
+
body { font: 13px/1.5 var(--font); color: var(--ink); background: var(--bg); min-height: 100vh; }
|
|
93
|
+
h1, h2, h3 { margin: 0; font-weight: 600; }
|
|
94
|
+
h2 { font-size: 17px; }
|
|
95
|
+
h3 { font-size: 14px; }
|
|
96
|
+
a { color: inherit; }
|
|
97
|
+
button, input, select { font: inherit; color: inherit; }
|
|
98
|
+
pre { margin: 0; }
|
|
99
|
+
.mono { font-family: var(--mono); font-size: 12px; }
|
|
100
|
+
.muted { color: var(--muted); }
|
|
101
|
+
.small { font-size: 12px; }
|
|
102
|
+
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
103
|
+
.spacer { flex: 1; }
|
|
104
|
+
.link { color: var(--accent); text-decoration: none; }
|
|
105
|
+
.link:hover { text-decoration: underline; }
|
|
106
|
+
.empty { color: var(--muted); margin: 8px 0; }
|
|
107
|
+
.loading { color: var(--muted); margin: 16px 0; }
|
|
108
|
+
.loading::before { content: ""; display: inline-block; width: 10px; height: 10px; margin-right: 8px; border: 2px solid var(--line-2); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: -1px; }
|
|
109
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
110
|
+
.fail-text { color: var(--failed); }
|
|
111
|
+
|
|
112
|
+
.topbar { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 14px; padding: 8px 16px; background: var(--surface); border-bottom: 1px solid var(--line); flex-wrap: wrap; }
|
|
113
|
+
.brand { font-weight: 700; font-size: 15px; text-decoration: none; letter-spacing: 0.02em; }
|
|
114
|
+
.nav { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
115
|
+
.nav a, .nav-off { padding: 4px 10px; border-radius: 6px; text-decoration: none; color: var(--ink-2); }
|
|
116
|
+
.nav a:hover { background: var(--surface-2); color: var(--ink); }
|
|
117
|
+
.nav a.active { background: var(--accent-soft); color: var(--accent); }
|
|
118
|
+
.nav-off { color: var(--muted); opacity: 0.6; cursor: default; }
|
|
119
|
+
.count { display: inline-block; margin-left: 6px; padding: 0 6px; min-width: 18px; border-radius: 9px; background: var(--accent); color: #fff; font-size: 11px; text-align: center; line-height: 18px; }
|
|
120
|
+
.root { color: var(--muted); max-width: 40vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; direction: rtl; text-align: left; }
|
|
121
|
+
.live { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
|
|
122
|
+
.live .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line-2); }
|
|
123
|
+
.live.on { color: var(--ink-2); }
|
|
124
|
+
.live.on .dot { background: var(--done); box-shadow: 0 0 0 3px color-mix(in srgb, var(--done) 25%, transparent); }
|
|
125
|
+
.every { padding: 3px 6px; border: 1px solid var(--line-2); border-radius: 6px; background: var(--surface); }
|
|
126
|
+
|
|
127
|
+
.main { max-width: 1440px; margin: 0 auto; padding: 16px; }
|
|
128
|
+
.page { display: flex; flex-direction: column; gap: 12px; }
|
|
129
|
+
.page-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
130
|
+
.section-title { margin-top: 8px; }
|
|
131
|
+
.page-fault { margin: 12px 16px 0; }
|
|
132
|
+
|
|
133
|
+
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px; min-width: 0; }
|
|
134
|
+
.card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; min-width: 0; }
|
|
135
|
+
.card-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
|
|
136
|
+
.card-tools { display: flex; gap: 4px; flex: none; }
|
|
137
|
+
.card-note { margin-bottom: 4px; }
|
|
138
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); gap: 12px; }
|
|
139
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(420px, 100%), 1fr)); gap: 12px; }
|
|
140
|
+
.stat-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
141
|
+
.stat-value { font-size: 18px; font-weight: 600; margin: 2px 0; overflow-wrap: anywhere; }
|
|
142
|
+
.chip { display: inline-block; margin: 2px 4px 2px 0; padding: 1px 7px; border-radius: 10px; background: var(--surface-2); border: 1px solid var(--line); font-size: 12px; }
|
|
143
|
+
.notice { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 8px 12px; border-radius: var(--radius); background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); }
|
|
144
|
+
.error-box { padding: 8px 12px; border-radius: var(--radius); color: var(--failed); background: color-mix(in srgb, var(--failed) 10%, transparent); border: 1px solid color-mix(in srgb, var(--failed) 35%, transparent); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
145
|
+
.notice-box { padding: 8px 12px; border-radius: var(--radius); color: var(--ink); background: color-mix(in srgb, var(--stopped) 12%, transparent); border: 1px solid color-mix(in srgb, var(--stopped) 40%, transparent); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
146
|
+
|
|
147
|
+
.button, .tool { display: inline-flex; align-items: center; gap: 4px; border: 1px solid var(--line-2); background: var(--surface); border-radius: 6px; cursor: pointer; text-decoration: none; white-space: nowrap; }
|
|
148
|
+
.button { padding: 5px 12px; }
|
|
149
|
+
.tool { padding: 2px 8px; font-size: 12px; color: var(--ink-2); }
|
|
150
|
+
.button:hover, .tool:hover { border-color: var(--accent); color: var(--accent); }
|
|
151
|
+
.tool.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
|
|
152
|
+
.button.ghost { border-color: transparent; background: transparent; }
|
|
153
|
+
.button.danger { border-color: color-mix(in srgb, var(--failed) 50%, transparent); color: var(--failed); }
|
|
154
|
+
.button.danger:hover { background: color-mix(in srgb, var(--failed) 10%, transparent); color: var(--failed); border-color: var(--failed); }
|
|
155
|
+
.button.disabled, .button:disabled { opacity: 0.5; pointer-events: none; }
|
|
156
|
+
.filter { padding: 5px 9px; border: 1px solid var(--line-2); border-radius: 6px; background: var(--surface); min-width: 0; width: min(360px, 100%); }
|
|
157
|
+
.filter:focus, .every:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
158
|
+
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
159
|
+
.toolbar .filter { flex: 1; width: auto; max-width: 640px; }
|
|
160
|
+
|
|
161
|
+
.badge { display: inline-block; padding: 0 8px; border-radius: 10px; font-size: 11px; line-height: 18px; font-weight: 600; white-space: nowrap; --tone: var(--stopped); color: var(--tone); background: color-mix(in srgb, var(--tone) 14%, transparent); border: 1px solid color-mix(in srgb, var(--tone) 35%, transparent); }
|
|
162
|
+
.badge.is-running { --tone: var(--running); }
|
|
163
|
+
.badge.is-stale { --tone: var(--stale); }
|
|
164
|
+
.badge.is-done { --tone: var(--done); }
|
|
165
|
+
.badge.is-stopped { --tone: var(--stopped); }
|
|
166
|
+
.badge.is-failed { --tone: var(--failed); }
|
|
167
|
+
.badge.is-partial { --tone: var(--stale); }
|
|
168
|
+
.badge.is-unknown { --tone: var(--muted); }
|
|
169
|
+
|
|
170
|
+
.progress { display: inline-flex; align-items: center; gap: 8px; min-width: 120px; }
|
|
171
|
+
.progress-track { flex: 1; min-width: 60px; height: 6px; border-radius: 3px; background: var(--surface-2); border: 1px solid var(--line); overflow: hidden; }
|
|
172
|
+
.progress-fill { display: block; height: 100%; background: var(--accent); border-radius: 3px; transition: width 0.3s ease; }
|
|
173
|
+
.progress.unknown .progress-track { opacity: 0.5; }
|
|
174
|
+
.progress-text { color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 12px; }
|
|
175
|
+
|
|
176
|
+
.spark { display: inline-block; vertical-align: middle; color: var(--accent); }
|
|
177
|
+
|
|
178
|
+
.table-box { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
|
|
179
|
+
.table-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
180
|
+
.table-wrap { overflow-x: auto; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
|
181
|
+
table.data { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
|
|
182
|
+
table.data th, table.data td { padding: 6px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: middle; white-space: nowrap; }
|
|
183
|
+
table.data th { position: sticky; top: 0; background: var(--surface-2); color: var(--ink-2); font-weight: 600; font-size: 12px; user-select: none; }
|
|
184
|
+
table.data th.sortable { cursor: pointer; }
|
|
185
|
+
table.data th.sortable:hover { color: var(--ink); }
|
|
186
|
+
table.data th.num, table.data td.num { text-align: right; }
|
|
187
|
+
table.data tbody tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
|
|
188
|
+
table.data tbody tr.selected { background: var(--accent-soft); }
|
|
189
|
+
table.data tbody tr:last-child td { border-bottom: 0; }
|
|
190
|
+
table.data .check { width: 28px; padding-right: 0; }
|
|
191
|
+
table.data .arrow { color: var(--accent); font-size: 9px; }
|
|
192
|
+
table.data td.empty { text-align: center; padding: 16px; }
|
|
193
|
+
|
|
194
|
+
.live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 12px; }
|
|
195
|
+
.live-card { display: flex; flex-direction: column; gap: 6px; text-decoration: none; }
|
|
196
|
+
.live-card:hover { border-color: var(--accent); }
|
|
197
|
+
.live-head { display: flex; align-items: center; gap: 8px; }
|
|
198
|
+
.live-name { font-weight: 600; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
199
|
+
|
|
200
|
+
.plot { width: 100%; min-height: 120px; }
|
|
201
|
+
.modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(10, 10, 9, 0.55); }
|
|
202
|
+
.modal-box { display: flex; flex-direction: column; width: min(1280px, 100%); height: min(860px, 100%); padding: 12px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
|
|
203
|
+
.plot.large { flex: 1; min-height: 0; }
|
|
204
|
+
|
|
205
|
+
.diff { font-family: var(--mono); font-size: 12px; padding: 8px; overflow-x: auto; background: var(--surface-2); border-radius: 6px; }
|
|
206
|
+
.diff span { display: block; white-space: pre; min-height: 1.4em; }
|
|
207
|
+
.diff .head { color: var(--muted); }
|
|
208
|
+
.diff .hunk { color: var(--accent); }
|
|
209
|
+
.diff .add { color: var(--done); background: color-mix(in srgb, var(--done) 10%, transparent); }
|
|
210
|
+
.diff .del { color: var(--failed); background: color-mix(in srgb, var(--failed) 10%, transparent); }
|
|
211
|
+
.shared { line-height: 2; }
|
|
212
|
+
|
|
213
|
+
.record-head { display: flex; flex-direction: column; gap: 8px; }
|
|
214
|
+
.record-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
215
|
+
.record-title h2 { overflow-wrap: anywhere; }
|
|
216
|
+
.record-meta { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
|
|
217
|
+
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); overflow-x: auto; }
|
|
218
|
+
.tab { padding: 6px 12px; text-decoration: none; color: var(--ink-2); border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap; }
|
|
219
|
+
.tab:hover { color: var(--ink); }
|
|
220
|
+
.tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
|
221
|
+
.tab-body { min-width: 0; }
|
|
222
|
+
|
|
223
|
+
@media (max-width: 720px) {
|
|
224
|
+
.main { padding: 12px 16px; }
|
|
225
|
+
.root { display: none; }
|
|
226
|
+
.grid { grid-template-columns: 1fr; }
|
|
227
|
+
.record-meta { gap: 8px 12px; }
|
|
228
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.chart-card .tool.tuned::after { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
|
|
2
|
+
|
|
3
|
+
.chart-modal { padding: 16px; }
|
|
4
|
+
.chart-box { width: min(1480px, 100%); height: min(940px, 100%); gap: 6px; box-shadow: var(--shadow); font-family: var(--font); color: var(--ink); }
|
|
5
|
+
.chart-box > .card-head { margin-bottom: 0; }
|
|
6
|
+
.chart-body { flex: 1; min-height: 0; display: flex; gap: 14px; }
|
|
7
|
+
.chart-stage { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; align-items: flex-start; overflow: auto; }
|
|
8
|
+
.chart-stage .plot.large { min-height: 200px; }
|
|
9
|
+
|
|
10
|
+
.chart-panel { flex: none; width: 300px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; padding: 0 4px 8px 14px; border-left: 1px solid var(--line); font-size: 12px; }
|
|
11
|
+
.chart-group { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; border: 0; }
|
|
12
|
+
.chart-group-title { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
13
|
+
details.chart-group > summary { cursor: pointer; user-select: none; }
|
|
14
|
+
details.chart-group > summary:hover { color: var(--ink); }
|
|
15
|
+
.chart-row { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
16
|
+
.chart-label { flex: none; width: 74px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
17
|
+
.chart-unit { flex: none; color: var(--muted); }
|
|
18
|
+
.chart-hint { color: var(--muted); font-size: 11px; line-height: 1.4; }
|
|
19
|
+
|
|
20
|
+
.chart-panel input[type="number"], .chart-panel input[type="text"], .chart-panel select, .chart-panel textarea { min-width: 0; padding: 3px 6px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); font-family: var(--font); font-size: 12px; }
|
|
21
|
+
.chart-row input[type="number"], .chart-row input[type="text"], .chart-row select { flex: 1 1 0; width: 0; }
|
|
22
|
+
.chart-panel input:focus, .chart-panel select:focus, .chart-panel textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
23
|
+
.chart-panel select:disabled { opacity: 0.5; }
|
|
24
|
+
.chart-panel input[type="range"] { flex: 1; min-width: 0; margin: 0; accent-color: var(--accent); }
|
|
25
|
+
.chart-panel input[type="checkbox"] { margin: 0; accent-color: var(--accent); }
|
|
26
|
+
.chart-panel textarea { width: 100%; min-height: 104px; resize: vertical; padding: 6px 8px; font-family: var(--mono); line-height: 1.45; }
|
|
27
|
+
.chart-check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: var(--ink); }
|
|
28
|
+
|
|
29
|
+
.chart-auto { flex: none; min-width: 42px; padding: 1px 4px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--muted); font-family: var(--mono); font-size: 11px; text-align: right; cursor: pointer; }
|
|
30
|
+
.chart-auto:not(:disabled):hover { color: var(--accent); border-color: var(--line); }
|
|
31
|
+
.chart-auto:disabled { cursor: default; }
|
|
32
|
+
|
|
33
|
+
.chart-swatches { display: flex; flex-wrap: wrap; gap: 3px; padding-left: 80px; }
|
|
34
|
+
.chart-swatch { width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--line); }
|
|
35
|
+
|
|
36
|
+
.chart-actions { flex-wrap: wrap; }
|
|
37
|
+
.chart-reset:hover { border-color: var(--failed); color: var(--failed); }
|
|
38
|
+
.chart-error { color: var(--failed); font-size: 11px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
39
|
+
|
|
40
|
+
@media (max-width: 900px) {
|
|
41
|
+
.chart-modal { padding: 8px; }
|
|
42
|
+
.chart-box { height: 100%; overflow-y: auto; }
|
|
43
|
+
.chart-body { flex: none; flex-direction: column; }
|
|
44
|
+
.chart-stage { flex: none; height: 62vh; }
|
|
45
|
+
.chart-panel { width: auto; overflow: visible; padding: 12px 0 0; border-left: 0; border-top: 1px solid var(--line); }
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const KNOWN = ["running", "stale", "done", "stopped", "failed", "partial", "unknown"];
|
|
2
|
+
|
|
3
|
+
export const Badge = {
|
|
4
|
+
name: "KBadge",
|
|
5
|
+
props: { status: { type: String, default: "unknown" } },
|
|
6
|
+
setup(props) {
|
|
7
|
+
const label = () => props.status || "unknown";
|
|
8
|
+
const tone = () => (KNOWN.includes(label()) ? label() : "unknown");
|
|
9
|
+
return { label, tone };
|
|
10
|
+
},
|
|
11
|
+
template: '<span class="badge" :class="\'is-\' + tone()">{{ label() }}</span>',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default Badge;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch }
|
|
2
|
+
from "https://cdn.jsdelivr.net/npm/vue@3.4.38/dist/vue.esm-browser.prod.js";
|
|
3
|
+
|
|
4
|
+
import { draw, exportImage, numberOf, purge } from "../plot.js";
|
|
5
|
+
import { store } from "../store.js";
|
|
6
|
+
import { ChartSettings, chartSettings, drawOptions, forgetSettings, isTuned, payloadOf, saveSettings }
|
|
7
|
+
from "./settings.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function labelsOf(kind, options) {
|
|
11
|
+
const fallback = { histogram: ["", "count"], heatmap: ["predicted", "truth"] }[kind] || ["", ""];
|
|
12
|
+
return { xlabel: options.xlabel || fallback[0], ylabel: options.ylabel || fallback[1] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Chart = {
|
|
16
|
+
name: "KChart",
|
|
17
|
+
components: { KChartSettings: ChartSettings },
|
|
18
|
+
props: {
|
|
19
|
+
title: { type: String, default: "" },
|
|
20
|
+
kind: { type: String, default: "lines" },
|
|
21
|
+
data: { default: null },
|
|
22
|
+
options: { type: Object, default: () => ({}) },
|
|
23
|
+
height: { type: Number, default: 260 },
|
|
24
|
+
logToggle: { type: Boolean, default: true },
|
|
25
|
+
note: { type: String, default: "" },
|
|
26
|
+
settingsKey: { type: String, default: "" },
|
|
27
|
+
binsBox: { type: Boolean, default: false },
|
|
28
|
+
},
|
|
29
|
+
emits: ["settings"],
|
|
30
|
+
setup(props, { emit }) {
|
|
31
|
+
const host = ref(null);
|
|
32
|
+
const large = ref(null);
|
|
33
|
+
const open = ref(false);
|
|
34
|
+
const panel = ref(true);
|
|
35
|
+
const failure = ref("");
|
|
36
|
+
const measured = ref({ width: 0, height: 0 });
|
|
37
|
+
let pressed = false;
|
|
38
|
+
let announced = "";
|
|
39
|
+
|
|
40
|
+
const base = () => props.options || {};
|
|
41
|
+
const key = computed(() => props.settingsKey || props.kind + ":" + props.title);
|
|
42
|
+
const settings = computed(() => chartSettings(key.value));
|
|
43
|
+
const tuned = computed(() => isTuned(settings.value));
|
|
44
|
+
const logy = computed(() => {
|
|
45
|
+
const scale = settings.value.yscale;
|
|
46
|
+
return scale ? scale === "log" : Boolean(base().logy);
|
|
47
|
+
});
|
|
48
|
+
const auto = computed(() => ({
|
|
49
|
+
logx: Boolean(base().logx),
|
|
50
|
+
logy: Boolean(base().logy),
|
|
51
|
+
horizontal: base().horizontal !== false,
|
|
52
|
+
...labelsOf(props.kind, base()),
|
|
53
|
+
}));
|
|
54
|
+
const box = computed(() => ({
|
|
55
|
+
width: numberOf(settings.value.boxWidth),
|
|
56
|
+
height: numberOf(settings.value.boxHeight),
|
|
57
|
+
}));
|
|
58
|
+
const largeStyle = computed(() => {
|
|
59
|
+
const style = {};
|
|
60
|
+
if (box.value.width) style.width = Math.max(200, box.value.width) + "px";
|
|
61
|
+
if (box.value.height) {
|
|
62
|
+
style.height = Math.max(200, box.value.height) + "px";
|
|
63
|
+
style.flex = "none";
|
|
64
|
+
}
|
|
65
|
+
return style;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const optionsFor = (height) => ({ ...base(), ...drawOptions(settings.value), revision: key.value, height });
|
|
69
|
+
const paint = (el, height) => {
|
|
70
|
+
if (!el || props.data === null || props.data === undefined) return;
|
|
71
|
+
try {
|
|
72
|
+
draw(props.kind, el, props.data, optionsFor(height));
|
|
73
|
+
failure.value = "";
|
|
74
|
+
} catch (error) {
|
|
75
|
+
failure.value = "cannot draw this chart: " + error.message;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const bigHeight = () => {
|
|
79
|
+
if (box.value.height) return Math.max(200, box.value.height);
|
|
80
|
+
return Math.max(240, (large.value && large.value.clientHeight) || window.innerHeight * 0.6);
|
|
81
|
+
};
|
|
82
|
+
const paintLarge = () => {
|
|
83
|
+
const el = large.value;
|
|
84
|
+
if (!open.value || !el) return;
|
|
85
|
+
paint(el, bigHeight());
|
|
86
|
+
const size = { width: el.clientWidth, height: el.clientHeight };
|
|
87
|
+
if (size.width !== measured.value.width || size.height !== measured.value.height) measured.value = size;
|
|
88
|
+
};
|
|
89
|
+
const redraw = () => {
|
|
90
|
+
paint(host.value, props.height);
|
|
91
|
+
if (open.value) nextTick(paintLarge);
|
|
92
|
+
};
|
|
93
|
+
const announce = (force) => {
|
|
94
|
+
const payload = payloadOf(key.value, settings.value, base());
|
|
95
|
+
const text = JSON.stringify(payload);
|
|
96
|
+
if (text === announced) return;
|
|
97
|
+
announced = text;
|
|
98
|
+
if (force) emit("settings", payload);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onKey = (event) => {
|
|
102
|
+
if (event.key === "Escape") close();
|
|
103
|
+
};
|
|
104
|
+
const close = () => {
|
|
105
|
+
purge(large.value);
|
|
106
|
+
open.value = false;
|
|
107
|
+
window.removeEventListener("keydown", onKey);
|
|
108
|
+
};
|
|
109
|
+
const expand = () => {
|
|
110
|
+
open.value = true;
|
|
111
|
+
window.addEventListener("keydown", onKey);
|
|
112
|
+
nextTick(paintLarge);
|
|
113
|
+
};
|
|
114
|
+
const press = (event) => {
|
|
115
|
+
pressed = event.target === event.currentTarget;
|
|
116
|
+
};
|
|
117
|
+
const release = (event) => {
|
|
118
|
+
if (pressed && event.target === event.currentTarget) close();
|
|
119
|
+
pressed = false;
|
|
120
|
+
};
|
|
121
|
+
const togglePanel = () => {
|
|
122
|
+
panel.value = !panel.value;
|
|
123
|
+
};
|
|
124
|
+
const toggleLog = () => {
|
|
125
|
+
const next = !logy.value;
|
|
126
|
+
settings.value.yscale = next === Boolean(base().logy) ? "" : next ? "log" : "linear";
|
|
127
|
+
};
|
|
128
|
+
const reset = () => forgetSettings(key.value);
|
|
129
|
+
const download = (format) => {
|
|
130
|
+
const current = settings.value;
|
|
131
|
+
exportImage(large.value, {
|
|
132
|
+
format,
|
|
133
|
+
width: numberOf(current.imageWidth) || measured.value.width,
|
|
134
|
+
height: numberOf(current.imageHeight) || measured.value.height,
|
|
135
|
+
scale: numberOf(current.imageScale),
|
|
136
|
+
filename: props.title || key.value,
|
|
137
|
+
}).catch((error) => {
|
|
138
|
+
failure.value = "cannot download this chart: " + error.message;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
watch([key, () => JSON.stringify(settings.value)], (now, before) => {
|
|
143
|
+
if (now[0] === before[0]) saveSettings(key.value, settings.value);
|
|
144
|
+
redraw();
|
|
145
|
+
announce(true);
|
|
146
|
+
});
|
|
147
|
+
watch(() => props.data, redraw);
|
|
148
|
+
watch(() => props.kind, redraw);
|
|
149
|
+
watch(() => JSON.stringify(props.options || {}), redraw);
|
|
150
|
+
watch(() => store.paint, redraw);
|
|
151
|
+
onMounted(() => {
|
|
152
|
+
redraw();
|
|
153
|
+
announce(tuned.value);
|
|
154
|
+
});
|
|
155
|
+
onBeforeUnmount(() => {
|
|
156
|
+
window.removeEventListener("keydown", onKey);
|
|
157
|
+
purge(host.value);
|
|
158
|
+
purge(large.value);
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
host, large, open, panel, failure, measured, settings, tuned, logy, auto, largeStyle, expand, close, press,
|
|
162
|
+
release, togglePanel, toggleLog, reset, download,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
template: `
|
|
166
|
+
<div class="card chart-card">
|
|
167
|
+
<div class="card-head">
|
|
168
|
+
<span class="card-title" :title="title">{{ title }}</span>
|
|
169
|
+
<span class="card-tools">
|
|
170
|
+
<button v-if="logToggle" type="button" class="tool" :class="{ on: logy }" @click="toggleLog">log y</button>
|
|
171
|
+
<button type="button" class="tool" :class="{ tuned: tuned }" @click="expand"
|
|
172
|
+
:title="tuned ? 'open this chart large; it has saved settings' : 'open this chart large with its settings'">expand</button>
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div v-if="note" class="card-note muted small">{{ note }}</div>
|
|
176
|
+
<div v-if="failure" class="error-box">{{ failure }}</div>
|
|
177
|
+
<div ref="host" class="plot" :style="{ height: height + 'px' }"></div>
|
|
178
|
+
<teleport to="body">
|
|
179
|
+
<div v-if="open" class="modal chart-modal" @mousedown="press" @click="release">
|
|
180
|
+
<div class="modal-box chart-box" role="dialog" aria-modal="true" :aria-label="title || 'chart'">
|
|
181
|
+
<div class="card-head">
|
|
182
|
+
<span class="card-title" :title="title">{{ title }}</span>
|
|
183
|
+
<span class="card-tools">
|
|
184
|
+
<button v-if="logToggle" type="button" class="tool" :class="{ on: logy }" @click="toggleLog">log y</button>
|
|
185
|
+
<button type="button" class="tool" :class="{ on: panel }" @click="togglePanel">settings</button>
|
|
186
|
+
<button type="button" class="tool" @click="close">close</button>
|
|
187
|
+
</span>
|
|
188
|
+
</div>
|
|
189
|
+
<div v-if="note" class="card-note muted small">{{ note }}</div>
|
|
190
|
+
<div v-if="failure" class="error-box">{{ failure }}</div>
|
|
191
|
+
<div class="chart-body">
|
|
192
|
+
<div class="chart-stage">
|
|
193
|
+
<div ref="large" class="plot large" :style="largeStyle"></div>
|
|
194
|
+
</div>
|
|
195
|
+
<k-chart-settings v-if="panel" :settings="settings" :kind="kind" :auto="auto" :size="measured"
|
|
196
|
+
:bins-box="binsBox" @reset="reset" @download="download"></k-chart-settings>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</teleport>
|
|
201
|
+
</div>`,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export default Chart;
|