OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 135 LINES
 
[ HISTORY ]  [ UP ]
 

---
import "./SpecDistributionChart.scss";
import type { StatsSpecCountBucket } from "../../../lib/types";
import { DUNGEON_SLUG_TO_ID } from "../../../lib/dungeon-thresholds";
import HelpHint from "../../HelpHint/HelpHint.astro";

interface Props {
  buckets: Record<string, StatsSpecCountBucket>;
  // unique scope id so multiple charts on a page can each find their own data isl
and
  scope: string;
}

const { buckets, scope } = Astro.props;

// Dungeon dropdown options. "all" is the default (overall data); each dungeon
// id maps to a per-dungeon slice via bucket.by_dungeon[id].
const DUNGEON_OPTIONS: Array<{ key: string; label: string }> = [
  { key: "all", label: "All Dungeons" },
  { key: "2", label: "Temple of the Jade Serpent" },
  { key: "56", label: "Stormstout Brewery" },
  { key: "57", label: "Gate of the Setting Sun" },
  { key: "58", label: "Shado-Pan Monastery" },
  { key: "59", label: "Siege of Niuzao Temple" },
  { key: "60", label: "Mogu'shan Palace" },
  { key: "76", label: "Scholomance" },
  { key: "77", label: "Scarlet Halls" },
  { key: "78", label: "Scarlet Monastery" },
];

// build-time sanity check against the canonical slug?id map
for (const o of DUNGEON_OPTIONS) {
  if (o.key === "all") continue;
  if (!Object.values(DUNGEON_SLUG_TO_ID).includes(parseInt(o.key, 10))) {
    throw new Error(`Unknown dungeon id ${o.key} in SpecDistributionChart`);
  }
}

const BUCKET_TABS: Array<{ key: string; label: string }> = [
  { key: "all_runs", label: "All Runs" },
  { key: "gold_runs", label: "Gold" },
  { key: "platinum_runs", label: "Platinum" },
  { key: "title_runs", label: "Title" },
  { key: "top_50_runs", label: "Top 50" },
];

const ROLE_TABS: Array<{ key: string; label: string }> = [
  { key: "all", label: "All Roles" },
  { key: "tank", label: "Tank" },
  { key: "healer", label: "Healer" },
  { key: "dps", label: "DPS" },
];

// "Picks" counts every spec slot (includes spec stacking, can exceed run count).
// "Runs" counts distinct runs containing the spec (capped at the bucket's
// total_runs). Naming borrows the MOBA pick-rate convention so it's familiar.
const METRIC_OPTIONS: Array<{ key: string; label: string }> = [
  { key: "picks", label: "Picks" },
  { key: "runs", label: "Runs" },
];

// data-buckets carries all 5 buckets so client can switch without refetching
const dataJson = JSON.stringify(buckets);
---

<figure
  class="spec-distribution-chart"
  data-scope={scope}
  data-buckets={dataJson}
>
  <div class="spec-distribution-chart__controls">
    <nav
      class="spec-distribution-chart__tabs"
      role="tablist"
      aria-label="Run filter"
    >
      {
        BUCKET_TABS.map((b, i) => (
          <button
            type="button"
            class:list={["spec-distribution-chart__tab", { active: i === 0 }]}
            role="tab"
            aria-selected={i === 0 ? "true" : "false"}
            data-bucket-key={b.key}
          >
            {b.label}
          </button>
        ))
      }
    </nav>
    <div class="spec-distribution-chart__selects">
      <div class="filter-group">
        <label for={`spec-dist-metric-${scope}`}>
          Metric
          <HelpHint title="Picks vs Runs">
            <p>
              <strong>Picks</strong> counts every spec slot. A run with two of the
              same spec contributes <strong>2 picks</strong>.
            </p>
            <p>
              <strong>Runs</strong> counts distinct runs containing the spec. A ru
n
              with two of the same spec still counts as <strong>1 run</strong>
            </p>
          </HelpHint>
        </label>
        <select id={`spec-dist-metric-${scope}`} data-metric-select>
          {METRIC_OPTIONS.map((m) => <option value={m.key}>{m.label}</option>)}
        </select>
      </div>
      <div class="filter-group">
        <label for={`spec-dist-role-${scope}`}>Role</label>
        <select id={`spec-dist-role-${scope}`} data-role-select>
          {ROLE_TABS.map((r) => <option value={r.key}>{r.label}</option>)}
        </select>
      </div>
      <div class="filter-group">
        <label for={`spec-dist-dungeon-${scope}`}>Dungeon</label>
        <select id={`spec-dist-dungeon-${scope}`} data-dungeon-select>
          {DUNGEON_OPTIONS.map((d) => <option value={d.key}>{d.label}</option>)}
        </select>
      </div>
    </div>
  </div>
  <div class="spec-distribution-chart__canvas">
    {/* Filled by SpecDistributionChart.ts on mount; kept empty server-side. */}
  </div>
</figure>

<script>
  import { initSpecDistributionCharts } from "./SpecDistributionChart.ts";
  initSpecDistributionCharts();
  document.addEventListener("astro:page-load", () =>
    initSpecDistributionCharts(),
  );
</script>

---
import "./SpecDistributionChart.scss";
import type { StatsSpecCountBucket } from ".
./../../lib/types";
import { DUNGEON_SLUG_TO_ID } from "../../..
/lib/dungeon-thresholds";
import HelpHint from "../../HelpHint/HelpHin
t.astro";

interface Props {
  buckets: Record<string, StatsSpecCountBuck
et>;
  // unique scope id so multiple charts on a
 page can each find their own data island
  scope: string;
}

const { buckets, scope } = Astro.props;

// Dungeon dropdown options. "all" is the de
fault (overall data); each dungeon
// id maps to a per-dungeon slice via bucket
.by_dungeon[id].
const DUNGEON_OPTIONS: Array<{ key: string; 
label: string }> = [
  { key: "all", label: "All Dungeons" },
  { key: "2", label: "Temple of the Jade Ser
pent" },
  { key: "56", label: "Stormstout Brewery" }
,
  { key: "57", label: "Gate of the Setting S
un" },
  { key: "58", label: "Shado-Pan Monastery" 
},
  { key: "59", label: "Siege of Niuzao Templ
e" },
  { key: "60", label: "Mogu'shan Palace" },
  { key: "76", label: "Scholomance" },
  { key: "77", label: "Scarlet Halls" },
  { key: "78", label: "Scarlet Monastery" },
];

// build-time sanity check against the canon
ical slug?id map
for (const o of DUNGEON_OPTIONS) {
  if (o.key === "all") continue;
  if (!Object.values(DUNGEON_SLUG_TO_ID).inc
ludes(parseInt(o.key, 10))) {
    throw new Error(`Unknown dungeon id ${o.
key} in SpecDistributionChart`);
  }
}

const BUCKET_TABS: Array<{ key: string; labe
l: string }> = [
  { key: "all_runs", label: "All Runs" },
  { key: "gold_runs", label: "Gold" },
  { key: "platinum_runs", label: "Platinum" 
},
  { key: "title_runs", label: "Title" },
  { key: "top_50_runs", label: "Top 50" },
];

const ROLE_TABS: Array<{ key: string; label:
 string }> = [
  { key: "all", label: "All Roles" },
  { key: "tank", label: "Tank" },
  { key: "healer", label: "Healer" },
  { key: "dps", label: "DPS" },
];

// "Picks" counts every spec slot (includes 
spec stacking, can exceed run count).
// "Runs" counts distinct runs containing th
e spec (capped at the bucket's
// total_runs). Naming borrows the MOBA pick
-rate convention so it's familiar.
const METRIC_OPTIONS: Array<{ key: string; l
abel: string }> = [
  { key: "picks", label: "Picks" },
  { key: "runs", label: "Runs" },
];

// data-buckets carries all 5 buckets so cli
ent can switch without refetching
const dataJson = JSON.stringify(buckets);
---

<figure
  class="spec-distribution-chart"
  data-scope={scope}
  data-buckets={dataJson}
>
  <div class="spec-distribution-chart__contr
ols">
    <nav
      class="spec-distribution-chart__tabs"
      role="tablist"
      aria-label="Run filter"
    >
      {
        BUCKET_TABS.map((b, i) => (
          <button
            type="button"
            class:list={["spec-distribution-
chart__tab", { active: i === 0 }]}
            role="tab"
            aria-selected={i === 0 ? "true" 
: "false"}
            data-bucket-key={b.key}
          >
            {b.label}
          </button>
        ))
      }
    </nav>
    <div class="spec-distribution-chart__sel
ects">
      <div class="filter-group">
        <label for={`spec-dist-metric-${scop
e}`}>
          Metric
          <HelpHint title="Picks vs Runs">
            <p>
              <strong>Picks</strong> counts 
every spec slot. A run with two of the
              same spec contributes <strong>
2 picks</strong>.
            </p>
            <p>
              <strong>Runs</strong> counts d
istinct runs containing the spec. A run
              with two of the same spec stil
l counts as <strong>1 run</strong>
            </p>
          </HelpHint>
        </label>
        <select id={`spec-dist-metric-${scop
e}`} data-metric-select>
          {METRIC_OPTIONS.map((m) => <option
 value={m.key}>{m.label}</option>)}
        </select>
      </div>
      <div class="filter-group">
        <label for={`spec-dist-role-${scope}
`}>Role</label>
        <select id={`spec-dist-role-${scope}
`} data-role-select>
          {ROLE_TABS.map((r) => <option valu
e={r.key}>{r.label}</option>)}
        </select>
      </div>
      <div class="filter-group">
        <label for={`spec-dist-dungeon-${sco
pe}`}>Dungeon</label>
        <select id={`spec-dist-dungeon-${sco
pe}`} data-dungeon-select>
          {DUNGEON_OPTIONS.map((d) => <optio
n value={d.key}>{d.label}</option>)}
        </select>
      </div>
    </div>
  </div>
  <div class="spec-distribution-chart__canva
s">
    {/* Filled by SpecDistributionChart.ts o
n mount; kept empty server-side. */}
  </div>
</figure>

<script>
  import { initSpecDistributionCharts } from
 "./SpecDistributionChart.ts";
  initSpecDistributionCharts();
  document.addEventListener("astro:page-load
", () =>
    initSpecDistributionCharts(),
  );
</script>
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET