igroffman commited on
Commit
64d3ce0
·
verified ·
1 Parent(s): ed13c12

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +167 -122
app.R CHANGED
@@ -1438,30 +1438,32 @@ create_tableau_location_plot <- function(pitcher_df, pitch_colors) {
1438
  # =====================================================================
1439
  create_tableau_movement_plot <- function(pitcher_df, pitch_colors) {
1440
  filter_types <- get_valid_pitch_types(pitcher_df)
1441
-
1442
  df <- pitcher_df %>%
1443
  filter(TaggedPitchType %in% filter_types,
1444
  !is.na(HorzBreak), !is.na(InducedVertBreak))
1445
-
1446
  if (nrow(df) == 0) {
1447
- return(ggplot() + theme_void() +
1448
  labs(title = "Movement Profile") +
1449
  theme(plot.title = element_text(size = 12, face = "bold", hjust = 0.5)))
1450
  }
1451
-
1452
- ggplot(df, aes(x = HorzBreak, y = InducedVertBreak, color = TaggedPitchType)) +
1453
  geom_vline(xintercept = 0, color = "black", linewidth = 0.4) +
1454
  geom_hline(yintercept = 0, color = "black", linewidth = 0.4) +
1455
- geom_point(size = 4, alpha = 1, shape = 21, color = "black", stroke = 2) +
1456
- scale_color_manual(values = pitch_colors) +
1457
- coord_cartesian(xlim = c(-25, 25), ylim = c(-25, 25)) +
 
 
1458
  labs(title = "Movement Profile", x = "HB", y = "IVB") +
1459
  theme_minimal() +
1460
  theme(
1461
- plot.title = element_text(size = 9, face = "bold", hjust = 0.5),
1462
  legend.position = "none",
1463
- axis.title = element_text(size = 7),
1464
- axis.text = element_text(size = 6),
1465
  plot.margin = margin(2, 2, 2, 2)
1466
  )
1467
  }
@@ -1471,116 +1473,163 @@ create_tableau_movement_plot <- function(pitcher_df, pitch_colors) {
1471
  # =====================================================================
1472
  create_tableau_release_plot <- function(pitcher_df, pitch_colors) {
1473
  filter_types <- get_valid_pitch_types(pitcher_df)
1474
-
1475
  df <- pitcher_df %>%
1476
  filter(TaggedPitchType %in% filter_types,
1477
  !is.na(RelSide), !is.na(RelHeight))
1478
-
1479
  if (nrow(df) == 0) {
1480
- return(ggplot() + theme_void() +
1481
  labs(title = "Release Plot") +
1482
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5)))
1483
  }
1484
-
1485
- # Mound arc
1486
  mound_theta <- seq(0, pi, length.out = 100)
1487
  mound_radius <- 3
1488
  mound_df <- data.frame(
1489
  x = mound_radius * cos(mound_theta),
1490
  y = mound_radius * sin(mound_theta) * 0.35
1491
  )
1492
-
1493
  ggplot(df, aes(x = RelSide, y = RelHeight)) +
1494
- geom_polygon(data = mound_df, aes(x = x, y = y),
1495
  fill = "#C0392B", color = NA, inherit.aes = FALSE) +
1496
- annotate("rect", xmin = -0.5, xmax = 0.5, ymin = 0.85, ymax = 1.05,
1497
  fill = "white", color = "gray40", linewidth = 0.3) +
1498
  geom_vline(xintercept = 0, color = "gray60", linetype = "dashed", linewidth = 0.3) +
1499
- geom_point(aes(color = TaggedPitchType), size = 4, alpha = 1, shape = 21, stroke = 2, color = "black") +
1500
- scale_color_manual(values = pitch_colors) +
 
1501
  coord_cartesian(xlim = c(-4, 4), ylim = c(0, 7)) +
1502
  labs(title = "Release Plot", x = "Side", y = "Height") +
1503
  theme_minimal() +
1504
  theme(
1505
- plot.title = element_text(size = 9, face = "bold", hjust = 0.5),
1506
  legend.position = "none",
1507
- axis.title = element_text(size = 7),
1508
- axis.text = element_text(size = 6),
1509
  plot.margin = margin(2, 2, 2, 2)
1510
  )
1511
  }
1512
 
1513
  # =====================================================================
1514
- # TABLE DRAWING - Compact with bigger text
1515
  # =====================================================================
