diff --git a/client/dom/index.ts b/client/dom/index.ts
index a3a496c769..2423a3f4ee 100644
--- a/client/dom/index.ts
+++ b/client/dom/index.ts
@@ -10,7 +10,7 @@ export * from './fillbar'
export * from './genesearch'
export * from './menu'
export * from './numericAxis'
-export * from './radiobutton'
+export * from './radiobutton.ts'
export * from './sandbox'
export * from './sayerror'
export * from './search'
diff --git a/client/dom/radio2.js b/client/dom/radio2.js
deleted file mode 100644
index 735572eaf6..0000000000
--- a/client/dom/radio2.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { select as d3select, selectAll as d3selectAll } from 'd3-selection'
-
-// TODO may replace with radiobutton.js
-export function initRadioInputs(opts) {
- const divs = opts.holder
- .selectAll('div')
- .data(opts.options, d => d.value)
- .style('display', 'block')
-
- divs.exit().each(function (d) {
- d3select(this).on('input', null).on('click', null).remove()
- })
-
- const labels = divs
- .enter()
- .append('div')
- .attr('aria-label', d => d.title)
- .style('display', 'block')
- .style('padding', opts.styles && 'padding' in opts.styles ? opts.styles.padding : '5px')
- .append('label')
-
- if (opts.styles) {
- for (const key in opts.styles) {
- labels.style(key, opts.styles[key])
- }
- }
-
- const inputs = labels
- .append('input')
- .attr('type', 'radio')
- .attr('name', opts.name)
- .attr('value', d => d.value)
- .style('vertical-align', 'top')
- .style('margin-top', '2px')
- .style('margin-right', 0)
- .property('checked', opts.isCheckedFxn)
- .on('mouseup', opts.listeners.input)
- .on('keyup', opts.listeners.input)
-
- labels
- .append('span')
- .style('vertical-align', 'top')
- .html(d => ' ' + d.label)
- .on('mouseup', opts.listeners.input)
- .on('keyup', opts.listeners.input)
-
- function isChecked(d) {
- return d.value == radio.currValue
- }
-
- const radio = {
- main(currValue) {
- radio.currValue = currValue
- inputs.property('checked', isChecked)
- },
- dom: {
- divs: opts.holder.selectAll('div'),
- labels: opts.holder.selectAll('label').select('span'),
- inputs: labels.selectAll('input')
- }
- }
-
- return radio
-}
diff --git a/client/dom/radiobutton.js b/client/dom/radiobutton.js
deleted file mode 100644
index da9f557790..0000000000
--- a/client/dom/radiobutton.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/* makes radio buttons
-
-******* Required
-.holder
-.options[ {} ]
- .label
- .value
- .checked
- only set for at most one option
-.callback
- async
-
-******* Optional
-.styles{}
- css to be applied to each
of the options
- e.g. { "padding":"5px", "display":"inline-block" }
-.inputName
- common Name of
, use random number if not given
-*/
-
-let nameSuffix = 0
-
-export function make_radios(arg) {
- const { holder, options, callback, styles } = arg
- const inputName = arg.inputName || 'pp-dom-radio-' + nameSuffix++
-
- const divs = holder
- .selectAll()
- .data(options, d => d.value)
- .enter()
- .append('div')
- .style('margin', '5px')
-
- if (styles) {
- for (const k in styles) {
- divs.style(k, styles[k])
- }
- }
-
- const labels = divs.append('label')
-
- const inputs = labels
- .append('input')
- .attr('type', 'radio')
- .attr('name', inputName)
- .attr('value', d => d.value)
- .on('input', async (event, d) => {
- inputs.property('disabled', true)
- await callback(d.value)
- inputs.property('disabled', false)
- })
- inputs.filter(d => d.checked).property('checked', true)
-
- labels.append('span').html(d => ' ' + d.label)
- return { divs, labels, inputs }
-}
diff --git a/client/dom/radiobutton.ts b/client/dom/radiobutton.ts
new file mode 100644
index 0000000000..f48c137c53
--- /dev/null
+++ b/client/dom/radiobutton.ts
@@ -0,0 +1,122 @@
+import { Elem, Input } from '../types/d3'
+
+/* makes radio buttons */
+
+type RadioButtonOpts = {
+ /** Fires .oninput. Intended for general use. */
+ callback?: (f?: any) => void
+ /** Required. */
+ holder: any
+ /** common Name of
, use random number if not given */
+ inputName?: string | number
+ /** Mass ui specific logic. Optional callback methods for
+ * non oninput events. Intended to address needs for assistive techology
+ */
+ listeners?: {
+ /** Fires on onmouseup and onkeyup for the button and text label
+ */
+ input: () => void
+ }
+ /** arr of objs defining the radio buttons and properties */
+ options: OptionEntry[]
+ /** css to be applied to each
of the options
+ * e.g. { "padding":"5px", "display":"inline-block" }
+ */
+ styles?: { [index: string]: string | number }
+}
+
+type OptionEntry = {
+ /** only set for only *one* option */
+ checked?: boolean | number
+ /** Text shown in the span to the right of the radio button */
+ label: string
+ /** Text shown in tooltip */
+ title?: string
+ /** Should correspond to 'currValue' in callbacks */
+ value: string | number
+}
+
+type RadioApi = {
+ /** Divs containing the labels with the padding and display styles applied. */
+ divs: Elem
+ /** Divs encapsulating the radio buttons and text. All styles provided in opts
+ * applied here. */
+ labels: Elem
+ /** Radio buttons, corresponding to the .options[] opt. */
+ inputs: Input
+ /** Trigger changing the checked button from the cooresponding value,
+ * independent of user and other callbacks. */
+ main: (value: number) => void
+}
+
+let nameSuffix = 0
+
+export function make_radios(opts: RadioButtonOpts): RadioApi {
+ if (!opts.callback && !opts.listeners) throw `Missing event callback for radios [#dom/radiobutton.js]`
+ if (opts.callback && opts.listeners)
+ throw `Both callback() and .listeners defined [#dom/radiobutton.js]. Only supply one.`
+ const inputName = opts.inputName || 'pp-dom-radio-' + nameSuffix++
+
+ const divs = opts.holder
+ .selectAll('div')
+ .style('display', 'block')
+ .data(opts.options, (d: any) => d?.value)
+
+ const labels = divs
+ .enter()
+ .append('div')
+ .attr('aria-label', (d: OptionEntry) => d.title)
+ .style('display', opts.styles && 'display' in opts.styles ? opts.styles.display : 'block')
+ .style('padding', opts.styles && 'padding' in opts.styles ? opts.styles.padding : '3px')
+ .append('label')
+
+ if (opts.styles) {
+ for (const k in opts.styles) {
+ labels.style(k, opts.styles[k])
+ }
+ }
+
+ const inputs = labels
+ .append('input')
+ .attr('type', 'radio')
+ .attr('name', inputName)
+ .attr('value', (d: OptionEntry) => d.value)
+ .style('vertical-align', 'top')
+ .style('margin-top', '2px')
+ .style('margin-right', 0)
+ .property('checked', (d: OptionEntry) => d?.checked)
+ if (opts.callback) {
+ inputs.on('input', async (event: KeyboardEvent, d: OptionEntry) => {
+ //Disable the radio buttons while the callback is running
+ inputs.property('disabled', true)
+ if (!opts.callback) return //So eslint doesn't complain
+ await opts.callback(d.value)
+ radio.main(d.value)
+ //Re-enable the radio buttons after the callback finishes
+ inputs.property('disabled', false)
+ })
+ }
+
+ const radioText = labels
+ .append('span')
+ .style('vertical-align', 'top')
+ .html((d: OptionEntry) => ' ' + d.label)
+
+ if (opts?.listeners?.input) {
+ //Mass UI specific logic for assistive technologies
+ inputs.on('mouseup', opts.listeners.input).on('keyup', opts.listeners.input)
+ radioText.on('mouseup', opts.listeners.input).on('keyup', opts.listeners.input)
+ }
+
+ const radio = {
+ divs,
+ labels,
+ inputs,
+ main(currValue: string | number) {
+ radio['currValue'] = currValue
+ inputs.property('checked', (d: OptionEntry) => d.value == radio['currValue'])
+ }
+ }
+
+ return radio
+}
diff --git a/client/dom/test/radiobutton.unit.spec.ts b/client/dom/test/radiobutton.unit.spec.ts
new file mode 100644
index 0000000000..1b1b9da717
--- /dev/null
+++ b/client/dom/test/radiobutton.unit.spec.ts
@@ -0,0 +1,175 @@
+import tape from 'tape'
+import * as d3s from 'd3-selection'
+import { make_radios } from '#dom'
+
+/* Tests
+ - default radio button rendering
+ - Missing callbacks
+ - Duplicate callbacks
+*/
+
+/**************
+ helper functions
+***************/
+
+function getHolder() {
+ return d3s
+ .select('body')
+ .append('div')
+ .style('border', '1px solid #aaa')
+ .style('padding', '5px')
+ .style('margin', '5px')
+}
+
+/**************
+ test sections
+***************/
+
+tape('\n', test => {
+ test.pass('-***- dom/radiobutton -***-')
+ test.end()
+})
+
+tape('default radio button rendering', test => {
+ test.timeoutAfter(100)
+ const holder = getHolder() as any
+
+ const styles = {
+ padding: '10px',
+ display: 'inline-block',
+ color: 'red'
+ }
+
+ const options = [
+ {
+ checked: true,
+ label: 'Test button',
+ title: 'test',
+ value: 1
+ },
+ {
+ checked: false,
+ label: 'Test button 2',
+ title: 'test',
+ value: 2
+ }
+ ]
+
+ const testArgs = {
+ holder,
+ styles,
+ options,
+ callback: () => {
+ //Comment so eslint doesn't complain
+ }
+ }
+
+ const { divs, labels, inputs, main } = make_radios(testArgs)
+
+ /** Divs */
+ const divData = divs
+ .enter()
+ .nodes()
+ .map((d: any) => d.__data__)
+ test.deepEqual(divData, options, 'Should create divs with the correct data')
+
+ /** Labels */
+ const renderedLabels = labels.nodes()
+ test.equal(renderedLabels.length, options.length, `Should create ${options.length} radio buttons`)
+ const labelStyles = renderedLabels[0].style
+ test.true(
+ labelStyles.display == styles.display && labelStyles.color == styles.color && labelStyles.padding == styles.padding,
+ 'Should render labels with opts.styles'
+ )
+
+ /** Inputs */
+ const renderedInputs = inputs.nodes()
+ test.equal(renderedInputs[0].checked, true, `Should check the first button`)
+
+ /** .main() */
+ main(2)
+ test.equal(renderedInputs[1].checked, true, `Should check the second button when main is called with it's value`)
+
+ if (test['_ok']) holder.remove()
+ test.end()
+})
+
+tape('Missing callbacks', test => {
+ test.timeoutAfter(100)
+ const holder = getHolder() as any
+
+ const options = [
+ {
+ checked: true,
+ label: 'Test button',
+ title: 'test',
+ value: 1
+ },
+ {
+ checked: false,
+ label: 'Test button 2',
+ title: 'test',
+ value: 2
+ }
+ ]
+
+ const testArgs = {
+ holder,
+ options
+ }
+
+ const message = 'Should throw when no callback(s) provided'
+ try {
+ make_radios(testArgs)
+ test.fail(message)
+ } catch (e: any) {
+ test.pass(`${message}: ${e.message || e}`)
+ }
+
+ if (test['_ok']) holder.remove()
+ test.end()
+})
+
+tape('Duplicate callbacks', test => {
+ test.timeoutAfter(100)
+ const holder = getHolder() as any
+
+ const options = [
+ {
+ checked: true,
+ label: 'Test button',
+ title: 'test',
+ value: 1
+ },
+ {
+ checked: false,
+ label: 'Test button 2',
+ title: 'test',
+ value: 2
+ }
+ ]
+
+ const testArgs = {
+ holder,
+ options,
+ callback: () => {
+ //Comment so eslint doesn't complain
+ },
+ listeners: {
+ input: () => {
+ //Comment so eslint doesn't complain
+ }
+ }
+ }
+
+ const message = 'Should throw when both callback() and listeners() provided'
+ try {
+ make_radios(testArgs)
+ test.fail(message)
+ } catch (e: any) {
+ test.pass(`${message}: ${e.message || e}`)
+ }
+
+ if (test['_ok']) holder.remove()
+ test.end()
+})
diff --git a/client/gdc/maf.js b/client/gdc/maf.js
index 5952c02208..23764a87a3 100644
--- a/client/gdc/maf.js
+++ b/client/gdc/maf.js
@@ -1,7 +1,5 @@
import { dofetch3 } from '#common/dofetch'
-import { sayerror } from '../dom/sayerror.ts'
-import { renderTable } from '../dom/table.ts'
-import { make_radios } from '#dom/radiobutton'
+import { make_radios, renderTable, sayerror } from '#dom'
import { fileSize } from '#shared/fileSize.js'
import { Menu } from '#dom/menu'
diff --git a/client/mds3/skewer.js b/client/mds3/skewer.js
index ae0e2415e8..c6bc77540d 100644
--- a/client/mds3/skewer.js
+++ b/client/mds3/skewer.js
@@ -95,7 +95,6 @@ export function may_render_skewer(data, tk, block) {
const currentMode = tk.skewer.viewModes.find(n => n.inuse)
if (!currentMode) throw 'no mode!!'
-
if (data.skewer) {
// register new mlst data
// otherwise will not overwrite skewer.mlst
diff --git a/client/plots/controls.config.js b/client/plots/controls.config.js
index cad5e0ff74..179e59cf1e 100644
--- a/client/plots/controls.config.js
+++ b/client/plots/controls.config.js
@@ -1,9 +1,9 @@
import { getCompInit, multiInit } from '../rx'
-import { initRadioInputs } from '../dom/radio2'
+import { make_radios } from '#dom'
import { termsettingInit } from '#termsetting'
import { rgb } from 'd3-color'
import { select } from 'd3-selection'
-import { TermTypes } from '#shared/terms.js'
+// import { TermTypes } from '#shared/terms.js'
// unique element ID's are needed for to be used for assigning unique
// radio button names by object instance
@@ -418,10 +418,10 @@ function setRadioInput(opts) {
}
]
- const styles = opts.styles || {}
+ const styles = opts.styles || { display: 'inline-block' }
for (const input of inputs) {
- self.inputs[input.settingsKey] = initRadioInputs({
- name: getElemId(opts.instanceNum),
+ self.inputs[input.settingsKey] = make_radios({
+ inputName: getElemId(opts.instanceNum),
holder: opts.holder
.append('td')
.attr('colspan', opts.colspan || '')
@@ -461,11 +461,11 @@ function setRadioInput(opts) {
for (const settingsKey in self.inputs) {
const radio = self.inputs[settingsKey]
radio.main(plot.settings[opts.chartType][settingsKey])
- radio.dom.divs.style('display', d =>
+ radio.divs.style('display', d =>
d.getDisplayStyle ? d.getDisplayStyle(plot) : opts.labelDisplay || 'inline-block'
)
- //radio.dom.labels.style('display', d => opts.labelDisplay || 'span')
- if (opts.setRadioLabel) radio.dom.labels.html(opts.setRadioLabel)
+ //radio.labels.style('display', d => opts.labelDisplay || 'span')
+ if (opts.setRadioLabel) radio.labels.html(opts.setRadioLabel)
}
}
}
diff --git a/client/plots/matrix/matrix.controls.js b/client/plots/matrix/matrix.controls.js
index 13b5576500..1b155225b8 100644
--- a/client/plots/matrix/matrix.controls.js
+++ b/client/plots/matrix/matrix.controls.js
@@ -398,7 +398,7 @@ export class MatrixControls {
{ label: 'By Input Data Order', value: 'asListed' },
{ label: `By ${l.sample} Count`, value: 'sampleCount' }
],
- styles: { padding: 0, 'padding-right': '10px', margin: 0 }
+ styles: { padding: 0, 'padding-right': '10px', margin: 0, display: 'inline-block' }
}
]
})
diff --git a/client/src/block.tk.aicheck.js b/client/src/block.tk.aicheck.js
index 8794a8749d..534ead4495 100644
--- a/client/src/block.tk.aicheck.js
+++ b/client/src/block.tk.aicheck.js
@@ -1,7 +1,7 @@
import { scaleLinear } from 'd3-scale'
import { axisLeft, axisRight } from 'd3-axis'
import * as client from './client'
-import { make_radios } from '../dom/radiobutton'
+import { make_radios } from '#dom'
/*
follows bigwig track, main & subpanel rendered separately
@@ -129,26 +129,19 @@ export function loadTk(tk, block) {
const imgh = tk.vafheight * 3 + tk.rowspace * 4 + tk.coverageheight * 2
tk.height_main = tk.toppad + imgh + tk.bottompad
- tk.img
- .attr('width', block.width)
- .attr('height', imgh)
- .attr('xlink:href', data.src)
+ tk.img.attr('width', block.width).attr('height', imgh).attr('xlink:href', data.src)
if (data.coveragemax) {
tk.coveragemax = data.coveragemax
}
if (!data.nodata) {
- const scale = scaleLinear()
- .domain([0, 1])
- .range([tk.vafheight, 0])
+ const scale = scaleLinear().domain([0, 1]).range([tk.vafheight, 0])
let y = 0
client.axisstyle({
axis: tk.Tvafaxis.attr('transform', 'translate(0,' + y + ')').call(
- axisLeft()
- .scale(scale)
- .tickValues([0, 1])
+ axisLeft().scale(scale).tickValues([0, 1])
),
color: 'black',
showline: true
@@ -158,9 +151,7 @@ export function loadTk(tk, block) {
y = tk.vafheight + tk.rowspace + tk.coverageheight + tk.rowspace
client.axisstyle({
axis: tk.Nvafaxis.attr('transform', 'translate(0,' + y + ')').call(
- axisLeft()
- .scale(scale)
- .tickValues([0, 1])
+ axisLeft().scale(scale).tickValues([0, 1])
),
color: 'black',
showline: true
@@ -169,26 +160,18 @@ export function loadTk(tk, block) {
y = 2 * (tk.vafheight + tk.rowspace + tk.coverageheight + tk.rowspace)
client.axisstyle({
- axis: tk.aiaxis.attr('transform', 'translate(0,' + y + ')').call(
- axisLeft()
- .scale(scale)
- .tickValues([0, 1])
- ),
+ axis: tk.aiaxis.attr('transform', 'translate(0,' + y + ')').call(axisLeft().scale(scale).tickValues([0, 1])),
color: 'black',
showline: true
})
tk.label_ai.attr('y', y + tk.vafheight / 2)
- const scale2 = scaleLinear()
- .domain([0, tk.coveragemax])
- .range([tk.coverageheight, 0])
+ const scale2 = scaleLinear().domain([0, tk.coveragemax]).range([tk.coverageheight, 0])
y = tk.vafheight + tk.rowspace
client.axisstyle({
axis: tk.Tcovaxis.attr('transform', 'translate(0,' + y + ')').call(
- axisRight()
- .scale(scale2)
- .tickValues([0, tk.coveragemax])
+ axisRight().scale(scale2).tickValues([0, tk.coveragemax])
),
color: 'black',
showline: true
@@ -198,9 +181,7 @@ export function loadTk(tk, block) {
y = 2 * (tk.vafheight + tk.rowspace) + tk.coverageheight + tk.rowspace
client.axisstyle({
axis: tk.Ncovaxis.attr('transform', 'translate(0,' + y + ')').call(
- axisRight()
- .scale(scale2)
- .tickValues([0, tk.coveragemax])
+ axisRight().scale(scale2).tickValues([0, tk.coveragemax])
),
color: 'black',
showline: true
@@ -331,11 +312,7 @@ function configPanel(tk, block) {
tk.gtotalcutoff = v
loadTk(tk, block)
})
- row
- .append('div')
- .style('font-size', '.7em')
- .style('opacity', 0.5)
- .text('Set to 0 to use all markers')
+ row.append('div').style('font-size', '.7em').style('opacity', 0.5).text('Set to 0 to use all markers')
}
// gmafrestrict
{
diff --git a/client/src/block.tk.bam.js b/client/src/block.tk.bam.js
index e03f41e8a4..a9506a552c 100644
--- a/client/src/block.tk.bam.js
+++ b/client/src/block.tk.bam.js
@@ -2,13 +2,8 @@ import { select as d3select } from 'd3-selection'
import { pointer } from 'd3-selection'
import { axisRight, axisTop } from 'd3-axis'
import { scaleLinear } from 'd3-scale'
-import { axisstyle } from '#dom/axisstyle'
-import { Menu } from '#dom/menu'
-import { sayerror } from '../dom/sayerror'
import { dofetch3 } from '#common/dofetch'
-import { make_radios } from '#dom/radiobutton'
-import { make_table_2col } from '#dom/table2col'
-import { make_one_checkbox } from '#dom/checkbox'
+import { Menu, axisstyle, sayerror, make_radios, make_table_2col, make_one_checkbox } from '#dom'
import urlmap from '#common/urlmap'
/*
diff --git a/client/termsetting/handlers/condition.ts b/client/termsetting/handlers/condition.ts
index 06f11c3ef7..5ce500f524 100755
--- a/client/termsetting/handlers/condition.ts
+++ b/client/termsetting/handlers/condition.ts
@@ -328,7 +328,7 @@ function showMenu_cutoff(self, div: any) {
make_radios({
holder: holder.append('div'),
options,
- styles: { margin: '' },
+ styles: { padding: '' },
callback: (v: 'age' | 'time') => (timeScaleChoice = v)
})
}