G9.js: Đồ họa tương tác tự động
(omrelli.ug)- g9.js là một thư viện đồ họa tương tác liên kết dữ liệu khóa-giá trị dạng số với hàm render; khi người dùng kéo các phần tử đồ họa, nó tìm ra giá trị dữ liệu mới rồi vẽ lại toàn bộ khung cảnh
- API cốt lõi chỉ có
g9(initialData, render[, onChange]); nó truyềninitialDatavà ngữ cảnh vẽ vàorender(data, ctx)để vẽ điểm, đường thẳng, hình tròn, ảnh, v.v. - Khi có thao tác như kéo thả, g9 sẽ tối ưu hóa trong không gian các giá trị dữ liệu khả dĩ để tìm giá trị gần nhất với thay đổi mong muốn, và nếu cần thì truyền dữ liệu mới qua
onChange - Phần gallery trình bày dần các ví dụ từ phản chiếu điểm cơ bản đến đường cong Bézier, đường tròn xác định bởi ba điểm, khối lập phương, đường cong rồng, ảnh, đồng hồ, LOGO Turtle, Grid Warp và Crank and Piston
- Có thể tổ chức phạm vi biến có thể thao tác, re-render và xử lý đầu vào người dùng trong lúc animation bằng các mẫu như
affects,ctx.pure,getData,setData,isManipulating,Math.max/Math.min
Luồng hoạt động của g9.js
- g9.js cung cấp một hàm duy nhất là
g9(initialData, render[, onChange]) - Trước tiên tạo dữ liệu khóa-giá trị gồm các giá trị số
- Ví dụ dùng đối tượng
initialDatanhưfoo: 10 - Tài liệu liên quan có tại initialData in the docs
- Ví dụ dùng đối tượng
- Dữ liệu và ngữ cảnh vẽ được truyền vào
render(data, ctx), và hàm render quyết định sẽ vẽ gì dựa trên các giá trị dữ liệu- Ví dụ vẽ một điểm bằng
ctx.point(data.foo, 17) - Tài liệu liên quan có tại render in the docs
- Ví dụ vẽ một điểm bằng
- Khi người dùng kéo một phần tử sang vị trí mới, g9 sẽ tìm giá trị trong không gian dữ liệu khả dĩ tạo ra thay đổi gần nhất
- Nếu kéo hình tròn sang trái, trong ví dụ có thể sinh ra dữ liệu mới như
{foo: 8}
- Nếu kéo hình tròn sang trái, trong ví dụ có thể sinh ra dữ liệu mới như
- Khi dữ liệu mới được xác định, toàn bộ khung cảnh sẽ được render lại để giữ tính nhất quán
- Nếu có cung cấp callback
onChange, g9 sẽ truyền dữ liệu mới để có thể cập nhật các phần khác của trang
- Nếu có cung cấp callback
- Toàn bộ API có thể xem trong Docs
Hình cơ bản và ràng buộc dữ liệu
- Ví dụ Basic là ví dụ tối thiểu chỉ dùng hai điểm
- Hàm render vẽ một điểm tại
(x, y)và điểm còn lại tại(y, x) - Khi kéo một điểm, điểm kia sẽ di chuyển như thể phản chiếu lẫn nhau
- Hàm render vẽ một điểm tại
- Rings đưa dữ liệu
radiusvàanglevào các hàm lượng giác để bố trí các điểm thành hai vòng tròn- Các điểm bên trong được vẽ màu đỏ
- Nếu bỏ chú thích
ctx.line(...)thì có thể thêm các đường nối giữa các điểm của hai vòng tròn
- The affects option cho thấy tùy chọn
affectsmà các hình cơ bản của g9 hỗ trợaffectschỉ định một hình có thể thay đổi phần nào của dữ liệu- Ba đường thẳng sử dụng các giá trị
affectskhác nhau - Một đường chỉ thay đổi
l2_start_x,l2_start_y, còn đường khác thay đổil3_start_x,l3_end_y
- Using Max and Min to limit a point's range of motion giới hạn phạm vi di chuyển của điểm bằng
Math.max(Math.min(...))- Vị trí của điểm ở giữa bị giới hạn trong khoảng từ
x1 + 20đếnx3 - 20 - Một điểm riêng biệt hiển thị nguyên giá trị
data.x2
- Vị trí của điểm ở giữa bị giới hạn trong khoảng từ
Ví dụ về đường cong, hình học và 3D
- Bézier Curve là ví dụ biến hoạt ảnh đường cong Bézier bậc hai trên Wikipedia thành phiên bản tương tác
- Dữ liệu dùng các tọa độ
start,middle,endvà giá trịt - Nó hiển thị nhãn
tween=và tạo đường cong mượt bằng nhiều đoạn thẳng theo từng bước - Các đường phụ trung gian và các đoạn của đường cong có gắn
affects: ['t']
- Dữ liệu dùng các tọa độ
- Three points define a circle xác định một đường tròn bằng ba điểm
- Nó tính các đoạn thẳng giữa ba điểm, trung điểm và các đường theo phương vuông góc
- Nó đặt một điểm tại tâm đã tính được và vẽ một đường tròn màu xanh lá với bán kính là khoảng cách tới điểm đầu tiên
- cube tạo các đỉnh và cạnh của khối lập phương rồi vẽ chúng thành các đường 2D sau khi xoay, tịnh tiến và chiếu 3D
- Các hàm
rx,ry,rztính phép quay theo trục - Hàm
projectchiếu theo cách300 * p[0] / p[2],300 * p[1] / p[2] - Các cạnh dùng
affects: ['ax', 'ay']để thay đổi giá trị góc quay bằng thao tác kéo
- Các hàm
Fractal, vẽ thủ tục và hình ảnh
- dragon vẽ đường cong rồng bằng hàm đệ quy
dragon(...)- Dữ liệu gồm
squareness,fromX,fromY,toX,toY - Tùy chọn đường thẳng dùng
affects: ['squareness']để điều khiển hình dạng đường cong - Điểm bắt đầu và điểm kết thúc được hiển thị bằng các điểm có bán kính
5
- Dữ liệu gồm
- tree vẽ cây theo cách đệ quy
- Dữ liệu gồm
deltaAngle,attenuation,startLength - Đầu cành và các đường thẳng dùng
affects: ['deltaAngle', 'attenuation'] - Nếu
n > 3thì vẽ đường màu nâu, ngược lại vẽ màu xanh lá - Callback
onChangein dữ liệu đã thay đổi ra console
- Dữ liệu gồm
- LOGO Turtle simulator tạo một giao diện Turtle đơn giản bằng
make_turtle(ctx)và lặp lại việc vẽ đường- Dữ liệu gồm
rvàtheta forwarddi chuyển theo hướng hiện tại đồng thời vẽ đường, còndturnđổi góc quay theo độ sang radian
- Dữ liệu gồm
- Grid Warp áp dụng một phép biến đổi làm bẻ cong các điểm trên lưới
- Dữ liệu gồm
spinvàscale - Sau khi tính khoảng cách
rvà gócthetacủa mỗi điểm, nó thay đổi lượng quay bằngtheta + data.spin * r - Nó vẽ các điểm với mọi tổ hợp
i,jtừ-10đến10
- Dữ liệu gồm
- image vẽ ảnh bằng
ctx.image- Nó chỉ định URL ảnh,
x,y, chiều rộng200, chiều cao200
- Nó chỉ định URL ảnh,
Cơ cấu cơ khí và tương tác theo thời gian
- Kevin Clock vẽ một chiếc đồng hồ bằng dữ liệu
time- Kim phút dùng
data.time / 60, còn kim giây dùngdata.timeđể tính điểm cuối bằngMath.cos,Math.sin setIntervaltăngtimethêm0.01mỗi 50ms- Chỉ khi
graphics.isManipulatingkhông phải true thì thời gian mới được cập nhật bằnggetData()vàsetData(data)
- Kim phút dùng
- Crank and Piston vẽ cơ cấu tay quay và pít-tông
- Dữ liệu chỉ có một giá trị là
time - Màu của vỏ máy thay đổi theo giá trị HSL tạo bằng
Math.cos(data.time) - Nó vẽ tay quay, trục và pít-tông bằng đường thẳng, hình chữ nhật và hình tròn
- Khối animation đang bị chú thích; nếu bỏ chú thích, nó sẽ tăng
timethêm0.05mỗi 10ms
- Dữ liệu chỉ có một giá trị là
- tongs tạo hình chiếc kẹp bằng phép quay và các đoạn thẳng lặp lại
- Dữ liệu gồm
a,b rotate(x, y, a)xoay tọa độ, và qua bốn lần lặp nó vẽ hai hàng đoạn thẳng- Callback
onChangein dữ liệu đã thay đổi ra console
- Dữ liệu gồm
1 bình luận
Ý kiến trên Hacker News
Khi nhấc một nút được render ở độ sâu khoảng 5–6 tầng trong cây đệ quy rồi di chuyển nó, việc phần còn lại của cây phản ứng trông như phép màu
Nó vẫn hoạt động dù không có hàm nào suy luận tường minh các tham số ban đầu của cây từ vị trí của một nút lá tùy ý
Tôi tò mò không biết nó được triển khai thế nào, và hoạt động tốt đến đâu khi vị trí các điểm không được nối với nhau bằng một hàm đủ trơn tru
Hoặc cũng có thể chỉ cần ném vào một bộ tối ưu hóa có sẵn là được
Có nhiều cách để mô hình hóa và giải bài toán inverse kinematics
Những cách linh hoạt nhất trong số đó thường tìm nghiệm xấp xỉ bằng tối ưu hóa lặp, vì khó giải ngược các phương trình forward kinematics và không gian nghiệm cũng có thể rỗng
Cốt lõi của nhiều phương pháp là mô hình hóa các phương trình forward kinematics bằng khai triển chuỗi Taylor, thứ có thể dễ đảo ngược để giải hơn hệ ban đầu
Ban đầu tôi nghĩ đây là một API đơn giản, hay và gọn gàng; nếu làm cùng một việc thì tôi thích nó hơn d3
Nhưng sau khi đọc câu “khi ai đó tương tác với đồ họa, ví dụ cố kéo một phần tử sang vị trí mới, g9 sẽ tối ưu hóa trong không gian các giá trị dữ liệu khả dĩ để tìm giá trị gần nhất với thay đổi đó”, tôi thấy đây giống một kiểu thao tác DOM có tính ma thuật khá rõ
Phần lớn ứng dụng cần thao tác biểu đồ để thay đổi dữ liệu có vẻ khá đặc thù, nhưng dùng để tạo sự thú vị thì lại rất phổ biến
Nếu gặp đúng use case như vậy thì chắc sẽ rất vui
Tôi tò mò không biết bạn đã thử trực tiếp các ví dụ khác trên trang chưa
Nếu trong vài frame đầu của sự kiện kéo, bạn lấy trung bình tốc độ và vector, tiếp tục cập nhật trung bình với nhiều frame hơn, rồi bắt đầu di chuyển mọi thứ tới điểm kết thúc dự đoán bằng ease-in-out mượt mà, thì cũng đã khá gần với “tối ưu hóa trong không gian các giá trị khả dĩ” rồi
Hồi trước khi cái này được thảo luận trên HN đã có vài bình luận thú vị: https://news.ycombinator.com/item?id=12572412
Tôi muốn xem phần giải thích nó hoạt động thế nào. Với tôi nó giống ma thuật hắc ám
Ví dụ có hai điểm, một điểm ở (0, 10), điểm kia ở (10, 0); nếu kéo điểm sau tới (20, 0), làm sao hệ thống biết rằng điểm đầu cũng phải được phản chiếu cùng một khoảng cách như vậy?
Nói lại lần nữa, đây là phép màu, và người tạo ra nó đã làm rất tốt
Lấy đạo hàm riêng của hàm này theo các biến dữ liệu, tối thiểu hóa hàm để tìm giá trị dữ liệu mới, rồi render lại toàn bộ với giá trị dữ liệu tối ưu mới
Lý do hơi dễ gây nhầm là thứ ở đây thường được gọi là “dữ liệu” thì trong tối ưu hóa toán học lại đáng gọi là tham số, còn trong thống kê/machine learning là trọng số
Trong machine learning, thứ cố định mới được gọi là “dữ liệu”, nên ở đây vị trí của phần tử bị kéo mới tương ứng với dữ liệu
Trông khá ngầu
Một điểm hơi tiếc là không có marker hay dấu hiệu chọn, nên nếu chưa đọc phần mô tả trước thì không rõ lắm là có thể kéo các đối tượng
Cách điều chỉnh lại vị trí đối tượng cũng hơi khó hiểu
Thông thường ta sẽ định nghĩa pointer handler rồi biến đổi theo input, nhưng nếu tôi hiểu đúng thì có vẻ engine đang tự động hóa việc đó bằng phỏng đoán tốt nhất của nó
Vài năm trước tôi thấy cái này và hào hứng đến mức đã port nó sang Pyodide để chạy trong trình duyệt cùng một thư viện na ná PyTorch: https://srush.github.io/g9py/
Nó hơi giật hơn JavaScript một chút, nhưng việc nó chạy được ngay từ đầu đã đáng kinh ngạc rồi
Cái này giống phù thủy thuật. Cực kỳ ấn tượng
Có vẻ có điểm chung với các công cụ phác thảo dựa trên ràng buộc trong 2D CAD
Tôi không rõ nên dùng nó vào đâu, nhưng nó được làm rất thông minh
Cách nhìn vấn đề khác quá nhiều, nên chắc tôi phải tự dùng nó vào thứ gì đó để nắm được cảm giác
Nó có thể rất hữu ích trong giáo dục để truyền đạt lượng giác hoặc các khái niệm toán học khác
Có nhiều người học thiên về thị giác gặp khó khăn khi ánh xạ công thức vào không gian trực quan; thêm một chút tương tác sẽ giúp củng cố hiểu biết rất nhiều