ManaDash logo ManaDash logo ManaDash
  • Home
  • Archetypes
  • Decktypes
  • Players
  • Elo
  • Deck Viewer
  • Cards Stats
  • Card Groups
  • Card Impact
  • Season Ranking
  • Standings
Players
  • Main Content
archetypeCountsData = transpose(player_archetype_counts)
decktypeCountsData = transpose(player_decktype_counts)
playerStatsData = transpose(player_stats)
playerVsData = transpose(player_vs_stats)
mostPlayedCardsData = transpose(most_played_cards)
powerPiecesData = transpose(power_pieces)
powerPiecePicksData = transpose(power_piece_picks)
archetypeWinrateData = transpose(player_archetype_winrate)
decktypeWinrateData = transpose(player_decktype_winrate)
cardWinrateData = transpose(player_card_winrate)
function forSelection(rows) {
  return rows.filter(d =>
    d.player === player && (d.season_id === undefined || d.season_id === season)
  );
}

pct = d => (d == null || isNaN(d)) ? "" : (d * 100).toFixed(1) + "%"
pctRaw = d => (d == null || isNaN(d)) ? "" : d.toFixed(1) + "%"
num = d => (d == null || isNaN(d)) ? "" : d3.format(".2f")(d)
// Sorts descending by `y`, optionally keeps the top `top` rows, and draws a bar
// chart. `rule` adds the 50% reference line.
function barChart(data, {x, y, yLabel, colors, top, rule = false, yMin,
                        height = 700, fontSize = 28, wrap = true} = {}) {
  const rows = data
    .filter(d => d.season_id === season)
    .sort((a, b) => d3.descending(a[y], b[y]))
    .slice(0, top ?? Infinity);

  const label = s => String(s).replace(/[_\s]+/g, "\n");
  const lines = wrap ? d3.max(rows, d => label(d[x]).split("\n").length) : 1;
  const rotate = wrap ? 0 : (rows.length > 12 ? -45 : 0);

  return Plot.plot({
    width,
    height,
    marginBottom: rotate ? height * 0.31
                : wrap ? fontSize * 1.2 * lines + 24
                : height * 0.16,
    marginLeft: fontSize * 2.5,
    style: {fontSize: `${fontSize}px`},
    x: {
      label: null,
      domain: rows.map(d => d[x]),
      ticks: rows.map(d => d[x]),
      tickFormat: wrap ? label : undefined,
      tickRotate: rotate,
      tickPadding: 8
    },
    y: {
      label: yLabel,
      labelArrow: "none",
      labelAnchor: "center",
      labelOffset: fontSize * 2.5,
      ticks: 5,
      grid: true,
      domain: yMin === undefined ? undefined : [yMin, d3.max(rows, d => d[y])]
    },
    color: {domain: Object.keys(colors), range: Object.values(colors)},
    marks: [
      Plot.barY(rows, {x, y, fill: x, tip: true}),
      rule ? Plot.ruleY([0.5], {
        stroke: "gray", strokeDasharray: "4,4", strokeOpacity: 0.5
      }) : null
    ]
  });
}
// Replaces the Dash DataTable: a search box plus a sortable, paged table.
function searchTable(rows, options = {}) {
  const search = Inputs.search(rows, {placeholder: "Filter rows…"});
  const out = document.createElement("div");

  const render = () => out.replaceChildren(
    Inputs.table(search.value, {layout: "auto", ...options})
  );

  search.addEventListener("input", render);
  render();

  const wrapper = document.createElement("div");
  wrapper.style.width = "100%";
  wrapper.style.height = "100%";
  wrapper.style.display = "flex";
  wrapper.style.flexDirection = "column";

  out.style.flex = "1";
  out.style.minHeight = "0";
  out.style.overflow = "auto";

  wrapper.append(search, out);
  return wrapper;
}
Archetypes
barChart(forSelection(archetypeCountsData), {
  x: "archetype",
  y: "arche_types_count",
  yLabel: "Count",
  colors: archetype_color_map
})
Decktypes
barChart(forSelection(decktypeCountsData), {
  x: "decktype",
  y: "deck_types_count",
  yLabel: "Count",
  colors: decktype_color_map
})
  • Player Statistics Overview
  • Player Versus Overview
  • Most Played Cards
  • Power Cards
  • Archetype Winrates
  • Decktype Winrates
  • Card Winrate Per Season
