8 คะแนน โดย GN⁺ 2023-08-10 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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

กลไกเครื่องจักรและการโต้ตอบตามเวลา

  • 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 ความคิดเห็น

 
GN⁺ 2023-08-10
ความคิดเห็นจาก Hacker News
  • เวลาหยิบโหนดที่เรนเดอร์อยู่ลึกลงไปสัก 5–6 ชั้นในต้นไม้แบบเรียกซ้ำแล้วขยับ แล้วส่วนที่เหลือของต้นไม้ตอบสนองตาม มันดู เหมือนเวทมนตร์ มาก
    มันทำงานได้แม้ไม่มีฟังก์ชันที่อนุมานพารามิเตอร์ตั้งต้นของต้นไม้อย่างชัดเจนจากตำแหน่งของ leaf node ใด ๆ
    อยากรู้ว่ามันถูกนำไปใช้งานอย่างไร และจะทำงานได้ดีแค่ไหนเมื่อจุดต่าง ๆ ไม่ได้เชื่อมโยงกันด้วยฟังก์ชันที่เรียบพอสมควร

    • เดาว่าน่าจะใช้การสุ่มตัวอย่างฟังก์ชันสองครั้งเพื่อหา อนุพันธ์ย่อย ของผลกระทบที่แต่ละพารามิเตอร์มีต่อวัตถุที่ถูกขยับ แล้วทำ gradient descent แบบธรรมดา จากนั้นทำซ้ำจนกว่าจะลู่เข้า
      หรือไม่ก็อาจโยนตัว optimizer สำเร็จรูปเข้าไปก็ได้
    • น่าจะคล้ายกับ inverse kinematics
      มีหลายวิธีในการโมเดลและแก้ปัญหา inverse kinematics
      วิธีที่ยืดหยุ่นที่สุดในกลุ่มนี้มักหาเฉลยโดยประมาณด้วย iterative optimization เพราะสมการ forward kinematics แก้ย้อนกลับได้ยาก และปริภูมิของคำตอบก็อาจว่างเปล่าได้
      แก่นของหลายวิธีคือการโมเดลสมการ forward kinematics ด้วย การกระจายอนุกรมเทย์เลอร์ ซึ่งอาจแก้กลับด้านได้ง่ายกว่าระบบเดิม
  • ตอนแรกคิดว่าเป็น API เรียบง่ายที่เจ๋งและสะอาดตา และถ้าจะทำงานแบบเดียวกันก็ชอบมากกว่า d3
    แต่พออ่านว่า “เมื่อมีใครโต้ตอบกับกราฟิก เช่น พยายามลากองค์ประกอบไปยังตำแหน่งใหม่ g9 จะ optimize ในปริภูมิของค่าข้อมูลที่เป็นไปได้ เพื่อหาค่าที่ใกล้เคียงกับการเปลี่ยนแปลงนั้นที่สุด” แล้ว มันให้ความรู้สึกเหมือน การจัดการ DOM แบบเวทมนตร์ อยู่ไม่น้อย
    แอปพลิเคชันที่ต้องแก้ข้อมูลด้วยการจัดการชาร์ตดูจะค่อนข้างเฉพาะทางเป็นส่วนใหญ่ แต่ถ้าเพื่อความสนุกแล้วใช้ได้ทั่วไป
    ถ้ามี use case แบบนั้นเข้ามาคงสนุกมาก

    • ไม่ใช่สำหรับชาร์ต แต่สำหรับ เดโมแบบอินเทอร์แอคทีฟ
      สงสัยว่าได้ลองแตะตัวอย่างอื่น ๆ ในหน้านั้นด้วยตัวเองหรือยัง
    • ถ้ามองในแง่การประยุกต์ใช้ มี แนวคิด STEM จำนวนมากที่สื่อสารได้อย่างมีประสิทธิภาพเมื่อเปิดให้โต้ตอบได้
    • ผมคงไม่เรียกมันว่าเป็นวิทยาศาสตร์ขั้นสูง
      ถ้าเฉลี่ยความเร็วกับเวกเตอร์จากสองสามเฟรมแรกของเหตุการณ์ลาก แล้วอัปเดตค่าเฉลี่ยต่อไปด้วยเฟรมที่มากขึ้น จากนั้นเริ่มขยับทุกอย่างไปยังจุดสิ้นสุดที่คาดการณ์ไว้ด้วย ease-in-out แบบนุ่มนวล ก็ใกล้เคียงกับ “optimize ในปริภูมิของค่าที่เป็นไปได้” พอสมควรแล้ว
  • ตอนที่เคยมีการคุยเรื่องนี้ใน HN มีคอมเมนต์ที่น่าสนใจอยู่: https://news.ycombinator.com/item?id=12572412

  • อยากเห็นคำอธิบายว่ามันทำงานอย่างไร สำหรับผมมันรู้สึกเหมือน มนตร์ดำ
    เช่น มีจุดสองจุด จุดหนึ่งอยู่ที่ (0, 10) อีกจุดอยู่ที่ (10, 0) แล้วถ้าลากจุดหลังไปที่ (20, 0) ระบบรู้ได้อย่างไรว่าจุดแรกก็ควรถูกสะท้อนไปด้วยระยะเท่ากัน?
    ย้ำอีกครั้งว่านี่คือเวทมนตร์ และคนทำทำได้ยอดเยี่ยมจริง ๆ

    • นิยามฟังก์ชันที่วัด error ระหว่างตำแหน่งขององค์ประกอบที่วาดกับตำแหน่งใหม่ที่การลากต้องการ
      ทำ อนุพันธ์ย่อย ของฟังก์ชันนี้เทียบกับตัวแปรข้อมูล แล้ว minimize ฟังก์ชันเพื่อหาค่าข้อมูลใหม่ จากนั้นเรนเดอร์ทุกอย่างใหม่ด้วยค่าข้อมูลที่เหมาะที่สุดชุดใหม่
      ที่อาจชวนสับสนเล็กน้อยคือสิ่งที่ปกติเราเรียกว่า “ข้อมูล” ในที่นี้ ในการ optimize เชิงคณิตศาสตร์อาจเรียกว่า พารามิเตอร์ และในสถิติ/แมชชีนเลิร์นนิงอาจเรียกว่า น้ำหนัก
      ในแมชชีนเลิร์นนิง สิ่งที่คงที่จะถูกเรียกว่า “ข้อมูล” ดังนั้นในที่นี้ตำแหน่งขององค์ประกอบที่ถูกลากจึงเป็นข้อมูล
    • คณิตศาสตร์ที่ก้าวหน้าพอแยกไม่ออกจากเวทมนตร์ และในที่นี้เกณฑ์ของคำว่า “ก้าวหน้าพอ” ต่ำกว่าที่คิดมาก
  • ดูเจ๋งทีเดียว
    สิ่งหนึ่งที่เสียดายคือไม่มี มาร์กเกอร์หรือสัญลักษณ์แสดงการเลือก เลย ทำให้ถ้าไม่อ่านคำอธิบายก่อน ก็ไม่ค่อยชัดว่าวัตถุสามารถลากได้
    วิธีปรับตำแหน่งวัตถุก็ชวนสับสนนิดหน่อย
    ปกติเราคงกำหนด pointer handler แล้วแปลงตามอินพุต แต่ถ้าผมเข้าใจถูก เครื่องยนต์กำลังพยายามเดาให้ดีที่สุดเพื่อทำสิ่งนั้นให้อัตโนมัติใช่ไหม

  • เมื่อหลายปีก่อนผมเห็นสิ่งนี้แล้วตื่นเต้น เลยพอร์ตไปใช้ Pyodide เพื่อให้รันในเบราว์เซอร์ร่วมกับไลบรารีคล้าย PyTorch ได้: https://srush.github.io/g9py/
    กระตุกกว่า JavaScript นิดหน่อย แต่แค่ที่มันทำงานได้ตั้งแต่แรกก็น่าทึ่งแล้ว

  • นี่ให้ความรู้สึกเหมือนคาถาแม่มด น่าประทับใจมาก

  • ดูเหมือนมีจุดร่วมกับเครื่องมือสเก็ตช์แบบอิงข้อจำกัดใน 2D CAD

  • ยังไม่ค่อยแน่ใจว่าควรเอาไปใช้กับอะไร แต่ทำออกมาได้ฉลาดจริง ๆ

    • โดยศักยภาพแล้วน่าจะใช้กับ อินเทอร์เฟซลากแล้วปล่อย แบบไหนก็ได้
      วิธีมองปัญหาของมันต่างออกไปมาก จนผมคงต้องลองใช้กับอะไรสักอย่างเองเพื่อจับทางให้ได้
  • อาจมีประโยชน์มากในการสอน ตรีโกณมิติ หรือแนวคิดคณิตศาสตร์อื่น ๆ ในแวดวงการศึกษา
    มีผู้เรียนสายภาพจำนวนมากที่มีปัญหาในการเชื่อมโยงสมการเข้ากับพื้นที่เชิงภาพ และการเพิ่มการโต้ตอบเข้าไปเล็กน้อยก็ช่วยให้เข้าใจแน่นขึ้นได้มาก