Recently made a requirement, one of the scenarios is to draw a radar map, looking for a circle, it seems that AntV under the F2 is very suitable for use:
But then consider for a moment, my current project is not visualized, the next big probability demand also won’t have this kind of visualization, just for one or two individual demand chart is the introduction of a visual library, price is a little low, hard to optimize code size, just because of a redundant code base a while back to before liberation, That’s really bad (although F2 is pretty streamlined)
In addition, I happen to be interested in canvas, so I can’t miss the opportunity to practice my skills. Besides, I felt a bit painful after reading the F2 document. If I have time to read this document, I would rather read the Canvas native Api directly, so I decided to do it myself
Draw polygons
This radar diagram looks pretty simple, but it’s actually a little tricky. I thought about it and divided it into three parts:
- Regular polygon
- Regular polygon vertex text
- Radar area
Polygons are shown as follows (regular pentagons are used as an example) :
This figure is actually composed of a number of regular pentagons with different sizes nested, and the diagonal vertices of the regular pentagons are connected together through 5 lines. The key is to know the coordinates of the five vertices of the regular pentagons, which is actually solving geometric math problems
As shown in the figure, rotate the regular pentagon so that its center point is at the origin of the coordinate axis, its leftmost edge is parallel to the Y-axis, and its maximum x-coordinate point (i.e. the right-most point) in this state is on the X-axis. Then draw an outer circle of the regular pentagon, and then you can solve it
The reason why you rotate the regular pentagons in this way is just to make it easier to solve for coordinates, but you can also make one side of the regular pentagons parallel to the X-axis or any other rotation, as long as you get the relative coordinates of the vertices of the regular polygon
It is obvious that the coordinates of each vertex of a regular pentagon are:
(Radius cos theta, radius sin theta)Copy the code
Here radius is the radius of the regular pentagonal circumferential circle, and θ is the Angle between the vertex and the origin and x
Radius is defined by ourselves, and only the solution of θ is left. According to the figure above, if the right-most vertex of the regular polygon (i.e., the maximum x coordinate) is the first vertex, the counterclockwise rotation is the second and third in turn… n
PI * 2 / n = math. PI * 2 / n = math. PI * 2 / n = math. PI * 2 / n
(radius * cos (theta * (n - 1)), the radius * sin (theta * (n - 1)))Copy the code
I’m just taking a regular pentagon as an example, and that’s what happens when you expand to a regular n-sided shape
Vertex, got the regular polygon is very good picture, but it is also need to note that the actual demand, the general is to require regular polygon is dish placed, the bottom edge parallel to the x, and according to the article here, the way of solving the vertex coordinates draw regular polygon, is parallel to the y side, so you need to get the regular polygon coordinate mapping, Convert it to upright
Rotate also provides this operation on canvas, which is called rotate. As long as the canvas coordinate system is rotated first or then, the drawn polygon can be visually placed in front
function drawPolygon () {
// #region draw polygons
const r = mRadius / polygonCount
let currentRadius = 0
for (let i = 0; i < polygonCount; i++) {
bgCtx.beginPath()
currentRadius = r * (i + 1)
for (let j = 0; j < mCount; j++) {
const x = currentRadius * Math.cos(mAngle * j)
const y = currentRadius * Math.sin(mAngle * j)
// Record the coordinates of the vertices of the outermost polygon
if (i === polygonCount - 1) {
polygonPoints.push([x, y])
}
j === 0 ? bgCtx.moveTo(x, y) : bgCtx.lineTo(x, y)
}
bgCtx.closePath()
bgCtx.stroke()
}
// #endregion
// #region draw diagonal lines of polygons
for (let i = 0; i < polygonPoints.length; i++) {
bgCtx.moveTo(0.0)
bgCtx.lineTo(polygonPoints[i][0], polygonPoints[i][1])
}
bgCtx.stroke()
// #endregion
}
Copy the code
Regular polygon vertex text
The position of the text is actually near the vertex, and it can be offset according to the coordinates of the vertex. As mentioned above, since the Canvas coordinate system has been rotated through the rotate, the coordinate system needs to be rotated back again to make the text drawn forward
In addition, pay attention to the way the text is drawn, which is handled by textAlign:
function drawVertexTxt () {
bgCtx.font = 'normal normal lighter 16px Arial'
bgCtx.fillStyle = '# 333'
// The odd-numbered polygon, the point closest to the top edge of the device (i.e. the highest point), requires a special textAlign setting
const topPointIndex = mCount - Math.round(mCount / 4)
for (let i = 0; i < polygonPoints.length; i++) {
bgCtx.save()
bgCtx.translate(polygonPoints[i][0], polygonPoints[i][1])
bgCtx.rotate(rotateAngle)
let indentX = 0
let indentY = 0
if (i === topPointIndex) {
/ / a peak
bgCtx.textAlign = 'center'
indentY = - 8 -
} else {
if (polygonPoints[i][0] > 0 && polygonPoints[i][1] > =0) {
bgCtx.textAlign = 'start'
indentX = 10
} else if (polygonPoints[i][0] < 0) {
bgCtx.textAlign = 'end'
indentX = - 10}}// If it is a regular quadrilateral, you need to deal with the lowest point separately
if (mCount === 4 && i === 1) {
bgCtx.textAlign = 'center'
indentY = 10
}
// Start drawing copy
mData[i].titleList.forEach((item, index) = > {
bgCtx.fillText(item, indentX, indentY + index * 20)
})
bgCtx.restore()
}
}
Copy the code
Radar area
Radar area is the beginning of the figure, the red line box area, this area is also a polygon, just not positive, but calculating coordinates and regular polygon is actually almost, just in the process of seek coordinate, coordinate parameter for a percentage of the scale, and this is where the vertices represent the actual value and the proportion of the total cost of (for instance, 100 is the full mark, the first point is only 80, so it is 80%.
If it is just a static graph, then there is nothing to say so far. Get the coordinates of each point in the radar area, then connect the path, close the path, and then stroke the edge. But if you want to make the dynamic filling of the radar area at the beginning of the article, it will be a little more troublesome
My initial idea was to dynamically solve the coordinates of each vertex in each frame of radar area, but later I calculated for a long time and found that it was too troublesome to pull out so many mathematical formulas. Even if I could solve it, the performance would not be much better, right
So abandoned it, find another way, suddenly a skill
The dynamic filling method at the beginning of the article looks like a circle fanned out. After looking at a thing called Clip in the canvas, I came to the idea that the required radar area should be clipped first, and then a circle sufficient to cover the clipping area should be placed on the clipping surface for dynamic fanning out. Doesn’t that do the trick?
for (let i = 0; i < mCount; i++) {
// Score cannot exceed fullScore
score = Math.min(mData[i].score, mData[i].fullScore)
const x = Math.cos(mAngle * i) * score / mData[i].fullScore
const y = Math.sin(mAngle * i) * score / mData[i].fullScore
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y)
}
ctx.closePath()
ctx.clip()
// ...
ctx.moveTo(0.0)
ctx.arc(0.0, canvasMaxSize, 0, currentAngle)
ctx.closePath()
ctx.fill()
Copy the code
The effect is as follows:
It seems feasible, but after comparing with the graph at the beginning of the article, it is found that there are some shortcomings. The radar area of the head graph has a red stroke, and after the complete drawing, there are small red dots at each vertex of the radar area
The little red dot at the vertex is easy to do, the vertex coordinates are known, it’s just a little circle at the vertex, but the stroke is a little bit tricky
The length of the stroke follows the progress of drawing the radar area, which requires knowing the coordinates of each vertex in the radar area for each frame. We agreed not to do so many formulas
Later I thought again, if the final state of the radar map is drawn well in advance, and then covered with a mask, and then the mask dynamic open is not also ok?
Looking at the Canvas documentation, I found an API called globalCompositeOperation, and there it is
To make it easier to draw, I redivided it to three canvas
The first canvas is used as the canvas for the final rendering effect, the second canvas is used to draw the complete static radar area, and the third canvas is used to draw the mask for the complete static radar area. The combination of the three canvases achieves the expected effect:
summary
The full Live Demo and sample code for this article have been uploaded, so you can try it out for yourself