1516
- draw_tableau_table_v3 <- function(title, data, metrics, pitch_types, pitch_colors,
1517
- x_start, y_start, col_w = 0.075, row_h = 0.014) {
1518
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1519
  # Title
1520
- grid.text(title, x = x_start + length(pitch_types) * col_w / 2, y = y_start + 0.018,
1521
- gp = gpar(fontface = "bold", cex = 0.65, col = "#006F71"))
1522
-
 
 
 
 
1523
  # Column headers (pitch types)
1524
- header_y <- y_start
1525
  for (i in seq_along(pitch_types)) {
1526
  pt <- pitch_types[i]
1527
- col_color <- if (pt %in% names(pitch_colors)) pitch_colors[pt] else "#95A5A6"
1528
-
1529
- grid.rect(x = x_start + (i-1)*col_w, y = header_y,
1530
- width = col_w * 0.96, height = row_h,
1531
- just = c("left", "center"),
1532
- gp = gpar(fill = col_color, col = "gray60", lwd = 0.3))
1533
-
1534
- # Abbreviate long pitch names
1535
- pt_short <- gsub("ChangeUp", "CH", pt)
1536
- pt_short <- gsub("Fastball", "FB", pt_short)
 
 
 
 
1537
  pt_short <- gsub("Curveball", "CB", pt_short)
1538
  pt_short <- gsub("Slider", "SL", pt_short)
1539
  pt_short <- gsub("Sinker", "SI", pt_short)
1540
  pt_short <- gsub("Cutter", "CT", pt_short)
1541
  pt_short <- gsub("Splitter", "SP", pt_short)
1542
  pt_short <- gsub("Sweeper", "SW", pt_short)
1543
-
1544
- grid.text(pt_short, x = x_start + (i-1)*col_w + col_w/2, y = header_y,
1545
- gp = gpar(col = "white", cex = 0.42, fontface = "bold"))
 
 
 
 
1546
  }
1547
-
1548
  # Data rows
1549
- for (m in seq_along(metrics)) {
1550
- metric_name <- metrics[m]
1551
- y_pos <- header_y - m * row_h
1552
-
1553
- # Row label - abbreviated
1554
- metric_short <- gsub("Usage vs. ", "", metric_name)
1555
- metric_short <- gsub("Avg. ", "", metric_short)
1556
- metric_short <- gsub("Max. ", "Max ", metric_short)
1557
- metric_short <- gsub("Pitch Count", "#", metric_short)
1558
- metric_short <- gsub(" Rate", "", metric_short)
1559
-
1560
- grid.text(metric_short, x = x_start - 0.008, y = y_pos, just = "right",
1561
- gp = gpar(cex = 0.42, fontface = "bold"))
1562
-
1563
- # Values for each pitch type
 
 
1564
  for (i in seq_along(pitch_types)) {
1565
  pt <- pitch_types[i]
1566
  idx <- which(data$TaggedPitchType == pt)
1567
- val <- if (length(idx) > 0 && metric_name %in% names(data)) {
1568
- as.character(data[[metric_name]][idx])
1569
- } else {
1570
- "-"
1571
  }
1572
-
1573
- grid.rect(x = x_start + (i-1)*col_w, y = y_pos,
1574
- width = col_w * 0.96, height = row_h * 0.92,
1575
- just = c("left", "center"),
1576
- gp = gpar(fill = "white", col = "gray80", lwd = 0.2))
1577
-
1578
- grid.text(val, x = x_start + (i-1)*col_w + col_w/2, y = y_pos,
1579
- gp = gpar(cex = 0.40))
 
 
 
 
 
 
 
 
1580
  }
1581
  }
1582
  }
1583
 
 
1584
  # =====================================================================
1585
  # MAIN PDF FUNCTION
1586
  # =====================================================================
