diff options
Diffstat (limited to 'data/vue')
| -rw-r--r-- | data/vue/App.vue | 378 | ||||
| -rw-r--r-- | data/vue/Plot.vue | 155 | ||||
| -rw-r--r-- | data/vue/Section.vue | 47 |
3 files changed, 0 insertions, 580 deletions
diff --git a/data/vue/App.vue b/data/vue/App.vue deleted file mode 100644 index 8bf2959..0000000 --- a/data/vue/App.vue +++ /dev/null @@ -1,378 +0,0 @@ -<template> - <div ref="top_pad"></div> - <div class="top_bar" ref="top_bar"> - <h1> - Salis data server » - <span class="opts_name"> - {{ opts.name }} - {{ query_in_progress ? '⧖' : '✓' }} - </span> - </h1> - <form @change="on_form_change"> - <span class="nobr">Rows: <input class="input_small" :class="{ input_touched: inputs.rows !== params.rows }" v-model="inputs.rows" /></span><wbr /> - <span class="nobr">nth: <input class="input_small" :class="{ input_touched: inputs.nth !== params.nth }" v-model="inputs.nth" /></span><wbr /> - <span class="nobr">Axis: <select class="input_small" :class="{ input_touched: inputs.axis !== params.axis }" v-model="inputs.axis"><option v-for="axis in axes">{{ axis }}</option></select></span><wbr /> - <span class="nobr">Low: <input :class="{ input_touched: inputs.low !== params.low }" v-model="inputs.low" /></span><wbr /> - <span class="nobr">High: <input :class="{ input_touched: inputs.high !== params.high }" v-model="inputs.high" /></span><wbr /> - <span class="nobr">Left: <input class="input_small" :class="{ input_touched: inputs.left !== params.left }" v-model="inputs.left" /></span><wbr /> - <span class="nobr">Pixels: <input class="input_small" :class="{ input_touched: inputs.pixels !== params.pixels }" v-model="inputs.pixels" /></span><wbr /> - <span class="nobr">Px-pow: <input class="input_small" :class="{ input_touched: inputs.pixel_pow !== params.pixel_pow }" v-model="inputs.pixel_pow" /></span><wbr /> - <span class="form_button" :class="{ form_button_disabled: !form_touched }" @click="reset_inputs">⨯</span><wbr /> - <span class="form_button" :class="{ form_button_disabled: !form_non_default }" @click="restore_inputs">←</span><wbr /> - <span class="form_button" @click="trigger_reload">↓</span> - </form> - </div> - <Section name="Options" visible> - <table> - <tr v-for="opt_fmt in opt_fmts"> - <td>{{ opt_fmt[0] }}:</td> - <td>{{ opt_fmt[2](opts[opt_fmt[1]]) }}</td> - </tr> - </table> - </Section> - <!-- Render plots after simulation options have been loaded --> - <div v-if="loaded"> - <Section :name="section" grid ref="plot_sections" :visible="section === 'General'" triggers_reload v-for="(section_plots, section) in plots"> - <Plot :name="name" :section="section" v-for="(_, name) in section_plots" /> - </Section> - <Section :name="section" grid ref="heatmap_sections" triggers_reload v-for="(section_heatmaps, section) in heatmaps"> - <Plot is_heatmap :name="name" :section="section" v-for="(_, name) in section_heatmaps" /> - </Section> - </div> -</template> - -<script setup> -import { onMounted, provide, useTemplateRef, ref, watch } from 'vue' - -import Plot from './Plot.vue' -import Section from './Section.vue' - -const root = window.location.href -const id = v => v -const hex = v => v !== undefined ? `0x${v.toString(16)}` : '' -const hex_pow = v => v !== undefined ? `0x${(2 ** v).toString(16)}` : '' -const disabled = v => v ? 'disabled' : 'enabled' - -const opt_fmts = [ - ['Ancestor', 'anc', id], - ['Architecture', 'arch', id], - ['Auto-save interval', 'auto_save_pow', hex_pow], - ['Clones', 'clones', id], - ['Cores', 'cores', id], - ['Data push interval', 'data_push_pow', hex_pow], - ['Mutator flip bit', 'muta_flip', id], - ['Mutator range', 'muta_pow', hex_pow], - ['Memory vector size', 'mvec_pow', hex_pow], - ['Save file compression', 'no_compress', disabled], - ['Seed', 'seed', hex], -] - -let visible_plot_tables = [] -let visible_heatmap_tables = [] -let query_timeout = null -let plot_low = 0 -let plot_redraw = false -let mvec_size = 0 -let reload_triggered = false - -const int_max = Number.MAX_SAFE_INTEGER -const uint32_max = (2 ** 32) - 1 -const hm_max_pixels = 2 ** 11 - -let defaults = { - rows: 2000, - nth: 1, - axis: 'rowid', - low: hex(0), - high: hex(int_max), - left: hex(0), - pixels: hex(2 ** 10), - pixel_pow: hex(0), -} - -const axes = ref(['rowid', 'step']) -const inputs = ref({ ...defaults }) -const params = ref({ ...defaults }) -const form_touched = ref(false) -const form_non_default = ref(false) - -const opts = ref({}) -const plots = ref({}) -const heatmaps = ref({}) -const loaded = ref(false) - -const query_in_progress = ref(false) -const data = ref([]) - -const top_pad = useTemplateRef('top_pad') -const top_bar = useTemplateRef('top_bar') -const plot_sections = useTemplateRef('plot_sections') -const heatmap_sections = useTemplateRef('heatmap_sections') - -const adjust_top_bar = () => top_pad.value.style.height = `${Math.round(top_bar.value.getBoundingClientRect().height)}px` - -const update_visible_tables = () => { - const plot_section_visibility = plot_sections.value.map(section => section.visible) - const heatmap_section_visibility = heatmap_sections.value.map(section => section.visible) - visible_plot_tables = Object.entries(plots.value).filter((_, i) => plot_section_visibility[i]).map((section, _) => [...new Set(Object.entries(section[1]).map(plot => plot[1].table))]).flat() - visible_heatmap_tables = Object.entries(heatmaps.value).filter((_, i) => heatmap_section_visibility[i]).map((section, _) => [...new Set(Object.entries(section[1]).map(plot => plot[1].table))]).flat() -} - -const sanitize = (field, min, max, fmt, override = null) => { - if (isNaN(Number(inputs.value[field])) || inputs.value[field] === '' || inputs.value[field] < min || inputs.value[field] >= max) { - inputs.value[field] = override !== null ? fmt(Number(override)) : defaults[field] - } else { - inputs.value[field] = fmt(Number(inputs.value[field])) - } -} - -const max_pixel_pow = () => Math.floor(Math.log2((mvec_size - Number(inputs.value.left)) / Number(inputs.value.pixels))) -const check_form_touched = () => form_touched.value = !Object.keys(inputs.value).every(key => inputs.value[key] === params.value[key]) -const check_form_non_default = () => form_non_default.value = !Object.keys(params.value).every(key => params.value[key] === defaults[key]) - -const on_form_change = () => { - sanitize('rows', 1, uint32_max, id) - sanitize('nth', 1, uint32_max, id) - sanitize('low', 0, int_max, hex) - sanitize('high', 1, int_max, hex) - - if (opts.value.mvec_loop) { - sanitize('left', 0, uint32_max, hex) - sanitize('pixels', 1, hm_max_pixels, hex) - sanitize('pixel_pow', 0, uint32_max, hex) - } else { - sanitize('left', 0, mvec_size, hex) - sanitize('pixels', 1, hm_max_pixels, hex) - sanitize('pixel_pow', 0, max_pixel_pow(), hex, max_pixel_pow()) - } - - check_form_touched() -} - -const trigger_reload = () => { - reload_triggered = true - query() -} - -const reset_inputs = () => { - inputs.value = { ...params.value } - on_form_change() -} - -const restore_inputs = () => { - inputs.value = { ...defaults } - on_form_change() -} - -const query_table = async (table, is_heatmap, high_now) => { - const url_params = { - table: table, - rows: params.value.rows, - nth: params.value.nth, - axis: params.value.axis, - low: plot_low, - high: high_now, - is_eva: is_heatmap, - ...is_heatmap ? { - left: Number(params.value.left), - pixels: Number(params.value.pixels), - pixel_pow: Number(params.value.pixel_pow), - } : {}, - } - - const search_params = new URLSearchParams(url_params) - const resp_table = await fetch(root + `data?${search_params}`, { method: 'GET' }) - const text_table = await resp_table.text() - - return JSON.parse(text_table) -} - -const query = async () => { - if (query_in_progress.value) return - - clearTimeout(query_timeout) - query_in_progress.value = true - - if (reload_triggered) { - update_visible_tables() - - params.value = { ...inputs.value } - plot_low = Number(params.value.low) - plot_redraw = true - - check_form_touched() - check_form_non_default() - - reload_triggered = false - } - - const high_params = new URLSearchParams({ axis: params.value.axis }) - const resp_high = await fetch(root + `high?${high_params}`, { method: 'GET' }) - const text_high = await resp_high.text() - const json_high = JSON.parse(text_high) - const high_max = json_high.high + 1 - const high_val = Number(params.value.high) - const high_now = high_max < high_val ? high_max : high_val; - - const plot_query_results = await Promise.all(visible_plot_tables.map(table => query_table(table, false, high_now))) - const heatmap_query_results = await Promise.all(visible_heatmap_tables.map(table => query_table(table, true, high_now))) - const plot_query_values = Object.fromEntries(visible_plot_tables.map((table, i) => [table, plot_query_results[i]])) - const heatmap_query_values = Object.fromEntries(visible_heatmap_tables.map((table, i) => [table, heatmap_query_results[i]])) - - // Keep track of the highest x-axis value fetched so far. - // Future queries will set this as the minimum, which prevents re-fetching already stored data. - plot_low = high_now - - data.value = { redraw: plot_redraw, plot_values: plot_query_values, heatmap_values: heatmap_query_values } - plot_redraw = false - - query_in_progress.value = false - - if (reload_triggered) { - query() - } else { - query_timeout = setTimeout(query, 10000) - } -} - -const with_big_ints = (_, val, { source }) => { - if (Number.isInteger(val) && !Number.isSafeInteger(val)) { - try { return BigInt(source) } catch {} - } - - return val -} - -onMounted(async () => { - const resp_opts = await fetch(root + 'opts', { method: 'GET' }) - const resp_plots = await fetch(root + 'plots', { method: 'GET' }) - const resp_heatmaps = await fetch(root + 'heatmaps', { method: 'GET' }) - - opts.value = JSON.parse(await resp_opts.text(), with_big_ints) - plots.value = JSON.parse(await resp_plots.text()) - heatmaps.value = JSON.parse(await resp_heatmaps.text()) - loaded.value = true - - mvec_size = 2 ** opts.value.mvec_pow - defaults.pixel_pow = hex(max_pixel_pow()) - inputs.value.pixel_pow = defaults.pixel_pow - params.value.pixel_pow = defaults.pixel_pow - - // All tables should include one cycle column for each core. - // This allows normalizing the plots against each core's cycle count - // (i.e. making `cycl_#` the plots' x-axis). - axes.value.push(...Array(opts.value.cores).keys().map(i => `cycl_${i}`)) -}) - -watch(loaded, _ => { - window.addEventListener('resize', adjust_top_bar) - - adjust_top_bar() - update_visible_tables() - query() -}, { flush: 'post' }) - -provide('plots', plots) -provide('heatmaps', heatmaps) -provide('params', params) -provide('data', data) -provide('trigger_reload', trigger_reload) -</script> - -<style> -html { - background-color: black; - color: gray; - font-family: sans-serif; -} - -h1 { - font-size: 18px; - font-weight: 600; - margin: 8px 0; -} - -input, select { - background-color: gray; - border: none; - color: black; - font-family: monospace; - font-size: 12px; - margin: 0 4px; - padding: 2px; - width: 120px; -} - -table { - border-collapse: collapse; - border-spacing: 0; - height: 100%; - width: 100%; -} - -tr:nth-child(odd) { - background-color: #111; -} - -td { - font-family: monospace; - font-size: 14px; - margin: 0; - padding: 0; -} - -.top_bar { - background-color: #000; - border-bottom: 1px solid gray; - left: 0; - padding: 8px; - position: fixed; - top: 0; - width: 100%; - z-index: 1; -} - -.opts_name { - color: #b58900; - font-weight: normal; -} - -.nobr { - font-size: 12px; - line-height: 28px; - margin-right: 6px; - white-space: nowrap; -} - -.input_small { - width: 80px; -} - -.input_touched { - background-color: #b58900; -} - -.form_button { - background-color: black; - border: 1.5px solid #b58900; - color: #b58900; - cursor: pointer; - display: inline-block; - font-family: monospace; - font-size: 14px; - font-weight: bold; - height: 16px; - line-height: 16px; - margin: 0 4px; - padding: 2px; - text-align: center; - width: 16px; -} - -.form_button_disabled { - border-color: gray; - color: gray; - cursor: default; - opacity: 0.5; -} -</style> diff --git a/data/vue/Plot.vue b/data/vue/Plot.vue deleted file mode 100644 index edc989d..0000000 --- a/data/vue/Plot.vue +++ /dev/null @@ -1,155 +0,0 @@ -<template> - <div class="plot_container" :class="{ plot_maximized: maximized, plot_minimized: !maximized }" ref="plot_container"> - <div class="plot" ref="plot_ref"> - <button class="plot_button" @click="plot_toggle_maximize"> - {{ maximized ? '-' : '+' }} - </button> - </div> - </div> -</template> - -<script setup> -import { defineProps, inject, onMounted, ref, useTemplateRef, watch } from 'vue' - -const props = defineProps({ is_heatmap: Boolean, section: String, name: String }) - -const maximized = ref(false) - -const plot_ref = useTemplateRef('plot_ref') -const plot_container = useTemplateRef('plot_container') - -const plots = inject('plots') -const heatmaps = inject('heatmaps') -const params = inject('params') -const data = inject('data') - -const plot_toggle_maximize = () => { - maximized.value = !maximized.value - Plotly.Plots.resize(plot_ref.value) - document.body.style.overflow = maximized.value ? 'hidden' : 'visible' -} - -const prevent_plotly_buttons_tab_focus = () => { - const focusableElements = plot_container.value.querySelectorAll('a, button, input, select') - focusableElements.forEach(elem => elem.setAttribute('tabindex', '-1')) -} - -const heatmap_init = [{ colorscale: 'Electric', type: 'heatmap', x: [], y: [], z: [] }] - -const plot_init = () => { - const plot_config = plots.value[props.section][props.name] - const plot_defs = { mode: 'lines', line: { width: 1 }, x: [], y: [] } - - switch (plot_config.type) { - case 'lines': - return Array.from(plot_config.cols, col => ({ ...plot_defs, name: col })) - case 'stack': - return Array.from(plot_config.cols, col => ({ ...plot_defs, stackgroup: 'sg', name: col })) - case 'stack_percent': - return Array.from(plot_config.cols, col => ({ ...plot_defs, stackgroup: 'sg', groupnorm: 'percent', name: col })) - } -} - -onMounted(() => { - Plotly.newPlot(plot_ref.value, props.is_heatmap ? heatmap_init : plot_init(), { - font: { color: 'gray', family: 'monospace' }, - legend: { maxheight: 100, orientation: 'h' }, - margin: { b: 48, l: 48, r: 48, t: 48 }, - paper_bgcolor: 'black', - plot_bgcolor: 'black', - title: { font: { size: 16 }, text: props.name, x: 0, xref: 'paper' }, - xaxis: { gridcolor: '#111', tickfont: { color: 'gray' }, zerolinecolor: 'gray' }, - yaxis: { gridcolor: '#111', tickfont: { color: 'gray' }, zerolinecolor: 'gray' }, - }, { - displayModeBar: true, - responsive: true, - }) - - prevent_plotly_buttons_tab_focus() -}) - -const update_plot = new_data => { - const plot_config = plots.value[props.section][props.name] - const cols = plot_config.cols - const cols_count = cols.length - const table_data = new_data.plot_values[plot_config.table] - const traces = [...Array(cols_count).keys()] - const xs = Array(cols_count).fill(table_data.map(elem => elem[params.value.axis])) - const ys = cols.map(column => table_data.map(elem => elem[column])) - - // Clear traces - if (new_data.redraw) { - const restyle = { - x: Array.from(cols, () => []), - y: Array.from(cols, () => []), - } - - Plotly.restyle(plot_ref.value, restyle) - } - - Plotly.extendTraces(plot_ref.value, { x: xs, y: ys }, traces, params.value.rows) -} - -const update_heatmap = new_data => { - const heatmap_config = heatmaps.value[props.section][props.name] - const table_data = new_data.heatmap_values[heatmap_config.table] - const ys = [table_data.map(elem => elem[params.value.axis])] - const zs = [table_data.map(elem => elem.eva_render.split(' ').map(str => Number('0x' + str)))] - - if (new_data.redraw) { - const px_size = Math.pow(2, Number(params.value.pixel_pow)) - const restyle = { - x: [Array.from(Array(Number(params.value.pixels)).keys()).map(i => Number(params.value.left) + i * px_size)], - y: [[]], - z: [[]], - } - - Plotly.restyle(plot_ref.value, restyle) - } - - Plotly.extendTraces(plot_ref.value, { y: ys, z: zs }, [0], params.value.rows) -} - -watch(data, props.is_heatmap ? update_heatmap : update_plot) -</script> - -<style> -.plot_container { - background-color: black; - display: inline-block; - width: 100%; -} - -.plot_maximized { - height: 100%; - left: 0; - position: fixed; - top: 0; - z-index: 999; -} - -.plot_minimized { - height: 400px; - position: relative; - z-index: 0; -} - -.plot_button { - background-color: black; - border: 1.5px solid gray; - color: gray; - cursor: pointer; - font-family: monospace; - font-size: 18px; - height: 26px; - padding: 0; - position: absolute; - right: 0; - top: 0; - width: 26px; -} - -.plot { - height: 100%; -} -</style> diff --git a/data/vue/Section.vue b/data/vue/Section.vue deleted file mode 100644 index 7f4d633..0000000 --- a/data/vue/Section.vue +++ /dev/null @@ -1,47 +0,0 @@ -<template> - <h2 class="section_header" @click="section_toggle_visible"> - {{ name }} <span class="section_button">{{ visible ? '-' : '+' }}</span> - </h2> - <div :class="{ section_grid: grid }" v-if="visible"> - <slot></slot> - </div> -</template> - -<script setup> -import { defineProps, inject, ref } from 'vue' - -const props = defineProps({ name: String, grid: Boolean, visible: Boolean, triggers_reload: Boolean }) -const visible = ref(props.visible) -const trigger_reload = inject('trigger_reload') - -const section_toggle_visible = () => { - visible.value = !visible.value - if (props.triggers_reload) trigger_reload() -} - -defineExpose({ visible }) -</script> - -<style> -.section_header { - border-bottom: 1px solid gray; - cursor: pointer; - font-size: 16px; - font-weight: normal; -} - -.section_button { - font-family: monospace; -} - -.section_grid { - display: grid; - grid-template-columns: 1fr 1fr; -} - -@media screen and (max-width: 800px) { - .section_grid { - grid-template-columns: 1fr; - } -} -</style> |
