G9.js: กราฟิกแบบโต้ตอบอัตโนมัติ
(omrelli.ug)- g9.js เป็นไลบรารีกราฟิกแบบโต้ตอบที่เชื่อมข้อมูลคีย์-ค่าที่เป็นตัวเลขเข้ากับฟังก์ชันเรนเดอร์ เมื่อผู้ใช้ลากองค์ประกอบกราฟิก ไลบรารีจะหาค่าข้อมูลใหม่แล้ววาดฉากทั้งหมดใหม่
- API หลักมีเพียง
g9(initialData, render[, onChange])โดยส่งinitialDataและคอนเท็กซ์สำหรับวาดให้กับrender(data, ctx)เพื่อวาดจุด เส้น วงกลม รูปภาพ และอื่นๆ - เมื่อเกิดการจัดการอย่างการลาก g9 จะ ปรับเหมาะ พื้นที่ของค่าข้อมูลที่เป็นไปได้ เพื่อหาค่าที่ใกล้เคียงกับการเปลี่ยนแปลงที่ตั้งใจที่สุด และหากจำเป็นจะส่งข้อมูลใหม่ผ่าน
onChange - แกลเลอรีแสดงตัวอย่างเป็นลำดับ ตั้งแต่การมิเรอร์จุดพื้นฐาน, เส้นโค้ง Bézier, วงกลมที่นิยามด้วยสามจุด, ลูกบาศก์, เส้นโค้งมังกร, รูปภาพ, นาฬิกา, LOGO Turtle, Grid Warp ไปจนถึง Crank and Piston
- สามารถใช้แพตเทิร์นอย่าง
affects,ctx.pure,getData,setData,isManipulating,Math.max/Math.minเพื่อกำหนดช่วงของตัวแปรที่จัดการได้ การเรนเดอร์ซ้ำ และการรับอินพุตผู้ใช้ระหว่างแอนิเมชัน
ลำดับการทำงานของ g9.js
- g9.js เปิดเผยฟังก์ชันเดียวคือ
g9(initialData, render[, onChange]) - ขั้นแรกสร้าง ข้อมูลคีย์-ค่า ที่มีค่าตัวเลข
- ตัวอย่างใช้วัตถุ
initialDataเช่นfoo: 10 - เอกสารที่เกี่ยวข้องอยู่ที่ initialData in the docs
- ตัวอย่างใช้วัตถุ
- ข้อมูลและคอนเท็กซ์สำหรับวาดจะถูกส่งไปยัง
render(data, ctx)และฟังก์ชันเรนเดอร์จะตัดสินใจว่าจะวาดอะไรตามค่าข้อมูล- ตัวอย่างวาดจุดด้วย
ctx.point(data.foo, 17) - เอกสารที่เกี่ยวข้องอยู่ที่ render in the docs
- ตัวอย่างวาดจุดด้วย
- เมื่อผู้ใช้ลากองค์ประกอบไปยังตำแหน่งใหม่ g9 จะหาค่าที่ทำให้เกิด การเปลี่ยนแปลงที่ใกล้ที่สุด ภายในพื้นที่ของค่าข้อมูลที่เป็นไปได้
- หากลากวงกลมไปทางซ้าย ในตัวอย่างอาจได้ข้อมูลใหม่อย่าง
{foo: 8}
- หากลากวงกลมไปทางซ้าย ในตัวอย่างอาจได้ข้อมูลใหม่อย่าง
- เมื่อกำหนดข้อมูลใหม่แล้ว จะเรนเดอร์ฉากทั้งหมดอีกครั้งเพื่อรักษาความสอดคล้อง
- หากระบุคอลแบ็ก
onChangeไว้ g9 จะส่งข้อมูลใหม่ให้ เพื่อให้สามารถอัปเดตส่วนอื่นของหน้าได้
- หากระบุคอลแบ็ก
- ดู API ทั้งหมดได้ใน Docs
รูปทรงพื้นฐานและข้อจำกัดของข้อมูล
- ตัวอย่าง Basic เป็นตัวอย่างขั้นต่ำที่ใช้เพียงสองจุด
- ฟังก์ชันเรนเดอร์วาดจุดหนึ่งที่
(x, y)และอีกจุดที่(y, x) - เมื่อลากจุดหนึ่ง อีกจุดจะเคลื่อนที่ราวกับมิเรอร์กัน
- ฟังก์ชันเรนเดอร์วาดจุดหนึ่งที่
- Rings นำข้อมูล
radiusและangleไปใส่ในฟังก์ชันตรีโกณมิติเพื่อจัดวางจุดเป็นวงกลมสองวง- จุดด้านในวาดเป็นสีแดง
- หากยกเลิกคอมเมนต์
ctx.line(...)ที่ถูกคอมเมนต์ไว้ จะสามารถเพิ่มเส้นระหว่างจุดของวงกลมทั้งสองได้
- The affects option แสดงตัวเลือก
affectsที่รูปทรงพื้นฐานของ g9 รับได้affectsระบุว่ารูปทรงสามารถเปลี่ยนส่วนใดของข้อมูลได้- เส้นสามเส้นใช้ค่า
affectsที่ต่างกัน - เส้นหนึ่งเปลี่ยนได้เฉพาะ
l2_start_x,l2_start_yส่วนอีกเส้นเปลี่ยนl3_start_x,l3_end_y
- Using Max and Min to limit a point's range of motion จำกัดช่วงการเคลื่อนที่ของจุดด้วย
Math.max(Math.min(...))- ตำแหน่งของจุดตรงกลางถูกจำกัดให้อยู่ระหว่าง
x1 + 20และx3 - 20 - จุดแยกอีกจุดแสดงค่า
data.x2ตามเดิม
- ตำแหน่งของจุดตรงกลางถูกจำกัดให้อยู่ระหว่าง
ตัวอย่างเส้นโค้ง เรขาคณิต และ 3D
- Bézier Curve เป็นตัวอย่างที่นำแอนิเมชัน quadratic Bézier curve จาก Wikipedia มาทำเป็นเวอร์ชันโต้ตอบ
- ใช้พิกัด
start,middle,endและค่าtเป็นข้อมูล - แสดงป้าย
tween=และประกอบเส้นโค้งที่ลื่นไหลจากส่วนของเส้นหลายขั้น - เส้นช่วยตรงกลางและส่วนของเส้นโค้งถูกกำหนด
affects: ['t']
- ใช้พิกัด
- Three points define a circle นิยามวงกลมด้วยสามจุด
- คำนวณส่วนของเส้นระหว่างจุดทั้งสาม จุดกึ่งกลาง และเส้นในแนวตั้งฉาก
- วางจุดที่จุดศูนย์กลางที่คำนวณได้ แล้ววาดวงกลมสีเขียวที่มีรัศมีเป็นระยะถึงจุดแรก
- cube สร้างจุดยอดและขอบของลูกบาศก์ แล้วผ่านการหมุน 3D การเลื่อนตำแหน่ง และการฉายภาพ จากนั้นวาดเป็นเส้น 2D
- คำนวณการหมุนแกนด้วยฟังก์ชัน
rx,ry,rz - ฟังก์ชัน
projectฉายภาพด้วยวิธี300 * p[0] / p[2],300 * p[1] / p[2] - ขอบใช้
affects: ['ax', 'ay']เพื่อให้เปลี่ยนค่าการหมุนได้ด้วยการลาก
- คำนวณการหมุนแกนด้วยฟังก์ชัน
แฟร็กทัล การวาดเชิงกระบวนการ และรูปภาพ
- dragon วาดเส้นโค้งมังกรด้วยฟังก์ชันเรียกซ้ำ
dragon(...)- ข้อมูลประกอบด้วย
squareness,fromX,fromY,toX,toY - ตัวเลือกของเส้นใช้
affects: ['squareness']เพื่อปรับรูปทรงของเส้นโค้ง - จุดเริ่มต้นและจุดสิ้นสุดแสดงเป็นจุดรัศมี
5
- ข้อมูลประกอบด้วย
- tree วาดต้นไม้แบบเรียกซ้ำ
- ข้อมูลคือ
deltaAngle,attenuation,startLength - ปลายกิ่งและเส้นใช้
affects: ['deltaAngle', 'attenuation'] - หาก
n > 3จะวาดเส้นเป็นสีน้ำตาล มิฉะนั้นเป็นสีเขียว - คอลแบ็ก
onChangeพิมพ์ข้อมูลที่เปลี่ยนแปลงลงคอนโซล
- ข้อมูลคือ
- LOGO Turtle simulator สร้างอินเทอร์เฟซ Turtle อย่างง่ายด้วย
make_turtle(ctx)และวาดเส้นซ้ำๆ- ข้อมูลคือ
rและtheta forwardเคลื่อนที่ไปตามทิศทางปัจจุบันพร้อมวาดเส้น และdturnแปลงการหมุนหน่วยองศาเป็นเรเดียน
- ข้อมูลคือ
- Grid Warp ใช้การแปลงที่ทำให้จุดในกริดบิดโค้ง
- ข้อมูลคือ
spinและscale - หลังคำนวณระยะ
rและมุมthetaของแต่ละจุดแล้ว จะเปลี่ยนปริมาณการหมุนด้วยtheta + data.spin * r - วาดจุดจากชุดค่าผสมของ
i,jตั้งแต่-10ถึง10
- ข้อมูลคือ
- image วาดรูปภาพด้วย
ctx.image- ระบุ URL รูปภาพ,
x,y, ความกว้าง200, ความสูง200
- ระบุ URL รูปภาพ,
กลไกเครื่องจักรและการโต้ตอบตามเวลา
- Kevin Clock วาดเป็นรูปนาฬิกาโดยใช้ข้อมูล
time- เข็มนาทีใช้
data.time / 60ส่วนเข็มวินาทีใช้data.timeเพื่อคำนวณปลายเข็มด้วยMath.cos,Math.sin setIntervalเพิ่มtimeทีละ0.01ทุก 50ms- อัปเดตเวลาด้วย
getData()และsetData(data)เฉพาะเมื่อgraphics.isManipulatingไม่เป็นจริง
- เข็มนาทีใช้
- Crank and Piston วาดโครงสร้างข้อเหวี่ยงและลูกสูบ
- ข้อมูลมีเพียง
time - สีของตัวเรือนเปลี่ยนตามค่า HSL ที่สร้างจาก
Math.cos(data.time) - วาดข้อเหวี่ยง เพลา และลูกสูบด้วยเส้น สี่เหลี่ยม และวงกลม
- บล็อกแอนิเมชันถูกคอมเมนต์ไว้ และเมื่อยกเลิกคอมเมนต์จะเพิ่ม
timeทีละ0.05ทุก 10ms
- ข้อมูลมีเพียง
- tongs สร้างรูปทรงคีมด้วยการหมุนและส่วนของเส้นซ้ำๆ
- ข้อมูลคือ
a,b - หมุนพิกัดด้วย
rotate(x, y, a)และวาดส่วนของเส้นสองแถวด้วยการทำซ้ำสี่ครั้ง - คอลแบ็ก
onChangeพิมพ์ข้อมูลที่เปลี่ยนแปลงลงคอนโซล
- ข้อมูลคือ
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เวลาหยิบโหนดที่เรนเดอร์อยู่ลึกลงไปสัก 5–6 ชั้นในต้นไม้แบบเรียกซ้ำแล้วขยับ แล้วส่วนที่เหลือของต้นไม้ตอบสนองตาม มันดู เหมือนเวทมนตร์ มาก
มันทำงานได้แม้ไม่มีฟังก์ชันที่อนุมานพารามิเตอร์ตั้งต้นของต้นไม้อย่างชัดเจนจากตำแหน่งของ leaf node ใด ๆ
อยากรู้ว่ามันถูกนำไปใช้งานอย่างไร และจะทำงานได้ดีแค่ไหนเมื่อจุดต่าง ๆ ไม่ได้เชื่อมโยงกันด้วยฟังก์ชันที่เรียบพอสมควร
หรือไม่ก็อาจโยนตัว optimizer สำเร็จรูปเข้าไปก็ได้
มีหลายวิธีในการโมเดลและแก้ปัญหา inverse kinematics
วิธีที่ยืดหยุ่นที่สุดในกลุ่มนี้มักหาเฉลยโดยประมาณด้วย iterative optimization เพราะสมการ forward kinematics แก้ย้อนกลับได้ยาก และปริภูมิของคำตอบก็อาจว่างเปล่าได้
แก่นของหลายวิธีคือการโมเดลสมการ forward kinematics ด้วย การกระจายอนุกรมเทย์เลอร์ ซึ่งอาจแก้กลับด้านได้ง่ายกว่าระบบเดิม
ตอนแรกคิดว่าเป็น API เรียบง่ายที่เจ๋งและสะอาดตา และถ้าจะทำงานแบบเดียวกันก็ชอบมากกว่า d3
แต่พออ่านว่า “เมื่อมีใครโต้ตอบกับกราฟิก เช่น พยายามลากองค์ประกอบไปยังตำแหน่งใหม่ g9 จะ optimize ในปริภูมิของค่าข้อมูลที่เป็นไปได้ เพื่อหาค่าที่ใกล้เคียงกับการเปลี่ยนแปลงนั้นที่สุด” แล้ว มันให้ความรู้สึกเหมือน การจัดการ DOM แบบเวทมนตร์ อยู่ไม่น้อย
แอปพลิเคชันที่ต้องแก้ข้อมูลด้วยการจัดการชาร์ตดูจะค่อนข้างเฉพาะทางเป็นส่วนใหญ่ แต่ถ้าเพื่อความสนุกแล้วใช้ได้ทั่วไป
ถ้ามี use case แบบนั้นเข้ามาคงสนุกมาก
สงสัยว่าได้ลองแตะตัวอย่างอื่น ๆ ในหน้านั้นด้วยตัวเองหรือยัง
ถ้าเฉลี่ยความเร็วกับเวกเตอร์จากสองสามเฟรมแรกของเหตุการณ์ลาก แล้วอัปเดตค่าเฉลี่ยต่อไปด้วยเฟรมที่มากขึ้น จากนั้นเริ่มขยับทุกอย่างไปยังจุดสิ้นสุดที่คาดการณ์ไว้ด้วย ease-in-out แบบนุ่มนวล ก็ใกล้เคียงกับ “optimize ในปริภูมิของค่าที่เป็นไปได้” พอสมควรแล้ว
ตอนที่เคยมีการคุยเรื่องนี้ใน HN มีคอมเมนต์ที่น่าสนใจอยู่: https://news.ycombinator.com/item?id=12572412
อยากเห็นคำอธิบายว่ามันทำงานอย่างไร สำหรับผมมันรู้สึกเหมือน มนตร์ดำ
เช่น มีจุดสองจุด จุดหนึ่งอยู่ที่ (0, 10) อีกจุดอยู่ที่ (10, 0) แล้วถ้าลากจุดหลังไปที่ (20, 0) ระบบรู้ได้อย่างไรว่าจุดแรกก็ควรถูกสะท้อนไปด้วยระยะเท่ากัน?
ย้ำอีกครั้งว่านี่คือเวทมนตร์ และคนทำทำได้ยอดเยี่ยมจริง ๆ
ทำ อนุพันธ์ย่อย ของฟังก์ชันนี้เทียบกับตัวแปรข้อมูล แล้ว minimize ฟังก์ชันเพื่อหาค่าข้อมูลใหม่ จากนั้นเรนเดอร์ทุกอย่างใหม่ด้วยค่าข้อมูลที่เหมาะที่สุดชุดใหม่
ที่อาจชวนสับสนเล็กน้อยคือสิ่งที่ปกติเราเรียกว่า “ข้อมูล” ในที่นี้ ในการ optimize เชิงคณิตศาสตร์อาจเรียกว่า พารามิเตอร์ และในสถิติ/แมชชีนเลิร์นนิงอาจเรียกว่า น้ำหนัก
ในแมชชีนเลิร์นนิง สิ่งที่คงที่จะถูกเรียกว่า “ข้อมูล” ดังนั้นในที่นี้ตำแหน่งขององค์ประกอบที่ถูกลากจึงเป็นข้อมูล
ดูเจ๋งทีเดียว
สิ่งหนึ่งที่เสียดายคือไม่มี มาร์กเกอร์หรือสัญลักษณ์แสดงการเลือก เลย ทำให้ถ้าไม่อ่านคำอธิบายก่อน ก็ไม่ค่อยชัดว่าวัตถุสามารถลากได้
วิธีปรับตำแหน่งวัตถุก็ชวนสับสนนิดหน่อย
ปกติเราคงกำหนด pointer handler แล้วแปลงตามอินพุต แต่ถ้าผมเข้าใจถูก เครื่องยนต์กำลังพยายามเดาให้ดีที่สุดเพื่อทำสิ่งนั้นให้อัตโนมัติใช่ไหม
เมื่อหลายปีก่อนผมเห็นสิ่งนี้แล้วตื่นเต้น เลยพอร์ตไปใช้ Pyodide เพื่อให้รันในเบราว์เซอร์ร่วมกับไลบรารีคล้าย PyTorch ได้: https://srush.github.io/g9py/
กระตุกกว่า JavaScript นิดหน่อย แต่แค่ที่มันทำงานได้ตั้งแต่แรกก็น่าทึ่งแล้ว
นี่ให้ความรู้สึกเหมือนคาถาแม่มด น่าประทับใจมาก
ดูเหมือนมีจุดร่วมกับเครื่องมือสเก็ตช์แบบอิงข้อจำกัดใน 2D CAD
ยังไม่ค่อยแน่ใจว่าควรเอาไปใช้กับอะไร แต่ทำออกมาได้ฉลาดจริง ๆ
วิธีมองปัญหาของมันต่างออกไปมาก จนผมคงต้องลองใช้กับอะไรสักอย่างเองเพื่อจับทางให้ได้
อาจมีประโยชน์มากในการสอน ตรีโกณมิติ หรือแนวคิดคณิตศาสตร์อื่น ๆ ในแวดวงการศึกษา
มีผู้เรียนสายภาพจำนวนมากที่มีปัญหาในการเชื่อมโยงสมการเข้ากับพื้นที่เชิงภาพ และการเพิ่มการโต้ตอบเข้าไปเล็กน้อยก็ช่วยให้เข้าใจแน่นขึ้นได้มาก