@@ -1708,56 +1757,52 @@ create_tableau_pitcher_pdf <- function(game_df, pitcher_name, output_file) {
1708
  # =====================================================================
1709
  # TABLES - Farther right, more compact, bigger text
1710
  # =====================================================================
 
 
 
 
 
 
 
 
 
 
 
 
1711
 
1712
- # Adjust table position based on number of pitch types
1713
- n_types <- length(pitch_types)
1714
- col_w <- min(0.085, 0.42 / max(1, n_types))
1715
- table_x <- 0.54
1716
-
1717
- # Location Data table
1718
- draw_tableau_table_v3(
1719
- title = "Location Data",
1720
- data = loc_data,
1721
- metrics = c("Zone%", "Edge%", "Strike%", "Whiff%"),
1722
- pitch_types = pitch_types,
1723
- pitch_colors = tableau_pitch_colors,
1724
- x_start = table_x, y_start = 0.78,
1725
- col_w = col_w, row_h = 0.014
1726
- )
1727
-
1728
- # Pitch Usage table
1729
- draw_tableau_table_v3(
1730
- title = "Pitch Usage",
1731
- data = usage_data,
1732
- metrics = c("Usage vs. LHH", "Usage vs. RHH", "Pitch Count"),
1733
- pitch_types = pitch_types,
1734
- pitch_colors = tableau_pitch_colors,
1735
- x_start = table_x, y_start = 0.66,
1736
- col_w = col_w, row_h = 0.014
1737
- )
1738
-
1739
- # Velo & Movement table
1740
- draw_tableau_table_v3(
1741
- title = "Velo & Movement",
1742
- data = velo_data,
1743
- metrics = c("Avg. Velo", "Max. Velo", "Avg. Spin", "Max. Spin", "Avg. IVB", "Avg. HB"),
1744
- pitch_types = pitch_types,
1745
- pitch_colors = tableau_pitch_colors,
1746
- x_start = table_x, y_start = 0.52,
1747
- col_w = col_w, row_h = 0.013
1748
- )
1749
-
1750
- # Release Data table
1751
- draw_tableau_table_v3(
1752
- title = "Release Data",
1753
- data = rel_data,
1754
- metrics = c("Rel Ht", "vs FB", "Rel Side", "vs FB (S)", "Ext"),
1755
- pitch_types = pitch_types,
1756
- pitch_colors = tableau_pitch_colors,
1757
- x_start = table_x, y_start = 0.36,
1758
- col_w = col_w, row_h = 0.013
1759
- )
1760
-
1761
  invisible(output_file)
1762
  }
1763
 
 
1438
  # =====================================================================
1439
  create_tableau_movement_plot <- function(pitcher_df, pitch_colors) {
1440
  filter_types <- get_valid_pitch_types(pitcher_df)
1441
+
1442
  df <- pitcher_df %>%
1443
  filter(TaggedPitchType %in% filter_types,
1444
  !is.na(HorzBreak), !is.na(InducedVertBreak))
1445
+
1446
  if (nrow(df) == 0) {
1447
+ return(ggplot() + theme_void() +
1448
  labs(title = "Movement Profile") +
1449
  theme(plot.title = element_text(size = 12, face = "bold", hjust = 0.5)))
1450
  }
1451
+
1452
+ ggplot(df, aes(x = HorzBreak, y = InducedVertBreak)) +
1453
  geom_vline(xintercept = 0, color = "black", linewidth = 0.4) +
1454
  geom_hline(yintercept = 0, color = "black", linewidth = 0.4) +
1455
+ # shape 21 uses fill for the inside color
1456
+ geom_point(aes(fill = TaggedPitchType), size = 4, alpha = 1,
1457
+ shape = 21, color = "black", stroke = 0.9) +
1458
+ scale_fill_manual(values = pitch_colors, drop = FALSE) +
1459
+ coord_cartesian(xlim = c(-30, 30), ylim = c(-30, 30)) +
1460
  labs(title = "Movement Profile", x = "HB", y = "IVB") +
1461
  theme_minimal() +
1462
  theme(
1463
+ plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
1464
  legend.position = "none",
1465
+ axis.title = element_text(size = 10),
1466
+ axis.text = element_text(size = 9),
1467
  plot.margin = margin(2, 2, 2, 2)
1468
  )
1469
  }
 
1473
  # =====================================================================
1474
  create_tableau_release_plot <- function(pitcher_df, pitch_colors) {
1475
  filter_types <- get_valid_pitch_types(pitcher_df)
1476
+
1477
  df <- pitcher_df %>%
1478
  filter(TaggedPitchType %in% filter_types,
1479
  !is.na(RelSide), !is.na(RelHeight))
1480
+
1481
  if (nrow(df) == 0) {
1482
+ return(ggplot() + theme_void() +
1483
  labs(title = "Release Plot") +
1484
  theme(plot.title = element_text(size = 10, face = "bold", hjust = 0.5)))
1485
  }
1486
+
 
1487
  mound_theta <- seq(0, pi, length.out = 100)
1488
  mound_radius <- 3
1489
  mound_df <- data.frame(
1490
  x = mound_radius * cos(mound_theta),
1491
  y = mound_radius * sin(mound_theta) * 0.35
1492
  )
1493
+
1494
  ggplot(df, aes(x = RelSide, y = RelHeight)) +
1495
+ geom_polygon(data = mound_df, aes(x = x, y = y),
1496
  fill = "#C0392B", color = NA, inherit.aes = FALSE) +
1497
+ annotate("rect", xmin = -0.5, xmax = 0.5, ymin = 0.85, ymax = 1.05,
1498
  fill = "white", color = "gray40", linewidth = 0.3) +
1499
  geom_vline(xintercept = 0, color = "gray60", linetype = "dashed", linewidth = 0.3) +
1500
+ geom_point(aes(fill = TaggedPitchType), size = 4, alpha = 1,
1501
+ shape = 21, color = "black", stroke = 0.9) +
1502
+ scale_fill_manual(values = pitch_colors, drop = FALSE) +
1503
  coord_cartesian(xlim = c(-4, 4), ylim = c(0, 7)) +
1504
  labs(title = "Release Plot", x = "Side", y = "Height") +
1505
  theme_minimal() +
1506
  theme(
1507
+ plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
1508
  legend.position = "none",
1509
+ axis.title = element_text(size = 10),
1510
+ axis.text = element_text(size = 9),
1511
  plot.margin = margin(2, 2, 2, 2)
1512
  )
1513
  }
1514
 
1515
  # =====================================================================
1516
+ # TABLE DRAWING (BIG + AUTO-SCALED TO FILL A RECTANGLE)
1517
  # =====================================================================
1518
+ draw_tableau_table_fill <- function(
1519
+ title,
1520
+ data,
1521
+ rows, # a named vector: names = display label, values = column name in `data`
1522
+ pitch_types,
1523
+ pitch_colors,
1524
+ x, y, # TOP-LEFT anchor of the table block (npc)
1525
+ width, height # size of the table block (npc)
1526
+ ) {
1527
+ if (is.null(pitch_types) || length(pitch_types) == 0) pitch_types <- "Undefined"
1528
+
1529
+ n_cols <- length(pitch_types)
1530
+ n_rows <- length(rows) + 1 # +1 header row
1531
+
1532
+ # Leave some space for the title
1533
+ title_h <- min(0.06, height * 0.20)
1534
+ table_top <- y - title_h
1535
+ table_h <- height - title_h
1536
+
1537
+ col_w <- width / n_cols
1538
+ row_h <- table_h / n_rows
1539
+
1540
+ # Scale font to row height (tuned for letter portrait)
1541
+ header_cex <- max(0.9, min(1.6, row_h * 28))
1542
+ body_cex <- max(0.85, min(1.5, row_h * 26))
1543
+ label_cex <- max(0.85, min(1.5, row_h * 26))
1544
+ title_cex <- max(1.1, min(1.9, row_h * 30))
1545
+
1546
  # Title
1547
+ grid.text(
1548
+ title,
1549
+ x = x + width/2,
1550
+ y = y,
1551
+ gp = gpar(fontface = "bold", cex = title_cex, col = "#006F71")
1552
+ )
1553
+
1554
  # Column headers (pitch types)
 
1555
  for (i in seq_along(pitch_types)) {
1556
  pt <- pitch_types[i]
1557
+ col_color <- if (pt %in% names(pitch_colors)) pitch_colors[[pt]] else "#95A5A6"
1558
+
1559
+ grid.rect(
1560
+ x = x + (i-1)*col_w,
1561
+ y = table_top,
1562
+ width = col_w * 0.98,
1563
+ height = row_h * 0.95,
1564
+ just = c("left", "top"),
1565
+ gp = gpar(fill = col_color, col = "gray30", lwd = 0.6)
1566
+ )
1567
+
1568
+ pt_short <- pt
1569
+ pt_short <- gsub("ChangeUp|Changeup", "CH", pt_short)
1570
+ pt_short <- gsub("Fastball|Four-Seam|FourSeamFastBall|Four-Seam Fastball|4-Seam Fastball|Four-Seam", "FB", pt_short)
1571
  pt_short <- gsub("Curveball", "CB", pt_short)
1572
  pt_short <- gsub("Slider", "SL", pt_short)
1573
  pt_short <- gsub("Sinker", "SI", pt_short)
1574
  pt_short <- gsub("Cutter", "CT", pt_short)
1575
  pt_short <- gsub("Splitter", "SP", pt_short)
1576
  pt_short <- gsub("Sweeper", "SW", pt_short)
1577
+
1578
+ grid.text(
1579
+ pt_short,
1580
+ x = x + (i-1)*col_w + col_w/2,
1581
+ y = table_top - row_h*0.48,
1582
+ gp = gpar(col = "white", cex = header_cex, fontface = "bold")
1583
+ )
1584
  }
1585
+
1586
  # Data rows
1587
+ row_names <- names(rows)
1588
+ col_names <- as.character(rows)
1589
+
1590
+ for (r in seq_along(col_names)) {
1591
+ disp <- row_names[r]
1592
+ coln <- col_names[r]
1593
+ y_row_top <- table_top - r*row_h
1594
+
1595
+ # Row label on the left (slightly outside the block)
1596
+ grid.text(
1597
+ disp,
1598
+ x = x - 0.010,
1599
+ y = y_row_top - row_h*0.55,
1600
+ just = "right",
1601
+ gp = gpar(cex = label_cex, fontface = "bold")
1602
+ )
1603
+
1604
  for (i in seq_along(pitch_types)) {
1605
  pt <- pitch_types[i]
1606
  idx <- which(data$TaggedPitchType == pt)
1607
+
1608
+ val <- "-"
1609
+ if (length(idx) > 0 && coln %in% names(data)) {
1610
+ val <- as.character(data[[coln]][idx[1]])
1611
  }
1612
+
1613
+ grid.rect(
1614
+ x = x + (i-1)*col_w,
1615
+ y = y_row_top,
1616
+ width = col_w * 0.98,
1617
+ height = row_h * 0.95,
1618
+ just = c("left", "top"),
1619
+ gp = gpar(fill = "white", col = "gray40", lwd = 0.6)
1620
+ )
1621
+
1622
+ grid.text(
1623
+ val,
1624
+ x = x + (i-1)*col_w + col_w/2,
1625
+ y = y_row_top - row_h*0.55,
1626
+ gp = gpar(cex = body_cex, fontface = "plain")
1627
+ )
1628
  }
1629
  }
1630
  }
