2026年2月15日 · 长春
今年过年没回家,享受一下在长春过春节的幸福时光吧。提前祝大家新春快乐!


library(dplyr)
library(ggplot2)
library(gganimate)
library(gifski)
library(transformr)
set.seed(2026) # 马年专属种子
# 定义三种菊花形烟花的配色
firework_shapes <- list(
chrysanthemum_blue = list(name = "蓝色菊花型", colors = c("#00CCFF", "#33DDFF", "#99EEFF", "#CCF5FF")),
chrysanthemum_yellow = list(name = "黄色菊花型", colors = c("#FFD700", "#FFE033", "#FFEB66", "#FFF5CC")),
chrysanthemum_red = list(name = "红色菊花型", colors = c("#FF3333", "#FF6666", "#FF9999", "#FFCCCC")),
line = list(name = "线条型", colors = c("#FFFFFF", "#CCCCCC", "#999999"))
)
# 预定义每个烟花的位置和类型 - 打乱顺序
firework_positions <- list(
# 菊花型烟花 - 不同颜色分布在画布不同位置
chrysanthemum_yellow1 = list(x = -12, y_base = 6, shape = "chrysanthemum_yellow"),
line1 = list(x = -15, y_base = 4, shape = "line"),
chrysanthemum_blue1 = list(x = -5, y_base = 9, shape = "chrysanthemum_blue"),
chrysanthemum_red1 = list(x = 3, y_base = 7, shape = "chrysanthemum_red"),
line2 = list(x = -8, y_base = 5, shape = "line"),
chrysanthemum_yellow2 = list(x = 8, y_base = 8, shape = "chrysanthemum_yellow"),
chrysanthemum_blue2 = list(x = 15, y_base = 6, shape = "chrysanthemum_blue"),
chrysanthemum_red2 = list(x = 0, y_base = 10, shape = "chrysanthemum_red"),
line3 = list(x = 8, y_base = 5, shape = "line"),
line4 = list(x = 15, y_base = 4, shape = "line")
)
# 主数据框
all_particles <- data.frame()
trail_particles <- data.frame()
# 烟花ID计数器
fw_id <- 1
# 为每个烟花生成数据
for(pos_name in names(firework_positions)) {
pos <- firework_positions[[pos_name]]
shape_type <- pos$shape
shape_info <- firework_shapes[[shape_type]]
# 差异化参数
launch_x <- pos$x
launch_y_start <- -8
explode_y <- pos$y_base + runif(1, -1, 1)
# 打乱爆炸顺序 - 更大的延迟范围
launch_delay <- sample(0:120, 1)
rise_dur <- sample(25:35, 1)
# 根据烟花类型确定粒子数和参数
if(shape_type == "chrysanthemum_blue") {
n_particles <- 400 # 蓝色菊花型粒子数最多
speed_range <- c(0.02, 0.30) # 蓝色速度最大,半径最大
resistance <- 0.0006
gravity <- 0.0
particle_size <- 1.6
explosion_duration <- 90 # 蓝色持续时间最长
} elseif(shape_type == "chrysanthemum_yellow") {
n_particles <- 350 # 黄色菊花型粒子数稍少
speed_range <- c(0.015, 0.22) # 黄色速度和半径比蓝色小
resistance <- 0.0008
gravity <- 0.0
particle_size <- 1.5
explosion_duration <- 75 # 黄色持续时间比蓝色短
} elseif(shape_type == "chrysanthemum_red") {
n_particles <- 300 # 红色菊花型粒子数最少
speed_range <- c(0.01, 0.18) # 红色速度和半径最小
resistance <- 0.0010
gravity <- 0.0
particle_size <- 1.4
explosion_duration <- 60 # 红色持续时间最短
} else { # 线条型
n_particles <- 150
speed_range <- c(0.01, 0.02)
resistance <- 0.0025
gravity <- 0.007
particle_size <- 2.2
explosion_duration <- 60
}
# 生成基础粒子数据
particle_data <- data.frame(
firework_id = fw_id,
shape_type = shape_type,
particle_id = 1:n_particles,
launch_x = launch_x,
explode_y = explode_y,
launch_delay = launch_delay,
rise_dur = rise_dur,
speed_range_min = speed_range[1],
speed_range_max = speed_range[2],
resistance = resistance,
gravity = gravity,
particle_size = particle_size,
explosion_duration = explosion_duration
)
# 根据形状计算角度和速度分布
particle_data <- particle_data %>%
mutate(
angle = case_when(
shape_type %in% c("chrysanthemum_blue", "chrysanthemum_yellow", "chrysanthemum_red") ~ {
# 菊花型分为5层
layer <- sample(1:5, n(), replace = TRUE, prob = c(0.1, 0.2, 0.3, 0.25, 0.15))
base_angle <- runif(n(), 0, 2*pi)
base_angle + layer * 0.35
},
shape_type == "line" ~ rep(3*pi/2, n()) + rnorm(n(), 0, 0.05)
),
# 修复:使用向量化方法而不是if语句
speed = ifelse(
shape_type %in% c("chrysanthemum_blue", "chrysanthemum_yellow", "chrysanthemum_red"),
{
# 先分配层
layer <- sample(1:5, n(), replace = TRUE, prob = c(0.1, 0.2, 0.3, 0.25, 0.15))
# 根据烟花类型和层分配基础速度
base_speeds <- numeric(n())
# 蓝色菊花型
blue_mask <- shape_type == "chrysanthemum_blue"
if(any(blue_mask)) {
blue_layers <- layer[blue_mask]
blue_speeds <- c(0.02, 0.08, 0.18, 0.25, 0.32)
base_speeds[blue_mask] <- blue_speeds[blue_layers] * runif(sum(blue_mask), 0.8, 1.2)
}
# 黄色菊花型
yellow_mask <- shape_type == "chrysanthemum_yellow"
if(any(yellow_mask)) {
yellow_layers <- layer[yellow_mask]
yellow_speeds <- c(0.015, 0.06, 0.14, 0.20, 0.25)
base_speeds[yellow_mask] <- yellow_speeds[yellow_layers] * runif(sum(yellow_mask), 0.8, 1.2)
}
# 红色菊花型
red_mask <- shape_type == "chrysanthemum_red"
if(any(red_mask)) {
red_layers <- layer[red_mask]
red_speeds <- c(0.01, 0.05, 0.12, 0.18, 0.22)
base_speeds[red_mask] <- red_speeds[red_layers] * runif(sum(red_mask), 0.8, 1.2)
}
base_speeds
},
# 线条型的速度
runif(n(), speed_range_min, speed_range_max)
),
color = sample(shape_info$colors, n(), replace = TRUE)
)
# 为菊花型记录层级信息
if(shape_type %in% c("chrysanthemum_blue", "chrysanthemum_yellow", "chrysanthemum_red")) {
particle_data <- particle_data %>%
mutate(
chrysanthemum_layer = sample(1:5, n(), replace = TRUE, prob = c(0.1, 0.2, 0.3, 0.25, 0.15))
)
} else {
particle_data <- particle_data %>%
mutate(chrysanthemum_layer = 0)
}
# 扩展到所有帧并计算轨迹
particle_data <- particle_data %>%
slice(rep(1:n(), each = total_frames)) %>%
mutate(
frame = rep(1:total_frames, times = n_particles),
effective_frame = frame - launch_delay,
is_rising = effective_frame <= rise_dur & effective_frame > 0,
is_exploding = effective_frame > rise_dur,
explode_time = effective_frame - rise_dur,
# 位置计算
x = case_when(
effective_frame <= 0 ~ launch_x,
is_rising ~ launch_x + rnorm(n(), 0, 0.015),
shape_type %in% c("chrysanthemum_blue", "chrysanthemum_yellow", "chrysanthemum_red") ~ {
# 菊花形:运动半径根据烟花类型调整
launch_x + cos(angle) * speed * explode_time * (1 - resistance * explode_time)
},
TRUE ~ launch_x + cos(angle) * speed * explode_time * (1 - resistance * explode_time)
),
y = case_when(
effective_frame <= 0 ~ launch_y_start,
is_rising ~ launch_y_start + (explode_y - launch_y_start) * (effective_frame / rise_dur) + rnorm(n(), 0, 0.005),
shape_type == "line" ~ explode_y + sin(angle) * speed * explode_time - gravity * explode_time^2,
# 菊花形:爆炸过程中没有下落
TRUE ~ explode_y + sin(angle) * speed * explode_time * (1 - resistance * explode_time)
),
# 大小变化
size = case_when(
effective_frame <= 0 ~ 0,
is_rising ~ particle_size * 0.3,
explode_time <= explosion_duration ~ particle_size,
explode_time <= (explosion_duration + 40) ~ particle_size - (explode_time - explosion_duration) * 0.025,
TRUE ~ particle_size * 0.3
),
# 透明度变化
alpha = case_when(
effective_frame <= 0 ~ 0,
is_rising ~ 0.7,
explode_time <= explosion_duration ~ 1.0,
explode_time <= (explosion_duration + 40) ~ 1 - (explode_time - explosion_duration)/40,
TRUE ~ 0
)
) %>%
mutate(
size = pmax(size, 0.1),
alpha = pmax(pmin(alpha, 1), 0),
x = pmin(pmax(x, -35), 35),
y = pmin(pmax(y, -12), 18)
) %>%
filter(effective_frame <= 180, effective_frame >= -5)
# 生成拖尾粒子数据
if(nrow(particle_data) > 0) {
explosion_particles <- particle_data %>%
filter(is_exploding, explode_time > 0, explode_time <= 150)
if(nrow(explosion_particles) > 0) {
# 根据烟花类型确定拖尾层数
if(shape_type %in% c("chrysanthemum_blue", "chrysanthemum_yellow", "chrysanthemum_red")) {
# 菊花形:更长的拖尾
tail_layers <- 10
tail_decay_rate <- 0.6
} else {
# 线条型:较短拖尾
tail_layers <- 5
tail_decay_rate <- 0.8
}
# 创建拖尾数据
tail_data_list <- list()
for(i in 1:tail_layers) {
tail_data <- explosion_particles %>%
group_by(firework_id, particle_id) %>%
arrange(frame) %>%
mutate(
tail_x = lag(x, i),
tail_y = lag(y, i),
# 拖尾大小和透明度随层数衰减
tail_size = particle_size * (tail_decay_rate^i),
tail_alpha = alpha * (tail_decay_rate^i) * 0.7
) %>%
ungroup() %>%
filter(!is.na(tail_x)) %>%
select(frame, firework_id, particle_id,
x = tail_x, y = tail_y,
size = tail_size, alpha = tail_alpha, color, shape_type) %>%
mutate(tail_layer = i)
tail_data_list[[i]] <- tail_data
}
# 合并所有拖尾数据
tail_points <- bind_rows(tail_data_list)
trail_particles <- bind_rows(trail_particles, tail_points)
}
}
# 添加主粒子数据
all_particles <- bind_rows(all_particles, particle_data)
fw_id <- fw_id + 1
}
# 绘制动画
p <- ggplot() +
# 1. 先绘制拖尾粒子(放在底层)
geom_point(
data = trail_particles,
aes(x = x, y = y, color = color, size = size, alpha = alpha),
shape = 16
) +
# 2. 再绘制主粒子(放在上层)
geom_point(
data = all_particles,
aes(x = x, y = y, color = color, size = size, alpha = alpha),
shape = 16
) +
scale_color_identity() +
scale_size_identity() +
scale_alpha_identity() +
coord_cartesian(xlim = canvas_xlim, ylim = canvas_ylim) +
theme_void() +
theme(
legend.position = "none",
plot.background = element_rect(fill = "#000033", color = NA),
panel.background = element_rect(fill = "#000033", color = NA),
plot.title = element_text(color = "gold", size = 24, hjust = 0.5, face = "bold", margin = margin(t = 10)),
plot.subtitle = element_text(color = "lightblue", size = 16, hjust = 0.5, margin = margin(t = 5, b = 10))
) +
transition_time(frame) +
ease_aes('cubic-out')
animate(
p,
nframes = total_frames,
fps = fps,
width = 1200,
height = 900,
renderer = gifski_renderer(loop = TRUE),
detail = 2
)
anim_save("专属烟花.gif", fps =20, width =1000, height =600, res =150)
