• Overview
  • Scores
  • Items
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']
})
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>`
Plot = import("https://esm.sh/@observablehq/plot@0.6.17")

// defined in _load-data-items.qmd
items = transpose(items_)
models = transpose(models_)

// create combined item_group and language
items_coded = items.map(d => ({...d, lang_group: `${d.item_group} | ${d.language}` }))
// group items by task for task selector
tasks = d3.group(items_coded, d => d.task_label)
viewof task_items = Inputs.select(tasks, { label: "Task", value: tasks.get("Math") })
item_vars = ["item_group", "item_uid", "n_responses", "difficulty"]
item_channels = Object.fromEntries(item_vars.map(k => [k, k]))

task = task_items[0].task_code // current items' task code
model = models.filter(t => t.task_code === task)[0] // current items' model type
by_lang = model.language !== undefined // whether items' model is by language

// sort order of languages -- alpha
langs = d3.sort(new Set(task_items.map(d => d.language)))
// sort order of item groups -- median difficulty (in first language)
l0_items = task_items.filter(d => d.language === langs[0])
groups = d3.groupSort(l0_items, g => d3.median(g, d => d.difficulty), d => d.item_group)
// sort order of language + item group -- above combined
indeces = d3.groups(task_items, d => d.lang_group).map(([lang_group, vals]) => [lang_group, langs.indexOf(vals[0].language), groups.indexOf(vals[0].item_group)])
lang_groups = d3.sort(indeces, d => d[1], d => d[2]).map(d => d[0])
heights = ({
  "hf":     180,
  "sds":    240,
  "mg":     220,
  "math":   490,
  "matrix": 220,
  "mrot":   150,
  "trog":   690,
  "vocab":  340,
  "tom":    500,
})

// shared options for both plots
item_opts = ({
         style: { fontFamily: "var(--sans-serif)", fontSize: 12, overflow: "visible" },
         width: 700,
        height: heights[task],
    marginLeft: 120,
     marginTop: 50,
  marginBottom: 40,
          grid: true,
         facet: { label: null, grid: true },
             x: { line: true },
             r: { range: [1, 4] },
            fy: { domain: by_lang ? lang_groups : groups },
         color: { domain: groups.length > 1 ? groups : langs, legend: false, scheme: "viridis" },
})

// dot mark for each plot, depending on x variable
item_dots = (x_var) => {
  return Plot.marks(
    Plot.dot(task_items, Plot.dodgeY("middle", {
    x: x_var,
    fy: by_lang ? "lang_group" : "item_group",
    fill: groups.length > 1 ? "item_group" : "language",
    r: "n_responses",
    tip: { format: { fy: false }, anchor: "left" },
    channels: item_channels
    }))
  )
}
// info on items' model registry record
html`<div class="source"><a href="${model.file_link}" target="_blank">Source</a>: ${model.itemtype} IRT model (${model.invariance_label}) fit to ${model.n_runs} runs, uploaded on ${model.added_at_date} to model registry version ${model.registry_version}.</div>`
// item difficulties plot
Plot.plot({
  ...item_opts,
  marks: item_dots("difficulty")
})
// item discriminations plot -- hide if model isn't 2PL
model.itemtype !== "2PL" ? html`<div></div>` :
Plot.plot({
  ...item_opts,
  marks: item_dots("discrimination")
})