1631
 
1632
+
1633
  # =====================================================================
1634
  # MAIN PDF FUNCTION
1635
  # =====================================================================
 
1757
  # =====================================================================
1758
  # TABLES - Farther right, more compact, bigger text
1759
  # =====================================================================
1760
+ table_x <- 0.54
1761
+ table_w <- 0.43 # wide right block
1762
+ # Each table gets a tall rectangle
1763
+ draw_tableau_table_fill(
1764
+ title = "Location Data",
1765
+ data = loc_data,
1766
+ rows = c("Zone%"="Zone%", "Edge%"="Edge%", "Strike%"="Strike%", "Whiff%"="Whiff%"),
1767
+ pitch_types = pitch_types,
1768
+ pitch_colors = tableau_pitch_colors,
1769
+ x = table_x, y = 0.80, width = table_w, height = 0.18
1770
+ )
1771
+
1772
 
1773
+ draw_tableau_table_fill(
1774
+ title = "Pitch Usage",
1775
+ data = usage_data,
1776
+ rows = c("Usage vs. LHH"="Usage vs. LHH", "Usage vs. RHH"="Usage vs. RHH", "Pitch Count"="Pitch Count"),
1777
+ pitch_types = pitch_types,
1778
+ pitch_colors = tableau_pitch_colors,
1779
+ x = table_x, y = 0.60, width = table_w, height = 0.18
1780
+ )
1781
+
1782
+ draw_tableau_table_fill(
1783
+ title = "Velo & Movement",
1784
+ data = velo_data,
1785
+ rows = c("Avg. Velo"="Avg. Velo", "Max Velo"="Max. Velo", "Avg. Spin"="Avg. Spin",
1786
+ "Max Spin"="Max. Spin", "Avg. IVB"="Avg. IVB", "Avg. HB"="Avg. HB"),
1787
+ pitch_types = pitch_types,
1788
+ pitch_colors = tableau_pitch_colors,
1789
+ x = table_x, y = 0.40, width = table_w, height = 0.22
1790
+ )
1791
+
1792
+ # IMPORTANT: your old call was using fake column names, so it printed "-" everywhere.
1793
+ draw_tableau_table_fill(
1794
+ title = "Release Data",
1795
+ data = rel_data,
1796
+ rows = c("Rel Ht"="Avg. Rel Ht",
1797
+ "Rel Ht vs FB (in)"="Rel Ht vs. FB",
1798
+ "Rel Side"="Avg. Rel Side",
1799
+ "Rel Side vs FB (in)"="Rel Side vs. FB",
1800
+ "Ext"="Avg. Ext"),
1801
+ pitch_types = pitch_types,
1802
+ pitch_colors = tableau_pitch_colors,
1803
+ x = table_x, y = 0.14, width = table_w, height = 0.22
1804
+ )
1805
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1806
  invisible(output_file)
1807
  }
1808