ns = Inputs.text().classList[0]
// custom css to override some ojs defaults for inputs
html`<style>
.${ns} {
--label-width: 80px;
}
form.${ns} {
flex-wrap: wrap;
}
.plot-inputs form.${ns} {
flex-direction: column;
}
.${ns} div label {
background-color: #f4f4f4;
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
margin-right: 0.25rem;
margin-bottom: 0.25rem;
width: auto;
}
.${ns} div label:hover,
.${ns} div label:active,
.${ns} div label:focus {
background-color: #fbe4b4;
}
.${ns} input[type="checkbox"] {
accent-color: black;
margin-bottom: 0;
}
.${ns} div input[type="number"] {
background-color: #f4f4f4;
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
flex-shrink: 3;
border: none;
}
.${ns} select {
background-color: #f4f4f4;
border: none;
border-radius: 0.5rem;
padding: 0.25rem 0.5rem;
//width: auto;
}
.${ns} .hist {
width: 100%;
display: flex;
flex-direction: column;
row-gap: 0em;
}
</style>`tol = ({
QualBright: ['#4477AA', '#EE6677', '#228833', '#CCBB44', '#66CCEE','#AA3377'],
QualHighContrast: ['#004488', '#DDAA33', '#BB5566'],
QualVibrant: ['#EE7733', '#0077BB', '#33BBEE', '#EE3377', '#CC3311', '#009988'],
QualMuted: ['#CC6677', '#332288', '#DDCC77', '#117733', '#88CCEE','#882255', '#44AA99', '#999933', '#AA4499'],
QualLight: ['#77AADD', '#EE8866', '#EEDD88', '#FFAABB', '#99DDFF', '#44BB99', '#BBCC33', '#AAAA00'],
Sunset: ['#364B9A', '#4A7BB7', '#6EA6CD', '#98CAE1', '#C2E4EF', '#EAECCC', '#FEDA8B', '#FDB366', '#F67E4B', '#DD3D2D', '#A50026'],
BuRd: ['#2166AC', '#4393C3', '#92C5DE', '#D1E5F0', '#F7F7F7', '#FDDBC7', '#F4A582', '#D6604D', '#B2182B'],
PRGn: ['#762A83', '#9970AB', '#C2A5CF', '#E7D4E8', '#F7F7F7', '#D9F0D3', '#ACD39E', '#5AAE61', '#1B7837'],
YlOrBr: ['#FFFFE5', '#FFF7BC', '#FEE391', '#FEC44F', '#FB9A29',
'#EC7014', '#CC4C02', '#993404', '#662506'],
Iridescent: ['#FEFBE9', '#FCF7D5', '#F5F3C1', '#EAF0B5', '#DDECBF',
'#D0E7CA', '#C2E3D2', '#B5DDD8', '#A8D8DC', '#9BD2E1',
'#8DCBE4', '#81C4E7', '#7BBCE7', '#7EB2E4', '#88A5DD',
'#9398D2', '#9B8AC4', '#9D7DB2', '#9A709E', '#906388',
'#805770', '#684957', '#46353A'],
RainbowPuRd: ['#6F4C9B', '#6059A9', '#5568B8', '#4E79C5', '#4D8AC6',
'#4E96BC', '#549EB3', '#59A5A9', '#60AB9E', '#69B190',
'#77B77D', '#8CBC68', '#A6BE54', '#BEBC48', '#D1B541',
'#DDAA3C', '#E49C39', '#E78C35', '#E67932', '#E4632D',
'#DF4828', '#DA2222'],
RainbowPuBr: ['#6F4C9B', '#6059A9', '#5568B8', '#4E79C5', '#4D8AC6',
'#4E96BC', '#549EB3', '#59A5A9', '#60AB9E', '#69B190',
'#77B77D', '#8CBC68', '#A6BE54', '#BEBC48', '#D1B541',
'#DDAA3C', '#E49C39', '#E78C35', '#E67932', '#E4632D',
'#DF4828', '#DA2222', '#B8221E', '#95211B', '#721E17',
'#521A13'],
RainbowWhRd: ['#E8ECFB', '#DDD8EF', '#D1C1E1', '#C3A8D1', '#B58FC2',
'#A778B4', '#9B62A7', '#8C4E99', '#6F4C9B', '#6059A9',
'#5568B8', '#4E79C5', '#4D8AC6', '#4E96BC', '#549EB3',
'#59A5A9', '#60AB9E', '#69B190', '#77B77D', '#8CBC68',
'#A6BE54', '#BEBC48', '#D1B541', '#DDAA3C', '#E49C39',
'#E78C35', '#E67932', '#E4632D', '#DF4828', '#DA2222'],
RainbowDiscrete: ['#E8ECFB', '#D9CCE3', '#D1BBD7', '#CAACCB', '#BA8DB4',
'#AE76A3', '#AA6F9E', '#994F88', '#882E72', '#1965B0',
'#437DBF', '#5289C7', '#6195CF', '#7BAFDE', '#4EB265',
'#90C987', '#CAE0AB', '#F7F056', '#F7CB45', '#F6C141',
'#F4A736', '#F1932D', '#EE8026', '#E8601C', '#E65518',
'#DC050C', '#A5170E', '#72190E', '#42150A']
})Plot = import("https://esm.sh/@observablehq/plot@0.6.17")
import {yamultiselect} from '@saneef/yet-another-multi-select'
overview = transpose(overview_)
task_info = ({
"hf": "Hearts & Flowers",
"sds": "Same & Different",
"mg": "Memory",
"math": "Math",
"matrix": "Pattern Matching",
"mrot": "Shape Rotation",
"trog": "Sentence Understanding",
"vocab": "Vocabulary",
"tom": "Stories",
"pa": "Language Sounds",
"sre": "Sentence Reading",
"swr": "Word Reading",
})
mutable dn = [{ name: "levante-data-pilots", ref: "48qk" }] // default datasource
ov = overview.map(obj => ({...obj, task_label: task_info[obj.task_code]})).filter(d => ds.includes(d.ref))displayScheme = [
[204, 102, 119],
[221, 204, 119],
[153, 153, 51],
[ 68, 170, 153],
[136, 204, 238],
[170, 68, 153],
]
displayConfig = ([
{ index: 0, property: "site", label: "Sites", icon: "globe" },
{ index: 1, property: "dataset", label: "Datasets", icon: "archive" },
{ index: 2, property: "language", label: "Languages", icon: "translate" },
{ index: 3, property: "task_code", label: "Tasks", icon: "ui-checks-grid" },
{ index: 4, property: "user_id_hash", label: "Participants", icon: "person-arms-up" },
{ index: 5, property: "run_id_hash", label: "Assessments", icon: "clipboard-data" },
])
displayBox = ({ index, property, label, icon }) => {
const values = new Set(ov.map(d => d[property]))
const color = displayScheme[index]
const color_str = `${color[0]}, ${color[1]}, ${color[2]}`
const borderColor = `rgb(${color_str})`
const backgroundColor = `rgba(${color_str}, 0.4)`
return html`<div class="callout callout-style-default callout-note no-icon callout-empty-content callout-titled callout-display" style="border-left-color: ${borderColor};">
<div class="callout-header d-flex align-content-center" style="background-color: ${backgroundColor};">
<div class="callout-title-container flex-fill">
<span class="display-box">
<span class="display-icon"><i class="bi-${icon}" role="img" aria-hidden="true"></i></span>
<span class="display-content">
<span class="display-var">${label}</span>
<span class="display-val">${values.size}</span>
</span>
</span>
</div>
</div>
</div>`
}
boxes = displayConfig.map(displayBox)
html`<div class="display-box-container">${boxes}</div>`x_var_opts = new Map([["Assessments", "run_id_hash"], ["Participants", "user_id_hash"]])
viewof x_var = Inputs.select(x_var_opts, { label: "Count of" })
y_var_opts = new Map([["Site", "site"], ["Dataset", "dataset"], ["Language", "language"], ["Task", "task_label"]])
viewof y_var = Inputs.select(y_var_opts, { label: "For each", value: "task_label" })
color_opts = new Map([["", null], ["Site", "site"], ["Dataset", "dataset"], ["Language", "language"], ["Task", "task_label"]])
viewof color_var = Inputs.select(color_opts, { label: "Grouped by", value: "language" })
scheme = tol.QualMuted // color scheme
default_color = scheme[1] // default color when there's no color variable
plt_color = color_var || default_color // plot color variable
x_lab = x_var_opts.entries().filter(([k, v]) => v === x_var)
y_lab = y_var_opts.entries().filter(([k, v]) => v === y_var)
//groups = d3.groupSort(ov, g => -new Set(g.map(d => d[x_var])).size, d => d[color_var])
marginLeft = 160
Plot.plot({
style: { fontFamily: "var(--sans-serif)", fontSize: 12 },
width: 0.5 * width,
marginLeft: marginLeft,
marginTop: 10,
marginBottom: 40,
//x: { grid: true },
x: { label: `${x_lab[0]} count`, axis: "bottom", line: true },
y: { label: y_lab[0], labelAnchor: "top", tickSize: 0, tickPadding: 2 },
color: {
//domain: groups,
range: scheme,
legend: color_var !== null,
marginLeft: marginLeft,
className: "color-legend"
},
marks: [
Plot.barX(ov, Plot.groupY(
{ x: g => new Set(g.map(d => d[x_var])).size },
{ y: y_var, fill: plt_color, sort: { y: "x", reverse: true, }, inset: 1 }
)),
Plot.textX(ov, Plot.stackX(Plot.groupY(
{ x: g => new Set(g.map(d => d[x_var])).size, text: g => new Set(g.map(d => d[x_var])).size },
{ y: y_var, z: plt_color, fill: "white" }
))),
]
})
hist_var_opts = new Map([["Age (years)", "age"]])
viewof hist_var = Inputs.select(hist_var_opts, { label: "Count of" })
stack_var_opts = new Map([["", null], ["Site", "site"], ["Dataset", "dataset"], ["Language", "language"], ["Task", "task_label"]])
viewof stack_var = Inputs.select(stack_var_opts, { label: "Grouped by", value: "language" })
hist_x_lab = hist_var_opts.entries().filter(([k, v]) => v === hist_var)
Plot.plot({
style: { fontFamily: "var(--sans-serif)", fontSize: 12 },
//width: 1000,
width: 0.5 * width,
height: 300,
marginTop: 10,
marginBottom: 40,
x: {
//domain: [3,12],
label: hist_x_lab[0],
line: true
},
color: {
legend: true,
label: "bem",
//domain: groups,
range: scheme,
//legend: stack_var !== null,
//marginLeft: marginLeft,
className: "color-legend"
},
marks: [
Plot.rectY(ov, Plot.binX({y: "count"}, {
fill: stack_var || default_color,
x: {
//interval: hist_bin / 12,
value: hist_var
}
})),
]
})// populates datasource selector -- first click should open redivis auth pop up
viewof auth_button = Inputs.button("Populate data sources", {
reduce: async () => {
const ds = await getDatasets()
mutable dn = allowedDatasetNames(ds)
}
})
dnf = dn.filter(d => overview.map(v => v.ref).includes(d.ref))
viewof ds = yamultiselect(new Map(dnf.map(d => [d.name, d.ref])), { value: dnf.map(d => d.ref), placeholder: "Select...", width: "100%" })Change data source…
Note: To change data sources, you need a Redivis account. Click on the button below to log into your Redivis account and list the data sources that you have access to.
redivis = require("redivis")
// get references to all LEVANTE datasets (with "properties" populated)
async function getDatasets() {
const ds = await redivis
.organization('LEVANTE')
.listDatasets()
return await Promise.all(ds.map(d => d.get()))
}
// filter datasets by authed user's access, extract their reference IDs
allowedDatasetNames = (ds) => {
const accessLevels = ["data", "edit"]
return ds.filter(d => accessLevels.includes(d.properties.accessLevel))
.map(d => ({ name: d.properties.name, ref: d.properties.referenceId }))
.sort((a, b) => d3.ascending(a.name, b.name))
.filter(d => d.name !== "levante-data-pilots-raw") // TODO: fix name inconsistencies
}