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