Created
May 29, 2025 03:01
-
-
Save aphix/83a799365f56c4fb8f514d19eb4e48db to your computer and use it in GitHub Desktop.
Karen Read Lexus Force Graph
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Lexus Impact Physics Visualization · Stratified Efficiency Overlay</title> | |
| <script src="https://d3js.org/d3.v7.min.js"></script> | |
| <style> | |
| body { font-family: Arial, sans-serif; background: #f4f4f4; } | |
| h2 { text-align: center; } | |
| .axis path, .axis line { fill: none; stroke: #000; } | |
| .region-sweet { fill: #90ee90; opacity: 0.6; } | |
| .region-shatter { fill: #ffffaa; opacity: 0.5; } | |
| .region-bone { fill: #ffa0a0; opacity: 0.6; } | |
| .curve { fill: none; stroke-width: 3; } | |
| .curve-2 { stroke: #204ecf; } | |
| .curve-5 { stroke: #30ae78; } | |
| .curve-10 { stroke: #d1771f; } | |
| .curve-20 { stroke: #f52276; } | |
| .curve-100 { stroke: #000; stroke-dasharray: 4 3; stroke-width: 2; } | |
| .legend rect { stroke: #666; stroke-width: 1; } | |
| .curve-label { | |
| font-size: 11px; font-weight: bold; text-shadow: 1px 1px #fff, -1px -1px #fff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h2>Lexus Impact: Force vs. Speed “Sweet Spot” (Stratified Efficiency)</h2> | |
| <svg id="chart" width="900" height="540"></svg> | |
| <div style="text-align: center;"> | |
| <small> | |
| Colors = outcome regions. Lines = impact force by energy transfer efficiency (\(\eta\)).<br> | |
| Dotted/black: 100% direct/perpendicular collision (theoretical max, highly unlikely).<br> | |
| Blue: 2% efficiency (very glancing, shallow angle). Green: 5%. Orange: 10%. Pink: 20%.<br> | |
| Legend shows typical biomechanical context for each angle/efficiency.<br> | |
| Zones, thresholds, and text as before. | |
| </small> | |
| </div> | |
| <script> | |
| const MS_TO_MPH = 2.23694; | |
| const MPH_TO_MS = 0.44704; | |
| // Fixed parameters | |
| const m_car = 2000; // kg | |
| const d = 0.03; // m deformation | |
| const F_bone = 3500; // N bone fracture | |
| const F_taillight = 500; // N tail light shatter | |
| const fatal_J = 50; // J | |
| const effs = [ | |
| {eta:0.02, color:"curve-2", label:"2% efficiency — glancing (10–30°)", y_off:40 }, | |
| {eta:0.05, color:"curve-5", label:"5% — moderate oblique (20–40°)", y_off:30 }, | |
| {eta:0.10, color:"curve-10", label:"10% — firm but not square (30–60°)", y_off:40 }, | |
| {eta:0.20, color:"curve-20", label:"20% — nearly direct (60–90°)", y_off:55 }, | |
| {eta:1.00, color:"curve-100", label:"100% — perfectly direct blow (theoretical)", y_off:105 } | |
| ]; | |
| const svg = d3.select("#chart"), | |
| margin = {top: 40, right: 90, bottom: 55, left: 75}, | |
| width = +svg.attr("width") - margin.left - margin.right, | |
| height = +svg.attr("height") - margin.top - margin.bottom; | |
| const g = svg.append("g").attr("transform",`translate(${margin.left},${margin.top})`); | |
| const mph_max = 35; | |
| const x = d3.scaleLinear().domain([0, mph_max]).range([0, width]); | |
| const y = d3.scaleLinear().domain([0, 8000]).range([height, 0]); | |
| let mphTicks = d3.range(0, mph_max+1, 5); | |
| g.append("g") | |
| .attr("transform",`translate(0,${height})`) | |
| .call(d3.axisBottom(x).tickValues(mphTicks)) | |
| .append("text") | |
| .attr("y", 40).attr("x", width/2) | |
| .attr("text-anchor","middle") | |
| .attr("fill","#000") | |
| .attr("font-weight", "bold") | |
| .text("Speed (mph)"); | |
| g.append("g") | |
| .call(d3.axisLeft(y).ticks(10)) | |
| .append("text") | |
| .attr("transform","rotate(-90)") | |
| .attr("y", -54) | |
| .attr("x", -height/2) | |
| .attr("text-anchor","middle") | |
| .attr("fill","#000") | |
| .attr("font-weight","bold") | |
| .text("Avg Impact Force (N)"); | |
| // For regions, use the "10%" (common blunt force) to shade | |
| function avForce(eta, mph) { | |
| let v = mph * MPH_TO_MS; | |
| return (eta * 0.5 * m_car * v * v) / d; | |
| } | |
| function avEnergy(eta, mph) { | |
| let v = mph * MPH_TO_MS; | |
| return eta * 0.5 * m_car * v * v; | |
| } | |
| const mphs = d3.range(0, mph_max+0.05, 0.05); | |
| let regions = []; | |
| for (let i=0; i<mphs.length-1; ++i) { | |
| let force_10 = avForce(0.10, mphs[i]), | |
| force_10p = avForce(0.10, mphs[i+1]), | |
| E_10 = avEnergy(0.10, mphs[i]), | |
| E_10p = avEnergy(0.10, mphs[i+1]); | |
| let type; | |
| if (force_10 >= F_bone || force_10p >= F_bone) | |
| type = "bone"; | |
| else if ((force_10>=F_taillight||force_10p>=F_taillight) && (E_10>=fatal_J || E_10p>=fatal_J)) | |
| type = "sweet"; | |
| else | |
| type = "shatter"; | |
| regions.push({ | |
| x0: x(mphs[i]), y0: y(force_10), | |
| x1: x(mphs[i+1]), y1: y(force_10p), | |
| type | |
| }); | |
| } | |
| regions.forEach(r=>{ | |
| if(r.type==="sweet") | |
| g.append("polygon").attr("points",`${r.x0},${r.y0} ${r.x1},${r.y1} ${r.x1},${height} ${r.x0},${height}`).attr("class","region-sweet"); | |
| if(r.type==="bone") | |
| g.append("polygon").attr("points",`${r.x0},${r.y0} ${r.x1},${r.y1} ${r.x1},${height} ${r.x0},${height}`).attr("class","region-bone"); | |
| if(r.type==="shatter") | |
| g.append("polygon").attr("points",`${r.x0},${r.y0} ${r.x1},${r.y1} ${r.x1},${height} ${r.x0},${height}`).attr("class","region-shatter"); | |
| }); | |
| // Draw threshold lines | |
| g.append("line") | |
| .attr("x1",0).attr("x2",width) | |
| .attr("y1",y(F_bone)).attr("y2",y(F_bone)) | |
| .attr("stroke", "#a00").attr("stroke-width", 2) | |
| .attr("stroke-dasharray", "8 3"); | |
| g.append("line") | |
| .attr("x1",0).attr("x2",width) | |
| .attr("y1",y(F_taillight)).attr("y2",y(F_taillight)) | |
| .attr("stroke", "#888800").attr("stroke-width", 2) | |
| .attr("stroke-dasharray", "8 3"); | |
| // Find speed for 50 J (at each efficiency) | |
| function mphForEnergyJ(eta, J) { | |
| let vms = Math.sqrt(J / (eta*0.5*m_car)); | |
| return vms * MS_TO_MPH; | |
| } | |
| let v_fatal = mphForEnergyJ(0.10, fatal_J); | |
| g.append("line") | |
| .attr("x1",x(v_fatal)).attr("x2",x(v_fatal)) | |
| .attr("y1",0).attr("y2",height) | |
| .attr("stroke", "#006") | |
| .attr("stroke-width", 2) | |
| .attr("stroke-dasharray", "8 3"); | |
| // Labels | |
| g.append("text") | |
| .attr("x", x(24)) | |
| .attr("y", y(F_bone)-8) | |
| .attr("fill", "#a00") | |
| .attr("font-size", "12px") | |
| .text("Bone Fracture Force (~3,500 N)"); | |
| g.append("text") | |
| .attr("x", x(3)) | |
| .attr("y", y(F_taillight)-8) | |
| .attr("fill", "#888800") | |
| .attr("font-size", "12px") | |
| .text("Tail Light Shatter Force (~500 N)"); | |
| g.append("text") | |
| .attr("x", x(v_fatal)+4) | |
| .attr("y", y(700)) | |
| .attr("fill", "#006") | |
| .attr("font-size", "12px") | |
| .attr("transform", `rotate(-90,${x(v_fatal)+4},${y(700)})`) | |
| .text("Potentially Fatal Energy (~50 J, η=10%)"); | |
| // Draw force curves for all efficiencies | |
| effs.forEach((e,ei)=>{ | |
| const curve = mphs.map(mph=>[x(mph), y(avForce(e.eta, mph))]); | |
| g.append("path") | |
| .attr("d", d3.line()(curve)) | |
| .attr("class", `curve ${e.color}`) | |
| .attr("fill","none") | |
| .attr("stroke-linecap","round") | |
| .attr("stroke-linejoin","round"); | |
| // Label | |
| let xm = x(32), ym = y(avForce(e.eta, 32)); | |
| g.append("text") | |
| .attr("x", xm+8) | |
| .attr("y", ym + e.y_off) | |
| .attr("class","curve-label") | |
| .attr("fill","black") | |
| .attr("stroke","white") | |
| .attr("paint-order","stroke") | |
| .style("pointer-events","none") | |
| .text(e.label); | |
| }); | |
| // Legend | |
| let legend = svg.append("g").attr("class","legend") | |
| .attr("transform",`translate(${width-30},${margin.top+15})`); | |
| legend.append("rect").attr("x",0).attr("y",0).attr("width",18).attr("height",18).attr("fill","#90ee90").attr("opacity",0.60); | |
| legend.append("text").attr("x",25).attr("y",13).text("Sweet Spot").style("font-size","12px"); | |
| legend.append("rect").attr("x",0).attr("y",22).attr("width",18).attr("height",18).attr("fill","#ffa0a0").attr("opacity",0.60); | |
| legend.append("text").attr("x",25).attr("y",35).text("Bone Fracture").style("font-size","12px"); | |
| legend.append("rect").attr("x",0).attr("y",44).attr("width",18).attr("height",18).attr("fill","#ffffaa").attr("opacity",0.60); | |
| legend.append("text").attr("x",25).attr("y",57).text("Other/Neither").style("font-size","12px"); | |
| let l2 = svg.append("g").attr("class","legend") | |
| .attr("transform",`translate(${width-25},${margin.top+92})`) | |
| .style("font-size","11.5px"); | |
| l2.append("line") | |
| .attr("x1",1).attr("x2",21).attr("y1",0).attr("y2",0) | |
| .attr("class","curve curve-2"); | |
| l2.append("text").attr("x",25).attr("y",4).text("2% (Shallow) "); | |
| l2.append("line") | |
| .attr("x1",1).attr("x2",21).attr("y1",18).attr("y2",18) | |
| .attr("class","curve curve-5"); | |
| l2.append("text").attr("x",25).attr("y",22).text("5% (Oblique) "); | |
| l2.append("line") | |
| .attr("x1",1).attr("x2",21).attr("y1",36).attr("y2",36) | |
| .attr("class","curve curve-10"); | |
| l2.append("text").attr("x",25).attr("y",40).text("10% (Typical Blunt)"); | |
| l2.append("line") | |
| .attr("x1",1).attr("x2",21).attr("y1",54).attr("y2",54) | |
| .attr("class","curve curve-20"); | |
| l2.append("text").attr("x",25).attr("y",58).text("20% (Near-Direct)"); | |
| l2.append("line") | |
| .attr("x1",1).attr("x2",21).attr("y1",72).attr("y2",72) | |
| .attr("class","curve curve-100"); | |
| l2.append("text").attr("x",25).attr("y",76).text("100% (Direct Hit)"); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment