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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more