Public
Edited
Jun 10, 2024
2 forks
16 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
while (true) {
let canvas = d3.select("canvas#canvas-wind");

if (!canvas.empty()) {
canvas = canvas.node();
const context = canvas.getContext("2d");

// // 배경 색 설정 (한번만 실행되도록 while 루프 외부로 이동)
// // if (!context.backgroundSet) {
// context.fillStyle = "rgba(255, 0, 0, 1)"; // 원하는 배경 색으로 변경
// context.fillRect(0, 0, canvas.width, canvas.height); // 캔버스 전체를 배경 색으로 채우기
// context.backgroundSet = true; // 배경색이 설정되었음을 표시
// // }

// canvas styling
context.fillStyle = "rgba(0,0,0,0.9)";
context.lineWidth = 1;
context.strokeStyle = "rgba(0, 150, 255, 0.47)";

// trailing effect
// by filling the canvas with a semi-transparent black rectangle, fading previous frames
const prev = context.globalCompositeOperation;
context.globalCompositeOperation = "destination-in";
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = prev;

// blending effect (overlapping particles appear brighter)
context.globalCompositeOperation = "lighter";

//// update particles
updateParticles();

//// render paths
const path = d3.geoPath(projection, context);

context.beginPath();
for (let i = 0; i < particles.length; i++) {
let p = particles[i];

let arc = {
type: "LineString",
coordinates: [
[p.x, p.y],
[p.xt, p.yt]
]
};
// 지리데이터를 기반으로 경로 추가
path(arc);

// 현재위치를 다음위치로 업데이트
p.x = p.xt;
p.y = p.yt;
}

// 경로를 실제로 그리기
context.stroke();
}

yield 1;
}
}
Insert cell
height = 560
Insert cell
backgroundColor = "#000"
Insert cell
Insert cell
// 지도 그리기
function drawMap(canvas) {
var context = canvas.getContext("2d");
const sphere = { type: "Sphere" };
const path = d3.geoPath(projection, context);

// 캔버스 초기화
context.clearRect(0, 0, canvas.width, canvas.height);

// sphere
context.beginPath();
path(sphere);
context.fillStyle = "#111";
// context.lineWidth = 0.8;
context.fill();
// context.strokeStyle = "#ddd"; // 외곽선 색상 설정
context.stroke();

// map land
context.beginPath();
path(land);
context.fillStyle = "#111";
context.fill(); // 지도의 채우기
context.strokeStyle = "rgba(255,255,255,0.65)"; // 외곽선 색상 설정
context.lineWidth = 0;
context.stroke(); // 외곽선 그리기
}
Insert cell
Insert cell
createParticles = (num_particles) => {
let thisParticles = [];
for (let i = 0; i < num_particles; i++) {
const randX = d3.randomUniform(lons[0], lons[lons.length - 1])();
const randY = d3.randomUniform(lats[0], lats[lats.length - 1])();

let p = {
age: 0,
x: randX,
y: randY,
xt: randX,
yt: randY,
life: 0
};
thisParticles.push(p);
}

return thisParticles;
}
Insert cell
updateParticles = () => {
for (let i = 0; i < particles.length; i++) {
let p = particles[i];
p.age += 1;

// over life -> init
if (p.age >= p.life) {
const randX = d3.randomUniform(lons[0], lons[lons.length - 1])();
const randY = d3.randomUniform(lats[0], lats[lats.length - 1])();

p.x = randX;
p.y = randY;
p.age = 0;
p.xt = randX;
p.yt = randY;
p.life = 30 + Math.random() * 30;
continue; // 입자 초기화 후 나머지 코드 실행 방지
}

// out of screen check
if (p.x < lons[0]) {
p.x = lons[lons.length - 1];
} else if (p.x > lons[lons.length - 1]) {
p.x = lons[0];
}
if (p.y < lats[0]) {
p.y = lats[lats.length - 1];
} else if (p.y > lats[lats.length - 1]) {
p.y = lats[0];
}

// update particle
const xIndex = Math.min(Math.max(wScale(p.x), 0), data.u[0].length - 1);
const yIndex = Math.min(Math.max(hScale(p.y), 0), data.u.length - 1);

const vx = (data.u[yIndex][xIndex] * data.factor) / data.maxSpeed; // normalization
const vy = (data.v[yIndex][xIndex] * data.factor) / data.maxSpeed;

p.xt = p.x + vx;
p.yt = p.y + vy;
}
}
Insert cell
Insert cell
hScale = d3
.scaleThreshold()
.domain(lats)
.range(d3.range(lats.length + 1))
Insert cell
wScale = d3
.scaleThreshold()
.domain(lons)
.range(d3.range(lons.length + 1))
Insert cell
// 위도 (y축)
lats = d3.range(data.u.length).map((d) => {
return data.miny + (d * (data.maxy - data.miny)) / (data.u.length - 1);
})
Insert cell
// 경도 (x축)
lons = d3.range(data.u[0].length).map((d) => {
return data.minx + (d * (data.maxx - data.minx)) / (data.u[0].length - 1);
})
Insert cell
Insert cell
Insert cell
data = await FileAttachment("wind.json").json();
Insert cell
land = topojson.feature(world, world.objects.land)
Insert cell
world = d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json")
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3proj = require("d3-geo-projection")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more