Code Weekly Downloads Chart trên npmjs.com
Bài đăng này đã không được cập nhật trong 3 năm
Mở đầu
Là một lập trình viên Web chắc hẳn ai cũng biết đến và từng truy cập website npmjs.com để khám phá, tìm hiểu (lấy link install ) các thư viện Javascript. Và mỗi lần truy cập mình điều thấy khá thích thú với biểu đồ Weekly Downloads, vậy nên mình sẽ cùng với các bạn tìm hiểu cách tạo ra một component tương tự nhé
Công nghệ mình dùng cho project là ReactJS và SVG
Nội dung
Data
data = [
{
downloads: 111154,
label: '2020-07-12 to 2020-07-18',
},
{
downloads: 111834,
label: '2020-07-19 to 2020-07-25',
},
...
]
Mình sử dụng định dạng data như trên, đấy cũng là dạng dữ liệu mà mà component của npmjs.com sử dụng (nhưng mình tìm toét cả mắt ở tag Network thì không thấy responce nào trả về như thế ai tìm được chỉ mình với nha)
Phần biểu đồ tim tím
const WIDTH = 500
const HEIGHT = 100
// ...
<svg width={WIDTH} height={HEIGHT}>
<path d={pathD1} fill='none'></path>
<path d={pathD2} stroke='none'></path>
<line x1={pointSelected.x} x2={pointSelected.x} y1={0} y2={HEIGHT}></line>
<circle cx={pointSelected.x} cy={pointSelected.y}></circle>
<rect
width={WIDTH}
height={HEIGHT}
onMouseMove={handleMouseMove}
onMouseOut={handleMouseOut}
></rect>
</svg>
// ...
Bên trong thẻ svg
mình dùng một số thẻ nữa với chức năng như sau
- thẻ
path
đầu tiên để vẽ đường xanh tím này - thẻ
path
thứ high để vẽ hình tím tím này - thẻ
line
vàcircle
để vẽ hình này khi ta hover trên biểu đồ - còn thẻ
rect
để ta bắt sự kiệnonMouseMove
vàonMouseOut
khi hover chuột để xem chi tiết lượt download của từng tuần
Path & Path
Phần quan trọng nhất là làm sao chúng ta có thể tính được pathD để vẽ được biểu đồ trên bằng dữ liệu bên trên. Mỗi tuần mình sẽ cho là 1 điểm có tọa độ x , y , số lượt downloads, và label là thời gian tuần đó {x, y, downloads, label}
.Đầu tiên chúng ta sẽ tính toán tọa độ các điểm:
const WIDTH = 500
const HEIGHT = 100
const { min, max } = findMinMax(data) // tìm giá trị download min và max (findMinMax là mình tự viết)
const deltaX = WIDTH / (data.length - 1) // tính khoảng cách theo trục X giữa 2 điểm
const deltaY = 60 / (max - min) // ở đây chiều cao chart của mình là 100px mình muốn tuần có
//lượt download lớn nhất ở tọa độ Y 0 tuần có lượt download thấp nhất ở tọa độ Y 60
//nên mình tính deltaY theo từng lượt download như trên
//đây là phầm tính tọa độ các điểm
let points = []
for (const [index, value] of data.entries()) {
points.push({
x: index * deltaX,
y: (max - value.downloads) * deltaY + 8, // tất cả +8 để cho đẹp chút thôi
downloads: value.downloads,
label: value.label,
})
}
Tiếp theo ta sẽ tính biến pathD. ở đây mình dùng attribute d
của thẻ path
, nó sẽ bao gồm một dãy các kí tự trong đó có một chữ cái đi kèm tọa độ x y
. Ở đây mình dùng những kí tự đơn giản sau (khó mình không biết )
M 20 40
: luôn bắt đầu vớiM
để di chuyển bút vẽ đến tọa{20,40}
L 40 60
: vẽ 1 đường thẳng từ điểm trước đó của bút đến tọa độ{40,60}
Z
: đóng lại đường đã vẽ thành một hình (màu trong hình được định nghĩa bằng attributefill
)
let pathD = `M ${points[0].x} ${points[0].y} `
for (const point of points) {
pathD += `L ${point.x} ${point.y} `
}
const pathD1 = pathD
const pathD2 = `${pathD} L ${WIDTH} ${HEIGHT} L 0 ${HEIGHT} Z`
Vậy là phần khó đã xong (style mình sẽ để riêng 1 file scss sau)
Bắt sự kiện Mouse và show thông tin chi tiết
//...
const defaultPoint = { // giá trị mặc định của điểm là không hiển thị trên svg
x: -1000,
y: -1000,
downloads: data[data.length - 1].downloads,
label: 'Weekly Downloads',
}
function Chart() {
const [pointSelected, setPointSelected] = useState(defaultPoint)
const handleMouseMove = (e) => {
let index = Math.round(e.nativeEvent.offsetX / deltaX)
// xác định tọa độ theo trục X chuột đang di chuyển trên để tìm point
setPointSelected(points[index])
}
const handleMouseOut = (e) => {
setPointSelected(defaultPoint)
}
return (
<div className='chart__container'>
<h1>{pointSelected.label}</h1>
<div className='content'>
<div className='num'>{Intl.NumberFormat().format(pointSelected.downloads)}</div>
// format lượt download dạng ###,###,###
<div className='chart'>
<svg width={WIDTH} height={HEIGHT}>
<path d={pathD1} fill='none'></path>
<path d={pathD2} stroke='none'></path>
<line x1={pointSelected.x} x2={pointSelected.x} y1={0} y2={HEIGHT}></line>
<circle cx={pointSelected.x} cy={pointSelected.y}></circle>
// các thuộc tính x1,x2 của line, cx, cy, r của circle các bạn tự tìm hiểu thêm nha
<rect
width={WIDTH}
height={HEIGHT}
onMouseMove={handleMouseMove}
onMouseOut={handleMouseOut}
></rect>
</svg>
</div>
</div>
</div>
)
}
Vậy là được rồi đấy các bạn
Đây là phần demo
Kết luận
Đây là bài viểt debut của mình, có gì sai sót mong các bạn, các anh chị góp ý
Hi vọng với chút chia sẻ này sẽ giúp đỡ được mọi người, đặc biệt là các bạn mới để cộng đồng DEV Việt Nam ngày càng phát triển!
All rights reserved