searchTable(forSelection(playerStatsData), {
  header: {
    season_id: "Season",
    player: "Player",
    games_played: "Drafts Attended",
    total_wins: "Drafts Won",
    total_points: "Total Points",
    avg_points_per_draft: "Average Points",
    most_common_archetype: "Most Common Archetype",
    most_common_decktype: "Most Common Decktype",
    average_omp: "Average OMP",
    average_gwp: "Average GWP",
    average_ogp: "Average OGP"
  },
  format: {
    avg_points_per_draft: num,
    average_omp: pctRaw,
    average_gwp: pctRaw,
    average_ogp: pctRaw
  }
})
searchTable(forSelection(playerVsData), {
  header: {
    season_id: "Season",
    player: "Player",
    opponent: "Opponent",
    games_played_vs: "Games Played VS",
    games_won_vs: "Games Won VS",
    game_win_rate_vs: "Game Win Rate",
    matches_played_vs: "Matches Played",
    matches_won_vs: "Matches Won",
    match_win_rate_vs: "Match Win Rate"
  },
  format: {
    game_win_rate_vs: pct,
    match_win_rate_vs: pct
  }
})
// Only ever TOP_CARDS rows, so no search box, and lift Inputs.table's default
// ~11-row max-height (sized for 22px rows; this theme's rows are taller).
{
  const table = Inputs.table(forSelection(mostPlayedCardsData), {
    layout: "auto",
    header: {
      season_id: "Season",
      player: "Player",
      rank: "Rank",
      card_name: "Card Name",
      pick_count: "Pick Count"
    }
  });
  table.style.maxHeight = "none";
  // Shrink to the widest card name instead of spanning the whole card.
  table.style.width = "max-content";
  table.style.maxWidth = "100%";
  return table;
}
// Summary row on top, then all power pieces with how often each was picked.
// Both are short, so no search box and no max-height (see Most Played Cards).
{
  const compact = table => {
    table.style.maxHeight = "none";
    table.style.width = "max-content";
    table.style.maxWidth = "100%";
    return table;
  };

  const summary = compact(Inputs.table(forSelection(powerPiecesData), {
    layout: "auto",
    header: {
      season_id: "Season",
      player: "Player",
      drafts_played: "Drafts Played",
      seasons_played: "Seasons Played",
      power_count: "Power Pieces",
      avg_power_per_draft: "Avg per Draft",
      most_picked_power_card: "Most Picked Power",
      most_picked_power_count: "Times Picked"
    },
    format: {avg_power_per_draft: num}
  }));

  const picks = compact(Inputs.table(forSelection(powerPiecePicksData), {
    layout: "auto",
    columns: ["card_name", "pick_count"],
    header: {card_name: "Power Piece", pick_count: "Times Picked"}
  }));
  picks.style.marginTop = "1.5rem";

  return html`<div>${summary}${picks}</div>`;
}
searchTable(forSelection(archetypeWinrateData), {
  header: {
    season_id: "Season",
    player: "Player",
    archetype: "Archetype",
    archtype: "Archetype",
    games_won: "Games Won",
    games_played: "Games Played",
    game_win_rate: "Game Win Rate",
    matches_played: "Matches Played",
    matches_won: "Matches Won",
    match_win_rate: "Match Win Rate"
  },
  format: {
    game_win_rate: pct,
    match_win_rate: pct
  }
})
searchTable(forSelection(decktypeWinrateData), {
  header: {
    season_id: "Season",
    player: "Player",
    decktype: "Decktype",
    games_won: "Games Won",
    games_played: "Games Played",
    game_win_rate: "Game Win Rate",
    matches_played: "Matches Played",
    matches_won: "Matches Won",
    match_win_rate: "Match Win Rate"
  },
  format: {
    game_win_rate: pct,
    match_win_rate: pct
  }
})
searchTable(forSelection(cardWinrateData), {
  header: {
    season_id: "Season",
    player: "Player",
    card_name: "Card Name",
    num_drafts_with_card: "Drafts with Card",
    games_won: "Games Won",
    games_played: "Games Played",
    game_win_rate: "Game Win Rate",
    matches_played: "Matches Played",
    matches_won: "Matches Won",
    match_win_rate: "Match Win Rate"
  },
  format: {
    game_win_rate: pct,
    match_win_rate: pct
  }
})
viewof player = Inputs.select(players, {
  label: "Player",
  value: players[0]
})
viewof season = Inputs.select(seasons, {
  label: "Season",
  value: seasons[0]
})
 

ManaDash — Vintage Cube Analysis