Published
Edited
Apr 16, 2020
3 stars
Insert cell
md`# Baseball Projection Chart`
Insert cell
{
// Create Svg
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
console.log(data);
// Create an x axis ticks
svg
.append('g')
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));

// Create an y axis ticks
svg
.append('g')
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

var t = svg
.transition()
.duration(2000)
.ease(d3.easeLinear);

const paths = svg
.append('g')
.selectAll('path')
.data(data)
.join('path')
.attr('d', d => line(d.points)) // Calls the line generator
.attr('stroke', d => d.color)
.attr('fill', 'none')
.attr('stroke-width', 2)
.on("mouseover", onHover)
.on("mouseout", onUnhover)
.style('cursor', 'pointer')
.attr("stroke-dasharray", 1500 + " " + 1500)
.attr("stroke-dashoffset", 1500);

paths
.transition(t)

.attr("stroke-dashoffset", 0);
//console.log(transition13)

// Appends a circle for each datapoint
const circles = svg
.append('g')
.selectAll('circle')
.data(data)
.join('circle')
.datum(d => ({ ...d.points[2], color: d.color }))
.attr('fill', d => d.color)
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 4);
return svg.node();
}
Insert cell
div = d3.select("body").append("div")
.style('position', 'absolute')
.style('text-align', 'center')
.style('width', 60)
.style('height', 30)
.style('font', '12px sans-serif')
.style("opacity", 0)
.style('border-radius', '8px')
.style('background', '#000')
.style('pointer-events', 'none')
.style('padding','5px')
.style("color", "#fff");
Insert cell
function onHover(d) {
const allPaths = d3.select(this.parentNode).selectAll('path')
const allCircles = d3.select(this.parentNode).selectAll('circle')
const filteredPaths = allPaths.filter(thisD => thisD !== d && thisD )
const filteredCircle = allCircles.filter(thisD =>thisD.id!==d.points[2].id )
filteredPaths.transition().duration(500).style('opacity', '0')
filteredCircle.transition().duration(500).style('opacity', '0')
div.transition()
.duration(200)
.style("opacity", .9);
div.html("Distance Covered: "+ d3.format(".0f")(d.distance)+"m" +
"<br/>" +"Peak Height: "+d3.format(".0f")(d.height)+"m" +
"<br/>" +"Flight Time: "+d3.format(".0f")(d.time*1000)+ "ms" )
.style("left", (d3.event.pageX ) + "px")
.style("top", (d3.event.pageY -100) + "px");
// console.log(d,d3.event.pageX,d3.event.pageY)
}
Insert cell
function onUnhover(d) {
const allPaths = d3.select(this.parentNode).selectAll('path')
const allCircles = d3.select(this.parentNode).selectAll('circle')
const filteredPaths = allPaths.filter(thisD => thisD !== d && thisD )
const filteredCircle = allCircles.filter(thisD =>thisD.id!==d.points[2].id )
filteredPaths.transition().duration(500).style('opacity', '1')
filteredCircle.transition().duration(500).style('opacity', '1')
div.transition()
.duration(500)
.style("opacity", 0);
// console.log(d,d3.event.pageX,d3.event.pageY)
}
Insert cell
// d3's line generator
line = d3
.line()
.x(function(d, i) {
return xScale(d.x)
}) // set the x values for the line generator
.y(function(d) {
return yScale(d.y)
}) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
Insert cell
// X scale will use the index of our data
xScale = d3
.scaleLinear()
.domain([0, maxAxis + 5]) // input
.range([margin.left, width - margin.right]) // output
Insert cell
// Y scale will use the randomly generate number
yScale = d3
.scaleLinear()
.domain([0, 30 + 5]) // input
.range([height - margin.bottom, margin.top]) // output
Insert cell
maxAxis=d3.max([maxDistance,maxHeight])
Insert cell
maxHeight=d3.max(data,d=>{return d.height})
Insert cell
maxDistance=d3.max(data,d=>{return d.distance})
Insert cell
// data Generator
data= rawData.map(obj => {
const distance = distanceCal(obj.angle, obj.speed)
const time = timeCal(obj.angle, obj.speed)
const height = heightCal(obj.angle, obj.speed)
// const getId=
const points = [
{id: obj.id, x: 0, y: obj.playerHeight },
{id: obj.id, x: distance / 2, y: height },
{id: obj.id, x: distance, y: 0 },
]
return {
distance,
height,
time,
points,
color: getColor(`${distance}`),
...obj,
}
})

Insert cell
// get d3 color
getColor = d3.scaleOrdinal(d3.schemeCategory10)
Insert cell
// calculate height in meter
heightCal = (angle, speed) => (Math.pow(speed, 2) / G ) * Math.pow(Math.sin(toRad(angle)), 2)
Insert cell
// calculate flight time in sec
timeCal =(angle, speed)=> (2 * speed * Math.sin(toRad(angle))) / G
Insert cell
// calculate distance in meter
distanceCal = (angle, speed)=> (Math.pow(speed, 2) / G ) * Math.sin(toRad(angle) * 2)
Insert cell
// convert degree to rad
toRad=angle=> (angle * Math.PI) / 180
Insert cell
// gravitational acceleration
G = 9.8
Insert cell
height = 300
Insert cell

// BaseBall bowling rawData
rawData = [
{id:0, speed: 40, angle: 10 , playerHeight:2.1},
{id:1, speed: 30, angle: 20 , playerHeight:1.7},
{id:2, speed: 28, angle: 20 , playerHeight:2.5},
{id:3, speed: 38, angle: 14 , playerHeight:1.6},
{id:4, speed: 45, angle: 9 , playerHeight:2.3},
{id:5, speed: 35, angle: 15 , playerHeight:1.8},
]
Insert cell
margin = ({ top: 20, right: 150, bottom: 30, left: 70 })
Insert cell
d3 = require("d3@5")
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