From e5b983979162cfadb628a835f666278700414e64 Mon Sep 17 00:00:00 2001 From: Paul Oliver Date: Thu, 16 Apr 2026 18:04:07 +0200 Subject: Improves web-ui input form usability --- data/vue/App.vue | 193 ++++++++++++++++++++++++++++++------------------------ data/vue/Plot.vue | 18 ++--- 2 files changed, 113 insertions(+), 98 deletions(-) (limited to 'data/vue') diff --git a/data/vue/App.vue b/data/vue/App.vue index cb04565..6c91964 100644 --- a/data/vue/App.vue +++ b/data/vue/App.vue @@ -8,16 +8,18 @@ {{ query_in_progress ? '⧖' : '✓' }} -
- Entries: - nth: - X-axis: - X-low: - X-high: - Left: - Px-count: - Px-pow: - + + Rows: + nth: + Axis: + Low: + High: + Left: + Pixels: + Px-pow: + + +
@@ -68,7 +70,7 @@ const opt_fmts = [ let visible_plot_tables = [] let visible_heatmap_tables = [] let query_timeout = null -let plot_x_low = 0 +let plot_low = 0 let plot_redraw = false let mvec_size = 0 @@ -76,23 +78,22 @@ const int_max = Number.MAX_SAFE_INTEGER const uint32_max = (2 ** 32) - 1 const hm_max_pixels = 2 ** 11 -const entries_def = 2000 -const nth_def = 1 -const x_axis_def = 'rowid' -const x_low_def = hex(0) -const x_high_def = hex(int_max) -const hm_left_def = hex(0) -const hm_px_count_def = hex(2 ** 10) - -const entries = ref(entries_def) -const nth = ref(nth_def) -const x_axes = ref(['rowid', 'step']) -const x_axis = ref(x_axis_def) -const x_low = ref(x_low_def) -const x_high = ref(x_high_def) -const hm_left = ref(hm_left_def) -const hm_px_count = ref(hm_px_count_def) -const hm_px_pow = ref(hex(0)) +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({}) @@ -116,68 +117,76 @@ const update_visible_tables = () => { 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 = (input, min, max, def, fmt) => { - if (isNaN(Number(input.value)) || input.value === '' || input.value < min || input.value >= max) { - input.value = fmt(def) +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_hm_px_pow = () => Math.floor(Math.log2((mvec_size - Number(hm_left.value)) / Number(hm_px_count.value))) - -const trigger_reload = () => { - update_visible_tables() +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]) - sanitize(entries, 1, uint32_max, entries_def, id) - sanitize(nth, 1, uint32_max, nth_def, id) - sanitize(x_low, 0, int_max, 0, hex) - sanitize(x_high, 1, int_max, int_max, hex) +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(hm_left, 0, uint32_max, 0, hex) - sanitize(hm_px_count, 1, hm_max_pixels, 2 ** 10, hex) - sanitize(hm_px_pow, 0, uint32_max, uint32_max, hex) + sanitize('left', 0, uint32_max, hex) + sanitize('pixels', 1, hm_max_pixels, hex) + sanitize('pixel_pow', 0, uint32_max, hex) } else { - sanitize(hm_left, 0, mvec_size, 0, hex) - sanitize(hm_px_count, 1, hm_max_pixels, 2 ** 10, hex) - sanitize(hm_px_pow, 0, max_hm_px_pow(), max_hm_px_pow(), hex) + sanitize('left', 0, mvec_size, hex) + sanitize('pixels', 1, hm_max_pixels, hex) + sanitize('pixel_pow', 0, max_pixel_pow(), hex, max_pixel_pow()) } - plot_x_low = Number(x_low.value) + check_form_touched() +} + +const trigger_reload = () => { + update_visible_tables() + + params.value = { ...inputs.value } + plot_low = Number(params.value.low) plot_redraw = true + check_form_touched() + check_form_non_default() query() } const reset_inputs = () => { - entries.value = entries_def - nth.value = nth_def - x_axis.value = x_axis_def - x_low.value = x_low_def - x_high.value = x_high_def - hm_left.value = hm_left_def - hm_px_count.value = hm_px_count_def - hm_px_pow.value = max_hm_px_pow() - - trigger_reload() + inputs.value = { ...params.value } + on_form_change() +} + +const restore_inputs = () => { + inputs.value = { ...defaults } + on_form_change() } -const query_table = async (table, is_heatmap, x_high_now) => { - const params = { +const query_table = async (table, is_heatmap, high_now) => { + const url_params = { table: table, - entries: entries.value, - nth: nth.value, - x_axis: x_axis.value, - x_low: plot_x_low, - x_high: x_high_now, + rows: params.value.rows, + nth: params.value.nth, + axis: params.value.axis, + low: plot_low, + high: high_now, is_eva: is_heatmap, ...is_heatmap ? { - hm_left: Number(hm_left.value), - hm_px_count: Number(hm_px_count.value), - hm_px_pow: Number(hm_px_pow.value), + left: Number(params.value.left), + pixels: Number(params.value.pixels), + pixel_pow: Number(params.value.pixel_pow), } : {}, } - const search_params = new URLSearchParams(params) + 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() @@ -190,22 +199,22 @@ const query = async () => { clearTimeout(query_timeout) query_in_progress.value = true - const high_params = new URLSearchParams({ x_axis: x_axis.value }) - const resp_x_high = await fetch(root + `x_high?${high_params}`, { method: 'GET' }) - const text_x_high = await resp_x_high.text() - const json_x_high = JSON.parse(text_x_high) - const x_high_max = json_x_high.x_high + 1 - const x_high_val = Number(x_high.value) - const x_high_now = x_high_max < x_high_val ? x_high_max : x_high_val; + 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, x_high_now))) - const heatmap_query_results = await Promise.all(visible_heatmap_tables.map(table => query_table(table, true, x_high_now))) + 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_x_low = x_high_now + plot_low = high_now data.value = { redraw: plot_redraw, plot_values: plot_query_values, heatmap_values: heatmap_query_values } plot_redraw = false @@ -233,12 +242,14 @@ onMounted(async () => { loaded.value = true mvec_size = 2 ** opts.value.mvec_pow - hm_px_pow.value = hex(max_hm_px_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). - x_axes.value.push(...Array(opts.value.cores).keys().map(i => `cycl_${i}`)) + axes.value.push(...Array(opts.value.cores).keys().map(i => `cycl_${i}`)) }) watch(loaded, _ => { @@ -251,11 +262,7 @@ watch(loaded, _ => { provide('plots', plots) provide('heatmaps', heatmaps) -provide('entries', entries) -provide('x_axis', x_axis) -provide('hm_left', hm_left) -provide('hm_px_count', hm_px_count) -provide('hm_px_pow', hm_px_pow) +provide('params', params) provide('data', data) provide('trigger_reload', trigger_reload) @@ -329,14 +336,19 @@ td { width: 80px; } -.reset_button { +.input_touched { + background-color: #b58900; +} + +.form_button { background-color: black; - border: 1.5px solid gray; - color: gray; + 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; @@ -344,4 +356,11 @@ td { text-align: center; width: 16px; } + +.form_button_disabled { + border-color: gray; + color: gray; + cursor: default; + opacity: 0.5; +} diff --git a/data/vue/Plot.vue b/data/vue/Plot.vue index 5bbc89a..edc989d 100644 --- a/data/vue/Plot.vue +++ b/data/vue/Plot.vue @@ -20,11 +20,7 @@ const plot_container = useTemplateRef('plot_container') const plots = inject('plots') const heatmaps = inject('heatmaps') -const entries = inject('entries') -const x_axis = inject('x_axis') -const hm_left = inject('hm_left') -const hm_px_count = inject('hm_px_count') -const hm_px_pow = inject('hm_px_pow') +const params = inject('params') const data = inject('data') const plot_toggle_maximize = () => { @@ -78,7 +74,7 @@ const update_plot = new_data => { 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[x_axis.value])) + 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 @@ -91,19 +87,19 @@ const update_plot = new_data => { Plotly.restyle(plot_ref.value, restyle) } - Plotly.extendTraces(plot_ref.value, { x: xs, y: ys }, traces, entries.value) + 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[x_axis.value])] + 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(hm_px_pow.value)) + const px_size = Math.pow(2, Number(params.value.pixel_pow)) const restyle = { - x: [Array.from(Array(Number(hm_px_count.value)).keys()).map(i => Number(hm_left.value) + i * px_size)], + x: [Array.from(Array(Number(params.value.pixels)).keys()).map(i => Number(params.value.left) + i * px_size)], y: [[]], z: [[]], } @@ -111,7 +107,7 @@ const update_heatmap = new_data => { Plotly.restyle(plot_ref.value, restyle) } - Plotly.extendTraces(plot_ref.value, { y: ys, z: zs }, [0], entries.value) + Plotly.extendTraces(plot_ref.value, { y: ys, z: zs }, [0], params.value.rows) } watch(data, props.is_heatmap ? update_heatmap : update_plot) -- cgit v1.3