First, the necessity of customization

The bottom layer of drawing is powerful, and the languages we use are just diverging in the pace of modern UI pursuits and user preferences, Extract package from into individual character style UI controls, face trillions, of course, level of the client each platform UI library can’t satisfy all the needs of customers, of course, a language can make sex also means that its strong, almost every platform provides interfaces allows developers to create the possibility of its UI is more likely to meet customer demand. ECharts as a front-end powerful graph K line and other drawing tools can be said to have unexpectedly, very coquettish. But the needs of users and products can never be satisfied by a library. Of course, as a technician, custom drawing should also be the need to master the technology. Our front end mobile terminal as a product row should make it unique, unique. So custom from our technical jobs, technology itself, hundreds of millions of different needs of users… Start, “Customization is necessary”.

Second, the ECharts

     EChartsThose of you who have used it know that it is extremely rich and extravagant. Nothing to write about the use of libraries? Our goal today is to learn how to analyze and write ECharts on your own, rather than using ECharts libraries. Although I haven’t written a front end before, with an API you can move on. As follows:

The line chart

K line graph

K line graph

. Of course there are many more.

3. Understanding of canvas

Different from Android, Flutter, etc. Canvas is not a real Canvas in HTML5. The

element itself has no drawing capability (it’s just a container for graphics) – you must use scripts to do the actual drawing. The getContext() method returns an object that provides methods and properties for drawing on the canvas. The getContext(” 2D “) object is available in HTML5 via the Canvas tag, which provides many properties and methods for drawing text, lines, rectangles, circles, and more on the Canvas.

1. Canvas creation

First we get the Canvas container tag via getElementById, and then we get the draw object via Canvas.getContext (“2d”).

<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<canvas id="canvas"/>

<script>
    //getElementById() to access canvas elements
    const canvas = document.getElementById("canvas");
    const context = canvas.getContext("2d");
    
    
    context.fillStyle= 'RGB (222155155).;
    context.fillRect(0.0,canvas.width,canvas.height);
</script>
</body>
</html>
Copy the code

2. Set the width and height of the canvas and the background color

Set the size of the canvas using the width and height properties of the canvas. Set the color of the drawing region with the fillStyle property. FillRect to set the size of the drawing area to the fixed width and height of the upper left corner of the coordinate.

Canvas. width sets the width of the canvas. Canvas. height sets the height of the canvas

attribute role
fillStyle Set the fill style, color, gradient, etc
fillRect() Defines the position and size of the filled rectangle
<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<canvas id="canvas"/>

<script>
    //getElementById() to access canvas elements
    const canvas = document.getElementById("canvas");
     // Set the canvas width and height
    canvas.width=400;
    canvas.height=200;
    // Get the drawn object
    const context = canvas.getContext("2d");
    // Set the fill color to color gradient etc
    context.fillStyle= 'RGB (222155155).;
    // Fill the area with a rectangle to define the position and size
    context.fillRect(0.0,canvas.width,canvas.height);
</script>
</body>
</html>
Copy the code

Set canvas width=1000; height=500; The effect is as follows:

 // Canvas width and height
 canvas.width=1000;
 canvas.height=5000; . context.fillStyle='RGB (222155155).;
Copy the code

3. Set gradient

Gradient is one of the most common effects in UI. Not only add aesthetic feeling, but also lofty on the road than the indispensable secret. Let’s try out the front end gradient.

Linear gradient

createLinearGradient(x0: number, y0: number, x1: number, y1: number)

Gradient in the direction of (x0,y0) link (x1,y1). The following (0,0) to (canvas.width,0) gradient is horizontal.

addColorStop(offset: number, color: string): void;

Used to set the proportional position at which the gradient starts. So let’s see what happens

const gradient = context.createLinearGradient(0.0, canvas.width,0);
    gradient.addColorStop(0  ,"RGB (100200155)")
    gradient.addColorStop(0.5."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (200220255)")
    context.fillStyle = gradient
Copy the code

The following (0, 0) to (canvas. Width, canvas. Height) is a diagonal direction gradient let’s look at the effect

Linear gradient in any direction
const gradient = context.createLinearGradient(0.0, canvas.width,canvas.height);

Copy the code

The asymptotic direction can be determined by (x0,y0) and (x1,y1) line direction. Use addColorStop to set the scale to the starting range of the gradient value.

Radial gradient

createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number)

R0 and R1 have larger radii than that one, so let’s go from that to smaller radii, not from inside to outside or from outside to inside.

    //region 4
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width=1000;
    canvas.height=500;

    // Get the drawn object
    const context = canvas.getContext("2d");
    //Radial
    //createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number)
    const gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2.50, canvas.width / 2, canvas.height / 2.20);
    gradient.addColorStop(0  ,"RGB (100200155)")
    gradient.addColorStop(0.8."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (00120105)")

    context.fillStyle = gradient
    FillRect (x: number, y: number, w: number, h: number)
    context.fillRect(0.0,canvas.width,canvas.height);
    //endregion
Copy the code

Radius 50 to 20 gradient – from outside to inside process as follows

Radius 20 to 50 gradient – from the inside out process is as follows

That’s where the gradient ends… Gradients will be fully used in this case. I won’t write a separate gradient case because of time.

Three, canvas transformation

Canvas can be transformed by translate, Rotate, Scale, skew, etc., which makes the drawing process more efficient with less effort. The default coordinate system for the canvas is the top left corner, and we can draw from coordinates (0,0) to (100,100) and line it up. The following code and effect:

BeginPath () indicates the start of a new path. The next filling will only modify the contents of this path. Context. Y) link to the next point context.strokestyle = gradient Sets the color of the unclosed path context.stroke() the path is a line

<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>canvas_change</title>
</head>
<body>
<canvas id="canvas"/>
</body>
<script>
    //region 1. Traslate
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 100;
    canvas.height = 100;
    // Get the drawn object
    const context = canvas.getContext("2d");

    // Start drawing the path
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(100.100)

    // Set the color of the line to gradient
    const gradient = context.createLinearGradient(
        0.0, canvas.width, canvas.height);
    gradient.addColorStop(0."RGB (100200155)")
    gradient.addColorStop(0.8."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (00120105)")
    context.strokeStyle = gradient

    // Draw a closed region. Fill is a closed region
    context.stroke()
    
</script>
</html>
Copy the code

[translate]

Our common ECharts diagrams have frames, and our frames default to the upper left corner. Most common coordinates are not in the upper left corner. It might be a little harder to draw if you have a dot in the upper left corner. Hopefully, (0,0) is the relative position we want, which makes it easier to do a lot of things.

The line chart

Above we learned how to draw lines. So let’s draw the default coordinate system and draw a circle of radius 50 in the upper left corner of the default center.

<script>
    //region 1. Traslate
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 400;
    canvas.height = 200;
    // Get the drawn object
    const context = canvas.getContext("2d");

    // Set the color of the line to gradient
    const gradient = context.createLinearGradient(
        0.0, canvas.width, canvas.height);
    gradient.addColorStop(0."RGB (100200155)")
    gradient.addColorStop(0.8."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (00120105)")
    context.strokeStyle = gradient



    // Start drawing the X-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(canvas.width, 0)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    // Draw the Y-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(0, canvas.height)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    context.beginPath()
    // Draw Draws the circle at the center of the circle
    context.arc(0.0.50.0.Math.PI * 2.true);
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.fillStyle = gradient
    context.fill()

</script>
Copy the code

So what I want to do is I want to put the far point in the middle of the canvas, and I want to translate it before I draw it. You can see that the axes of the circle are drawn with the center of the canvas as the dot, and of course you can move them around as much as you want.

<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>canvas_change</title>
</head>
<body>
<canvas id="canvas"/>
</body>
<script>
    //region 1. Traslate
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 400;
    canvas.height = 200;
    // Get the drawn object
    const context = canvas.getContext("2d");

    // Set the color of the line to gradient
    const gradient = context.createLinearGradient(
        0.0, canvas.width, canvas.height);
    gradient.addColorStop(0."RGB (100200155)")
    gradient.addColorStop(0.8."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (00120105)")
    context.fillStyle = "Rgba (100200155,0.2)"
    context.fillRect(0.0, canvas.width, canvas.height);
    context.strokeStyle = gradient

    // Pan the canvas
    context.translate(canvas.width/2,canvas.height/2)

    // Start drawing the X-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(canvas.width, 0)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    // Draw the Y-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(0, canvas.height)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    context.beginPath()
    // Draw Draws the circle at the center of the circle
    context.arc(0.0.50.0.Math.PI * 2.true);
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.fillStyle = "RGB (200120155)"
    context.fill()

</script>
</html>
Copy the code

Rotate canvas

First let’s guess the rotation of the canvas and then prove it. First draw a line, then rotate the canvas 10 degrees and draw the same line again. Before and after the drawing is as follows:

// Rotate the canvas
context.rotate(Math.PI/180*10)
Copy the code

Canvas scale

Canvas uses scale(float sx, float SY) to convert the drawing coordinate system to the desired coordinate system. For example, the default coordinate system is as follows:

The coordinate system IN my mind is not like this but like this and like this:

And then we want to have a coordinate system that looks like the following two.

Scale (-1, 1), y (-1, 1), canvas. Scale (-1, 1), origin (-1, -1)

The dots can be seen in the lower left corner of the coordinate system in figure 2. Y up is positive,x to the right is positive, as opposed to the top left corner of the default coordinate system, except the y is opposite. In this case, we can use canvas.scale(1,-1) mirror transform, and then pan down. Scale (1,-1) and shift canvas.height down along the X-axis

<script>
    //region 1. Transform rote
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 400;
    canvas.height = 200;
    // Get the drawn object
    const context = canvas.getContext("2d");

    // Set the color of the line to gradient
    const gradient = context.createLinearGradient(
        0.0, canvas.width, canvas.height);
    gradient.addColorStop(0."RGB (100200155)")
    gradient.addColorStop(0.8."RGB (200120155)")
    gradient.addColorStop(1.0."RGB (00120105)")
    context.fillStyle = "Rgba (100200155,0.2)"
    context.fillRect(0.0, canvas.width, canvas.height);
    context.strokeStyle = gradient

    // The most important thing to understand is that the y coordinate system is positive down and negative down through scale(1,-1).
    context.scale(1, -1)
    // Pan it down, notice that the countryside is in the negative direction
    context.translate(0,-canvas.height)

    // Start drawing the X-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(canvas.width, 0)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    // Draw the Y-axis
    context.beginPath()
    context.moveTo(0.0)
    context.lineTo(0, canvas.height)
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.stroke()

    context.beginPath()
    / / draw the line
    context.moveTo(0.0);
    context.lineTo(100.100);
    context.closePath()
    // Draw a closed region. Fill is a closed region
    context.strokeStyle = "RGB (200120155)"
    context.stroke()

</script>
Copy the code

Okay, so now that we’ve learned the transformation of coordinates, I’m sure you think that something so simple, is that it? Of course, the coordinate transformation has a great convenience and simplification function, we gradually in-depth, canvas transformation will let you get twice the result with half the effort, with ease.

4. Handwritten ECharts cases

1. Line chart

The following isECharts official first case: are text and a variety of lines and circles, but there are a lot of coordinate transformation with our relationship, we step by step to analyze the importance of canvas transformation.

Analysis and rendering process

2. Draw line 3 parallel to the X axis. Draw text. 4. Draw polylines and circles

1. Transform coordinate system — bring convenience to operation

We analyze the above figure, basically the lower left corner is the center of the coordinate circle for the entire line drawing. But by default our coordinate system is the top left corner, so what we’re going to do is transform the coordinate system to the bottom left corner, and in the example above we saw that there’s some distance between the bottom and the coordinate to draw the text and we’re going to set the distance to 50 below and 40 to the left.

 //region 1. Transform rote
    const marginBootom = 50;
    const marginLeft = 40;
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 400;
    canvas.height = 200;
    // Get the drawn object
    const context = canvas.getContext("2d");
    / / the gradient
    context.strokeStyle = "RGB (0,0,0,1)"
    context.lineWidth=0.2

    // Mirror the canvas along the x axis
    context.scale(1, -1)
    // Pan the canvas down -marginBootom height
    context.translate(marginLeft, -canvas.height+marginBootom)
    
Copy the code

So the coordinate system is going to look something like this, and I’m going to paste the coordinate system here for the sake of illustration and observation. Next we begin the drawing process.

2. Draw lines parallel to the X-axis. Do we need to calculate the starting and ending points of each line? So much trouble? Of course, the canvas transformation is a good solution to this problem. Our canvas is stateful and we can save each state and we can go back to the previous state. Here it is: we draw the bottom line.

So we can move it up in the Y direction at a fixed height every time we change coordinates and draw this line. Multiple draws form multiple line segments parallel to the X axis.


<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Line</title>
</head>
<body>
<canvas id="canvas"/>
<script>
    //region 1. Transform rote
    const marginBootom = 50;
    const marginLeft = 40;
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 400;
    canvas.height = 300;
    // Get the drawn object
    const context = canvas.getContext("2d");
    / / the gradient
    context.strokeStyle = "RGB (0,0,0,1)"
    context.lineWidth=0.08

    // Mirror the canvas along the x axis
    context.scale(1, -1)
    // Pan the canvas down -marginBootom height
    context.translate(marginLeft, -canvas.height+marginBootom)
    
    // Save the current canvas state. Since our center is in the lower left corner, we need to go back to this far point and do something else.
    context.save()
    const heightOfOne=30

    // Start drawing the X-axis
    for(let i=0; i<7; i++){
        context.beginPath()
        context.moveTo(0.0)
        context.lineTo(canvas.width, 0)
        context.closePath()
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        context.translate(0,heightOfOne)
    }
    context.restore()

</script>
</body>
</html>
Copy the code

In the same way, we divide the X-axis into 7 equal parts, but we have to draw a scale for each part. The following code

 // Start by drawing the scale
    context.save()
    context.lineWidth=0.2
    for(let i=0; i<8; i++){
        context.beginPath()
        context.moveTo(0.0)
        context.lineTo(0, -5)
        context.closePath()
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        context.translate(widthOfOn,0)
    }
    context.restore()

Copy the code

Draw the text below the X axis

If very precise, this may involve the measurement of words. Of course, we need to be precise, so I won’t go into the details of the TEXT drawing measurement API for time. It’s explained in the code.

The context. FillText (” text “, x, y); Draws text at position (x,y)

   // Draw the text array on the X-axis
    const xText = new Array("Mon"."Tue"."Wed"."Thu"."Fir"."Sat"."Sun");
    for(let i=0; i<xText.length; i++){
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        if(i===0) {// After the analysis, the first move is half of the unit length. Each time the following scale is shifted by one scale length, the center of the coordinate circle is shifted to the middle of each scale. The y axis has been shifted down by 5 pixels. So it doesn't overlap with the X-axis.
            context.translate(widthOfOn/2, -5)}else{
            context.translate(widthOfOn,0)
        }
        context.fillText(xText[i],0.0);
    }
    context.restore()
Copy the code

So here we see that the font is mirrored along the X axis. Because by default the bottom right is positive, we transform the top right to be positive. So before we can draw it here we just have to restore the coordinate system.

 // Draw the text array on the X-axis
    context.save()
    const xText = new Array("Mon"."Tue"."Wed"."Thu"."Fir"."Sat"."Sun");
    // Here is a mirror image transformation along the X-axis. So Y is positive down, X is positive without moving to the right.
    context.scale(1, -1)
    for(let i=0; i<xText.length; i++){
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        if(i===0) {// After the analysis, the first move is half of the unit length. Each time the following scale is shifted by one scale length, the center of the coordinate circle is shifted to the middle of each scale. The y axis has been shifted down by 5 pixels. So it doesn't overlap with the X-axis.
            context.translate(widthOfOn/2.15)}else{
            context.translate(widthOfOn,0)
        }
        context.fillText(xText[i],0.0);
    }
    // Restore the far point to the lower left state.
    context.restore()
Copy the code

Draw the text on the left side of the Y axis also need me BB….

    // Save the lower left corner as the coordinate dot state.
    context.save()
    context.scale(1, -1)
    context.translate(-20.0)
    context.font = "7pt Calibri";
    // Draw text to the left of the Y-axis
    for(let i=0; i<7; i++){
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        context.fillText((50*i).toString(),0.0);
        context.translate(0,-heightOfOne)

    }
    context.restore()
Copy the code

Fonts are all drawn in the image above, but our text is not exactly in the middle of each scale compared to the original. As shown in blue. And this is the picture that we drew. Here’s a comparison of the case where we draw the text in the middle rather than the unit scale in the middle. We just need to figure out the width of the text. Then subtract half the width of the position from the drawn text X. Think about it. It’s easy, right?

Context. MeasureText (” text “); FillText (xText[I], -textwidth.width /2,0);

    // Draw the text array on the X-axis
    context.save()
    const xText = new Array("Mon"."Tue"."Wed"."Thu"."Fir"."Sat"."Sun");
    // Here is a mirror image transformation along the X-axis. So Y is positive down, X is positive without moving to the right.
    context.scale(1, -1)
    context.font = "7pt Calibri";
    for(let i=0; i<xText.length; i++){
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Continue to move up after each drawing
        if(i===0) {// After the analysis, the first move is half of the unit length. Each time the following scale is shifted by one scale length, the center of the coordinate circle is shifted to the middle of each scale. The y axis has been shifted down by 5 pixels. So it doesn't overlap with the X-axis.
            context.translate(widthOfOn/2.15)}else{
            context.translate(widthOfOn,0)}const textWidth = context.measureText(xText[i]);
        context.fillText(xText[i],-textWidth.width/2.0);
    }
    // Restore the far point to the lower left state.
    context.restore()
Copy the code

The same Y axis text feels a wave by itself? Without further ado.

4. Drawing polylines and circles There is a slight problem with all drawing, regardless of which end we need to map our actual data to our coordinate system. This is just a simple calculation, and just to mention, for example, our internal coordinate system is calculated by the width of the canvas, which is pixels, and our actual data could be arbitrary. So we need to map the data to our coordinate system. This is the case with the broken line data:

Define class for coordinate storage, the first use of front-end object weird…..

const Point = {
    createNew: function (x,y) {
        const point = {};
        point.x = x;
        point.y = y;
        returnpoint; }};Copy the code
<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Line</title>
    <script type="text/javascript" src="js/canvas.. js"></script>
</head>

<body>
<canvas id="canvas"></canvas>
<script>
    //region 1. Transform rote
    const marginBootom = 50;
    const marginLeft = 40;
    const canvas = document.getElementById("canvas");
    // Set the canvas width and height
    canvas.width = 500;
    canvas.height = 300;
    // Get the drawn object
    const context = canvas.getContext("2d");
    / / the gradient
    context.strokeStyle = "RGB (0,0,0,1)"
    context.lineWidth = 0.09

    // Mirror the canvas along the x axis
    context.scale(1, -1)
    // Pan the canvas down -marginBootom height
    context.translate(marginLeft, -canvas.height + marginBootom)

    // Draw the X-axis and scale
    drawX(context)
    // Draw text
    drawText(context)
    // Draw polylines and circles
    drawLine(context)
    / / draw circle
    drawCircle(context)
</script>

</body>
</html>
Copy the code

Canvas. Js file

// Draw a polyline
function drawLine(context) {
    // Draw a polyline segment
    const widthOfOn = (canvas.width - marginLeft) / 7
    const danweiHeight=35/50;// The actual height of pixels occupied by each number
    const point01 = Point.createNew(widthOfOn/2.150*danweiHeight);
    const point02 = Point.createNew(widthOfOn/2+widthOfOn,250*danweiHeight);
    const point03 = Point.createNew(widthOfOn/2+widthOfOn*2.225*danweiHeight);
    const point04 = Point.createNew(widthOfOn/2+widthOfOn*3.211*danweiHeight);
    const point05 = Point.createNew(widthOfOn/2+widthOfOn*4.140*danweiHeight);
    const point06 = Point.createNew(widthOfOn/2+widthOfOn*5.148*danweiHeight);
    const point07 = Point.createNew(widthOfOn/2+widthOfOn*6.260*danweiHeight);


    const points = [point01, point02, point03, point04, point05, point06, point07];
    context.save();

    context.beginPath();
    for (let index = 0; index < points.length; index++) {
        context.lineTo(points[index].x,points[index].y);
    }
    context.strokeStyle="RGB (93111194)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();
    context.restore();
}
// Draw the circle
function drawCircle(context) {
    const widthOfOn = (canvas.width - marginLeft) / 7
    const danweiHeight=35/50;// The actual height of pixels occupied by each number
    const point01 = Point.createNew(widthOfOn/2.150*danweiHeight);
    const point02 = Point.createNew(widthOfOn/2+widthOfOn,250*danweiHeight);
    const point03 = Point.createNew(widthOfOn/2+widthOfOn*2.225*danweiHeight);
    const point04 = Point.createNew(widthOfOn/2+widthOfOn*3.211*danweiHeight);
    const point05 = Point.createNew(widthOfOn/2+widthOfOn*4.140*danweiHeight);
    const point06 = Point.createNew(widthOfOn/2+widthOfOn*5.148*danweiHeight);
    const point07 = Point.createNew(widthOfOn/2+widthOfOn*6.260*danweiHeight);


    const points = [point01, point02, point03, point04, point05, point06, point07];
    context.save();
    context.beginPath();
    for (let index = 0; index < points.length; index++) {
        context.beginPath();
        context.arc(points[index].x,points[index].y,3.0.Math.PI * 2.true);
        context.closePath();
        context.fillStyle = 'RGB (100255255).;
        context.shadowBlur = 5;
        context.shadowColor = 'RGB (100255255).;
        context.fill()
    }
    canvas.restore();
}
Copy the code

Here, if you go down step by step for the don’t understand baidu search API I believe that as long as you exercise at least three each using the transformation of the canvas, so the custom you have reached a good level, of course, for all kinds of SAO operation, we can further study bezier curves and animation and gestures, etc.

Smooth line chart

Today is the first time to contact HTML5 customization, in fact, the customization of each end is API encapsulation based on the underlying rendering, a good platform or language will have a perfect API,H5 I think the reason why ECharts library can be very perfect API, so I am very confident in this chapter.

  • curveCustom ICONS often appear in development, so learning to draw curves can make your software more usefulcreativeandEndless charm.

I. Curve recognition and understanding

  • Since Android has written some overview and understanding before, so here is the Android code and understanding, the time problem here can see the basic understanding
Curve common API 1. First-order curve 2. Second-order curve 3Copy the code

We in junior high school learning to learn a variety of straight lines, circles, ellipses, is xuan… Coordinate system equations for curves and so on, and then let’s review our equations for lines and curves and so on.

  • So the first step we’re going to do is we’re going to define a class and we’re going to create a coordinate system, and we’re going to rotate the screen in landscape
package com.example.android_draw.view

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View

/ * * * * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * │ ┌ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┐ │ x │ │ Esc │! 1 2 │ │ @ # % 3 $4 │ │ │ 5 6 7 │ │ & * ^ 8 │ │ │ (9) 0 _ + = │ │ - | \ │ ` ~ │ │ * │ ├ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ┤ │ x │ │ Tab │ │ │ │ │ Q W E R T I │ │ │ Y U │ │ │ P O {[│}] │ BS │ │ x │ ├ ─ ─ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ─ ─ ─ ┤ │ x │ │ Ctrl │ │ │ │ │ │ F G D S A H │ │ │ K J L │ :; │ │ "' Enter │ │ X │ ├ ─ ─ ─ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ─ ┬ ─ ─ ─ ┤ │ X │ │ Shift │ │ X │ │ Z C V │ │ │ N M B , │ │ < >. │? / │ Shift │ Fn │ │ x │ └ ─ ─ ─ ─ ─ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ x │ │ Fn Alt │ │ Space Alt │ │ Win │ HHKB │ x │ └ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ Copyright (c) 2015 Bohai Xinneng All Rights Reserved@authorFeiWang * Version: 1.5 * Created date: 2/8/21 * Description: Android_Draw * E-mail: 1276998208@qq.com
 * CSDN:https://blog.csdn.net/m0_37667770/article
 * GitHub:https://github.com/luhenchang
 */
class LHC_Cubic_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    init{}override fun onDraw(canvas: Canvas?). {
        super.onDraw(canvas)
    }

}

Copy the code

In order to facilitate observation and rendering, grid and coordinate axes are drawn. I believe that I have mastered the transformation operation of canvas in the last article. I will not explain the code of grid axis. Look at the picture.

1. The equation is mapped to the coordinate system

Remember we learned in junior high that Y of x is equal to ax plus b. So let’s look at this equation mapped to the coordinate system. I’m going to define a function y equals 2x minus 80 and I’m going to get a set of points, and for the sake of effect we’re going to draw x even, and then we’re going to draw points. The code is as follows:

    private var number=0.420.
    // The equation of the line is y=2x-80
    private fun drawzxLine(canvas: Canvas) {
        pointList= ArrayList()
        // Draw y=10x+20
        val gPaint = getPaint()
        number.forEach { t ->
            val point=PointF()
            if (t%2= =0) {// Draw even points on the X-axis
                point.x = t.toFloat()
                point.y = 2f * t - 80
                pointList.add(point)
                canvas.drawPoint(point.x, point.y, gPaint)
            }
        }
    }

Copy the code

1. The equations of the same circle and ellipse can be mapped to the coordinate system in this way.

2.The curve represented is a circle with center O(a, b) and radius R.

3. For example: (x – 10)2+(y-10)2= 1602Let’s map it to the coordinate system.

  • I should be able to solve the equation. Let’s convert to the familiar equation:

(x-10)2+(y-10)2=1602 1. (y-10)2=1602-(x-10)2 2 The equation is as follows: There are positive and negative values after the square root

  1. Y = SQRT (160.0 pow (2.0). The toFloat () – ((point. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
  2. Y = – SQRT (160.0 pow (2.0). The toFloat () – ((pointDown. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
 // Draw the circle
        number.forEach { t ->
            val point = PointF()
            val pointDown = PointF()

            //(x-10)2+ (y-10)2 =1602
            point.x = t.toFloat()
            pointDown.x = t.toFloat()
            // I don't need to tell you.
            point.y =
                sqrt(160.0.pow(2.0).toFloat() - ((point.x - 10).toDouble()).pow(2.0)).toFloat() + 10
            pointDown.y = -sqrt(
                160.0.pow(2.0).toFloat() - ((pointDown.x - 10).toDouble()).pow(2.0)
            ).toFloat() + 10
            canvas.drawPoint(point.x, point.y, gPaint)
            canvas.drawPoint(pointDown.x, pointDown.y, gPaint)

        }
Copy the code

2. Bezier curve

From the above we can see that any function can be mapped to coordinate system drawing one by one, and of course there are equations for Bezier curves. There are the following:

Linear Bezier curve

  • Given points P0, P1, the linear Bezier curve is just a straight line between two points. The line is given by the following formula:

     

Quadratic Bezier curve

  • The path of the quadratic Bezier curve is traced by B (t) of the given points P0, P1, and P2:

     

Bessel curve to the third power

The points P0, P1, P2, and P3 define a cubic Bezier curve in the plane or in three dimensions. It starts at P0 going to P1, and it goes from P2 to P3. It usually doesn’t go through P1 or P2; The formula is as follows:

Of course, the Native layer of Android terminal has encapsulated methods, quadratic Bezier curve and cubic Bezier curve, and known functions can be encapsulated.

Second and third order bezier curves are provided on the Android side:public void quadTo(float x1, float y1, float x2, float y2)
    publicVoid rQuadTo(float dx1, float dy1, float dx2, float dy2)public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
    public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3) 

Copy the code

Next, we draw a second-order curve, and the control point can move the screen with the gesture and press down. It is very clear in the previous section of the broken line about the mapping between the gesture coordinate system and the screen coordinate system, which will not be explained here.

  • quadTo(float x1, float y1, float x2, float y2)
    // Record the moving canvas coordinates, not gesture coordinates, which are converted to canvas coordinates for refreshing
    private var moveX: Float = 160f
    private var moveY: Float = 160f
   private fun drawQuz(canvas: Canvas) {
        controllRect = Rect(
            (moveX - 30f).toInt(),
            (moveY + 30f).toInt(),
            (moveX + 30).toInt(),
            (moveY - 30f).toInt()
        )
        val quePath = Path()
        canvas.drawCircle(0f.0f.10f, getPaintCir(Paint.Style.FILL))
        canvas.drawCircle(320f.0f.10f, getPaintCir(Paint.Style.FILL))
        // The first point and control point are connected to the last point chain. For the sake of observation
        val lineLeft = Path()
        lineLeft.moveTo(0f.0f)
        lineLeft.lineTo(moveX, moveY)
        lineLeft.lineTo(320f.0f)
        canvas.drawPath(lineLeft, getPaint(Paint.Style.STROKE))
        // Draw a circle at p0. I'm going to draw a control point circle at the second p1, and then I'm going to draw a control point circle at the end.
        canvas.drawCircle(moveX, moveY, 10f, getPaintCir(Paint.Style.FILL))
        quePath.quadTo(moveX, moveY, 320f.0f)
        canvas.drawPath(quePath, getPaint(Paint.Style.STROKE))
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            ACTION_DOWN,
            ACTION_MOVE -> {
                // Within the range near the control point, move
                Log.e("x="."onTouchEvent: (x,y)"+(event.x - width / 2).toInt()+":"+(-(event.y - height / 2)).toInt())
                // Convert gesture coordinates to screen coordinates
                moveX = event.x - width / 2
                moveY = -(event.y - height / 2)
                invalidate()
            }
        }
        return true
    }

Copy the code

In the figure above, you can drag the control point, and the curve between the beginning and the end deforms with the control point. The protrusion near the radian of the control point is inclined to the other side. It is only necessary to have a preliminary understanding of this rule, and the control point is constantly adjusted in practice to meet our needs. But in the figure above we see that the radians are not circular enough, and we can adjust the radians well in the third order function. Now let’s look at third-order functions

The third order curve

  • public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

And again we’re going to plot the third order curve in the coordinate system. In order to get a good view of the effect we are going to do fine control this time, we can drag any control point we want and look at our third order curve. In the previous section, the contains method of Rect with the gesture can be used for local clicking, of course, dragging is also no problem. As shown in the figure below: We only need to draw the distance shape near the control point to wrap the control point, and the control point and corresponding distance shape can be refreshed by gesture sliding.

Now I think we have a pretty good idea of what second and third order curves are controlling in the general direction of radians. You think this is the end of it. Now let’s begin the formal application of the entry curve.

Graph analysis

4. The rescue of the third-order curve

When y1y_1y1< y2y_2y2, as shown in the figure 1 above, find the coordinates of the midpoint: control point x+40px in the lower part of the X-axis and x-40px in the upper part, and adjust the Y-axis to make the smoothness of control point Y-40x in the lower part and y+40 in the upper part. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =((x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y_2y2)/2) when y1y_1y1> Y2y_2y2 is shown in figure 2. Figure out the coordinates of the midpoint: the upper part of the X-axis +40px, the lower part x-40px, the Y-axis can also be adjusted, and the Y-axis can also be adjusted to make the smoothness of the upper part of the control point Y +40x, the lower part y-40. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =(x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y2)/2)

/** * draw a curve *@param context* /
function drawQuaraticLine(context) {
    // Draw a polyline segment
    const widthOfOn = (canvas.width - marginLeft) / 7
    const danweiHeight=35/50;// The actual height of pixels occupied by each number
    const point01 = Point.createNew(widthOfOn/2.150*danweiHeight);
    const point02 = Point.createNew(widthOfOn/2+widthOfOn,250*danweiHeight);
    const point03 = Point.createNew(widthOfOn/2+widthOfOn*2.225*danweiHeight);
    const point04 = Point.createNew(widthOfOn/2+widthOfOn*3.211*danweiHeight);
    const point05 = Point.createNew(widthOfOn/2+widthOfOn*4.140*danweiHeight);
    const point06 = Point.createNew(widthOfOn/2+widthOfOn*5.148*danweiHeight);
    const point07 = Point.createNew(widthOfOn/2+widthOfOn*6.260*danweiHeight);


    const dataList = [point01, point02, point03, point04, point05, point06, point07];
    context.save();
    context.beginPath();
    context.lineTo(point01.x,point01.y)
    //500=grid_width-40 Each unit of length = pixel length
    const danweiX = widthOfOn;
    const grid_width=widthOfOn;
    const xMoveDistance=20
    const yMoveDistance=30
    for (let index = 0; index < dataList.length-1; index++) {if (dataList[index] === dataList[index + 1]) {
            context.lineTo(danweiX*(index+1),0)}else if(dataList[index] < dataList[index + 1]) {/ / y1 < y2
            const centerX=(grid_width * index + grid_width * (1 + index)) / 2
            const centerY=(dataList[index].y + dataList[index + 1].y) / 2
            const controX0=(grid_width * index+centerX)/2
            const controY0=(dataList[index].y+centerY)/2
            const controX1=(centerX+ grid_width * (1 + index))/2
            const controY1=(centerY+dataList[index+1].y)/2context.bezierCurveTo(controX0+xMoveDistance,controY0-yMoveDistance,controX1-xMoveDistance,controY1+yMoveDistance,grid_w idth * (1 + index),dataList[index + 1].y)
        }else{
            const centerX=(grid_width * index + grid_width * (1 + index)) / 2
            const centerY=(dataList[index].y + dataList[index + 1].y) / 2
            const controX0=(grid_width * index+centerX)/2
            const controY0=(dataList[index].y+centerY)/2
            const controX1=(centerX+ grid_width * (1 + index))/2
            const controY1=(centerY+dataList[index+1].y)/2context.bezierCurveTo(controX0+xMoveDistance,controY0+yMoveDistance,controX1-xMoveDistance,controY1-yMoveDistance,grid_w idth * (1 + index),dataList[index + 1].y)
        }
    }
    context.strokeStyle="RGB (93111194)"
    context.lineWidth=2
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();
    context.restore();
}
Copy the code

Due to the time problem, the parameters were not adjusted in detail. Of course, the gap between the X axis was too small, so it was embarrassing to watch. If you have time you can refer to my previous onesAndroid custom curveA wave of blogs

3. Filled line chart

We’ve done polylines and curves before, but how do we do this fill down here? How to conduct more SAO operation? Let’s explore that.

Let’s build on that, we can build on that and draw a closed polygon and fill it.

<! DOCTYPE html><html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Line</title>
  <script type="text/javascript" src="js/canvas.. js"></script>
  <script type="text/javascript" src="js/canvas_fill.js"></script>
</head>

<body>
<canvas id="canvas"></canvas>
<script>
  //region 1. Transform rote
  const marginBootom = 50;
  const marginLeft = 40;
  const canvas = document.getElementById("canvas");
  // Set the canvas width and height
  canvas.width = 500;
  canvas.height = 300;
  // Get the drawn object
  const context = canvas.getContext("2d");
  / / the gradient
  context.strokeStyle = "RGB (0,0,0,1)"
  context.lineWidth = 0.09

  // Mirror the canvas along the x axis
  context.scale(1, -1)
  // Pan the canvas down -marginBootom height
  context.translate(marginLeft, -canvas.height + marginBootom)

  // Draw the X-axis and scale
  drawX(context)
  // Draw text
  drawText(context)
  // Draw polylines and circles
  drawFillLine(context)
  / / draw circle
  drawCircle(context)




</script>

</body>
</html>
Copy the code

Fill in part code

function drawFillLine(context) {
    // Draw a polyline segment
    const widthOfOn = (canvas.width - marginLeft) / 7
    const danweiHeight=35/50;// The actual height of pixels occupied by each number
    const point00 = Point.createNew(0.150*danweiHeight);
    const point01 = Point.createNew(widthOfOn/2.150*danweiHeight);
    const point02 = Point.createNew(widthOfOn/2+widthOfOn,250*danweiHeight);
    const point03 = Point.createNew(widthOfOn/2+widthOfOn*2.225*danweiHeight);
    const point04 = Point.createNew(widthOfOn/2+widthOfOn*3.211*danweiHeight);
    const point05 = Point.createNew(widthOfOn/2+widthOfOn*4.140*danweiHeight);
    const point06 = Point.createNew(widthOfOn/2+widthOfOn*5.148*danweiHeight);
    const point07 = Point.createNew(widthOfOn/2+widthOfOn*6.260*danweiHeight);


    const points = [point00,point01, point02, point03, point04, point05, point06, point07];
    context.save();
    context.beginPath();
    for (let index = 0; index < points.length; index++) {
        context.lineTo(points[index].x,points[index].y);
    }
    context.strokeStyle="RGB (93111194)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();

    context.beginPath();
    // Draw a closed polygon
    context.moveTo(0.0)
    for (let index = 0; index < points.length; index++) {
        context.lineTo(points[index].x,points[index].y);
    }
    context.lineTo(points[points.length-1].x,0);
    context.lineTo(0.0);
    context.closePath();
    context.fillStyle="Rgba (93111194,0.5)"
    context.lineWidth=3
    context.shadowBlur = 5;
    context.fill();
    context.restore();


}
Copy the code

The use of gradient color makes it more characteristic and charming. Of course, I am not a standard designer, beauty does not lie in us, but in the design, a good design should not be as ugly as my color matching, right?

3. Multiple line charts

Here are multiple line charts. We got one. Can’t we get more? You just need to do the same thing, just a different set of data.

Due to the time problem, we directly operate on the previous basis

<! DOCTYPE html><html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Line</title>
  <script type="text/javascript" src="js/canvas.. js"></script>
  <script type="text/javascript" src="js/canvas_fill.js"></script>
</head>

<body>
<canvas id="canvas"></canvas>
<script>
  //region 1. Transform rote
  const marginBootom = 50;
  const marginLeft = 40;
  const canvas = document.getElementById("canvas");
  // Set the canvas width and height
  canvas.width = 500;
  canvas.height = 300;
  // Get the drawn object
  const context = canvas.getContext("2d");
  / / the gradient
  context.strokeStyle = "RGB (0,0,0,1)"
  context.lineWidth = 0.09

  // Mirror the canvas along the x axis
  context.scale(1, -1)
  // Pan the canvas down -marginBootom height
  context.translate(marginLeft, -canvas.height + marginBootom)

  // Draw the X-axis and scale
  drawX(context)
  // Draw text
  drawText(context)

  function drawMoreLine(context) {
    // Draw a polyline segment
    const widthOfOn = (canvas.width - marginLeft) / 7
    const danweiHeight=35/50;// The actual height of pixels occupied by each number
    const point01 = Point.createNew(widthOfOn/2.200*danweiHeight);
    const point02 = Point.createNew(widthOfOn/2+widthOfOn,  250*danweiHeight);
    const point03 = Point.createNew(widthOfOn/2+widthOfOn*2.225*danweiHeight);
    const point04 = Point.createNew(widthOfOn/2+widthOfOn*3.211*danweiHeight);
    const point05 = Point.createNew(widthOfOn/2+widthOfOn*4.140*danweiHeight);
    const point06 = Point.createNew(widthOfOn/2+widthOfOn*5.148*danweiHeight);
    const point07 = Point.createNew(widthOfOn/2+widthOfOn*6.260*danweiHeight);


    const point011 = Point.createNew(widthOfOn/2.150*danweiHeight);
    const point012 = Point.createNew(widthOfOn/2+widthOfOn,  200*danweiHeight);
    const point013 = Point.createNew(widthOfOn/2+widthOfOn*2.125*danweiHeight);
    const point014 = Point.createNew(widthOfOn/2+widthOfOn*3.181*danweiHeight);
    const point015 = Point.createNew(widthOfOn/2+widthOfOn*4.90*danweiHeight);
    const point016 = Point.createNew(widthOfOn/2+widthOfOn*5.98*danweiHeight);
    const point017 = Point.createNew(widthOfOn/2+widthOfOn*6.210*danweiHeight);


    const point021 = Point.createNew(widthOfOn/2.60*danweiHeight);
    const point022 = Point.createNew(widthOfOn/2+widthOfOn,  65*danweiHeight);
    const point023 = Point.createNew(widthOfOn/2+widthOfOn*2.61*danweiHeight);
    const point024 = Point.createNew(widthOfOn/2+widthOfOn*3.70*danweiHeight);
    const point025 = Point.createNew(widthOfOn/2+widthOfOn*4.78*danweiHeight);
    const point026 = Point.createNew(widthOfOn/2+widthOfOn*5.68*danweiHeight);
    const point027 = Point.createNew(widthOfOn/2+widthOfOn*6.72*danweiHeight);


    const points = [point01, point02, point03, point04, point05, point06, point07];
    const point1s = [point011, point012, point013, point014, point015, point016, point017];
    const point2s = [point021, point022, point023, point024, point025, point026, point027];

    context.save();
    context.beginPath();
    for (let index = 0; index < points.length; index++) {
      context.lineTo(points[index].x,points[index].y);
    }
    context.strokeStyle="RGB (93111194)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();




    context.beginPath();
    for (let index = 0; index < point1s.length; index++) {
      context.lineTo(point1s[index].x,point1s[index].y);
    }
    context.strokeStyle="RGB (193111194)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();

    context.beginPath();
    for (let index = 0; index < point2s.length; index++) {
      context.lineTo(point2s[index].x,point2s[index].y);
    }
    context.strokeStyle="RGB (293111294)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();


    context.beginPath();
    for (let index = 0; index < point2s.length; index++) {
      context.lineTo(point2s[index].x,point2s[index].y-15);
    }
    context.strokeStyle="RGB (293211194)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();

    context.beginPath();
    for (let index = 0; index < point2s.length; index++) {
      context.lineTo(point2s[index].x,point2s[index].y-35);
    }
    context.strokeStyle="RGB (93211294)"
    context.lineWidth=1
    context.shadowBlur = 5;
    context.stroke();
    context.closePath();
    for (let index = 0; index < points.length; index++) {
      context.beginPath();
      context.arc(points[index].x,points[index].y, 3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = 'RGB (100255255).;
      context.shadowBlur = 10;
      context.shadowColor = 'RGB (100255255).;
      context.fill()
    }

    // The circle above the first one
    for (let index = 0; index < points.length; index++) {
      context.beginPath();
      context.arc(points[index].x,points[index].y, 3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = "RGB (93111194)";
      context.shadowBlur = 10;
      context.shadowColor = "RGB (93111194)";
      context.fill()
    }
    // The circle above the second line
    for (let index = 0; index < point1s.length; index++) {
      context.beginPath();
      context.arc(point1s[index].x,point1s[index].y, 3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = "RGB (193111194)"
      context.shadowBlur = 10;
      context.shadowColor ="RGB (193111194)"
      context.fill()
    }
    // The circle of the third line
    for (let index = 0; index < point2s.length; index++) {
      context.beginPath();
      context.arc(point2s[index].x,point2s[index].y, 3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = "RGB (293111294)"
      context.shadowBlur = 10;
      context.shadowColor ="RGB (293111294)"
      context.fill()
    }
    / / four... round
    for (let index = 0; index < point2s.length; index++) {
      context.beginPath();
      context.arc(point2s[index].x,point2s[index].y-15.3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = "RGB (293211194)"
      context.shadowBlur = 1;
      context.shadowColor ="RGB (293211194)"
      context.fill()
    }
    // Circle on line 5
    for (let index = 0; index < point2s.length; index++) {
      context.beginPath();
      context.arc(point2s[index].x,point2s[index].y-35.3.0.Math.PI * 2.true);
      context.closePath();
      context.fillStyle = "RGB (93211294)"
      context.shadowBlur = 1;
      context.shadowColor ="RGB (93211294)"
      context.fill()
    }

    context.restore();
  }

  // Draw fill polylines and circles
  drawMoreLine(context)
</script>

</body>
</html>
Copy the code

4. Multiple polyline filling diagram

I’ll just do one last case because of time. Please look forward to the better special effects cases in the back of my small book, has been on the way to progress in writing, I hope to meet you as soon as possible.

Analyzing the superposition of closed regions

// The first line closes the area
    context.beginPath();
    context.moveTo(0.0)
    for (let index = 0; index < points.length; index++) {
      context.lineTo(points[index].x,points[index].y);
    }
    context.lineTo(points[points.length-1].x,0);
    context.closePath();

    context.fillStyle="Rgba (93111194,0.5)"
    context.lineWidth=11
    context.shadowBlur = 5;
    context.fill();
    context.closePath();
Copy the code

A few words short

// The first line closes the area
    context.beginPath();
    context.moveTo(0.0)
    for (let index = 0; index < points.length; index++) {
      context.lineTo(points[index].x,points[index].y);
      // We need to transform the coordinate system because the text is reversed. And in order to facilitate the operation of each coordinate dot move to the vertex with home convenient operation
      context.save()
      context.translate(points[index].x,points[index].y)
      context.scale(1, -1)
      context.fillText(points[index].y+"".0, -10)
      // Remember that the text is drawn to complete the restoration of the coordinate system, because the line behind the drawing, does not affect the coordinate system dot is called the lower left circle.
      context.restore()
    }
    context.lineTo(points[points.length-1].x,0);
    context.closePath();
Copy the code

5. Bar chart cases

There is a bar chart mentioned in the group, I saw that there are so many front leaders in the afternoon, so many people praised this article on hydrology, so I can’t wait to add this bar chart haha. Thank you very much to the front guys. Write JS content for the first time to point out many problems in many places forgive me.

1. Draw the X-axis and scale. Look at the code comment

let marginLeft=80
let marginBootom=100
function translateCanvas(context) {
    // Change the canvas to the bottom left corner.
    context.translate(marginLeft,canvas.height-marginBootom)
    context.scale(1, -1)}// Draw X lines and scales, etc
function drawXLine(context) {
    // Draw the X-axis
    context.beginPath();
    / / starting point
    context.moveTo(0.0)
    // End point of X-axis
    context.lineTo(canvas.width-marginLeft*2.0)
    context.closePath();
    context.strokeStyle = 'rgb(0,0,0)';
    context.shadowBlur = 2;
    context.lineWidth=0.1
    context.shadowColor = 'RGB (100255255).;
    context.stroke()


    // Draw parallel lines
    context.save()
    // Calculate the width of each unit
    let heightOne=(canvas.height-marginBootom*2) /7
    for (let index=0; index<8; index++){
        context.beginPath();
        context.moveTo(0.0)
        context.lineTo(canvas.width-marginLeft*2.0)
        context.closePath();
        context.strokeStyle = 'rgb(0,0,0)';
        context.shadowBlur = 2;
        context.lineWidth=0.1
        context.shadowColor = 'RGB (100255255).;
        context.stroke()
        context.translate(0,heightOne)
    }
    context.restore()


    // Draw the scale
    context.save()
    // Calculate the width of each unit
    let widthOne=(canvas.width-marginLeft*2) /20
    for (let index=0; index<21; index++){
        context.beginPath();
        context.moveTo(0.0)
        context.lineTo(0, -5)
        context.closePath();
        context.strokeStyle = 'rgb(0,0,0)';
        context.lineWidth=0.1
        context.stroke()
        context.translate(widthOne,0)
    }
    context.closePath()
    context.restore()
}
Copy the code

The effect is as follows:

2. Draw the text below the X axis. Look at the code comment

So here’s a little bit more plotted text that you can measure by measureText how do you plot a text to the middle of the scale?

let xText=["Beijing"."Tianjin"."Hebei"."Shanxi"."Sichuan"."Guangdong"."Shanghai"."Shenzhen"."Jiangsu"."Henan"."Shanxi"."Shaanxi"."Gansu"."Inner Mongolia"."The sky"."Yuncheng"."Harbin"."Japan"."Taiwan"."Hong Kong"]
// Draw the bottom text
function drawXDownText(context){
    // Draw the scale
    context.save()
    context.scale(1, -1)
    context.beginPath();
    // Calculate the width of each unit
    let widthOne=(canvas.width-marginLeft*2) /20
    for (let index=0; index<xText.length; index++){
        const textWidth = context.measureText(xText[index]);
        // Calculate the text of the text, draw a picture to see very simple is relative position to cut to cut
        context.strokeStyle = 'RGB (111,11,111)';
        context.fillText(xText[index],widthOne/2-textWidth.width/2,textWidth.emHeightAscent)
        context.stroke()
        context.translate(widthOne,0)
    }
    context.restore()


    // Draw the text to the left of the Y-axis
    context.save()
    // This is important in order not to invert the font
    context.scale(1, -1)
    context.translate(-30.0)
    context.font = "7pt Calibri";
    let heightOne=(canvas.height-marginBootom*2) /7
    // Draw text to the left of the Y-axis
    for (let i = 0; i < 8; i++) {
        // Draw a closed region. Fill is a closed region
        context.stroke()
        // Measure text
        const textHeight = context.measureText((3000 * i).toString());
        // Sets the position of the text to draw
        context.fillText((3000 * i).toString(), 0, textHeight.emHeightAscent / 2);
        // Continue to move up after each drawing
        context.translate(0, -heightOne)
    }
    context.restore()


}
Copy the code

3. Draw large and manufactured data

⭐️⭐ ⭐ ⭐ ⭐️ whale ️ the most important point here is how to combine the actual data with the coordinate system. For example, our actual data from the background are thousands and tens of thousands. Our coordinate system is 500px tight. In fact, the simple calculation is how high the number of a unit of the actual pixel danwei=500/ maximum (for example, 2000). So 12000* Danwei is where 12000 should be in the actual canvas.

let datas=[[100.2800.9000], [150.2900.600], [300.12000.400], [500.13333.4000], [1300.2000.122], [111.3333.1111], [1111.2111.1111], [111.1611.222], [444.4444.333], [222.11111.2222], [2222.2235.11], [111.1345.1111], [1111.11111.2234], [1122.12223.12], [121.1665.111], [234.334.21]
    ,[112.12134.1211], [1212.12111.134], [124.2021.112], [1222.20345.1212], [1412.17771.1111], [111.12222.1111], [1123.121333.1111], [11112.11212.111]]// Draw the giant bar
function drawRect(context) {
    // Draw the scale
    context.save()
    context.beginPath();
    // Calculate the width of each unit
    let widthOne=(canvas.width-marginLeft*2) /20
    let widthOneRect=widthOne/3
    let heightOne=(canvas.height-marginBootom*2) /7
    let danwei=heightOne/3000
    for (let index=0; index<xText.length; index++){
        // Calculate the text of the text, draw a picture to see very simple is relative position to cut to cut
        context.fillStyle = 'RGB (189119119).;
        context.fill()
        // The first stripe
        context.fillRect(0.0,widthOneRect-1,datas[index][0]*danwei)
        context.fillStyle = 'RGB (19172172).;
        // The second stripe
        context.fillRect(widthOneRect,0,widthOneRect-1,datas[index][1]*danwei)
        context.fillStyle = 'RGB (111,73,142)';
        // The third stripe
        context.fillRect(widthOneRect*2.0,widthOneRect-1,datas[index][2]*danwei)
        context.translate(widthOne,0)
    }
    context.restore()
}
Copy the code

There will be more time behind the other…..

5. Radar series diagram

If above you all according to me to knock down, I think custom for you is no longer difficult, let’s see through the case to customize simple API and junior high school simple math calculation can bring us what?

Analysis before drawing

The convenience brought by coordinate transformation to the center of the screen to draw multiple skeleton line segments how to map the actual data to the screen to complete the line filling

1. Coordinate transformation

As shown in the image above, it is very convenient to operate if the dot is in the center of the screen. Go directly to…. I don’t know. Look at the coordinate transformation part above

<! DOCTYPE html><html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Radar map</title>
</head>
<body>
<canvas id="canvas" style="background: black"></canvas>
</body>
<script>
    const canvas = document.getElementById("canvas");
    canvas.width = 831;
    canvas.height = 706;
    // Get the drawn object
    const context = canvas.getContext("2d")
    context.translate(canvas.width/2,canvas.height/2)
    context.scale(1, -1)
    / / draw circle
    context.arc(0.0.50.0.Math.PI*2.true)
    context.strokeStyle="RGB (189142)"
    context.stroke()

</script>
</html>
Copy the code

2. Draw multiple skeleton line segments

We see that there are three skeleton lines dividing the screen into six equal parts, and we can easily solve the equation for the three line segments, right? Junior high school math I’m sure you can understand.

Yx=-tan30*x

Yx= tan30*x

 let y1= Math.tan(Math.PI/180*30(-) *300)
    let y2= Math.tan(Math.PI/180*30) *300
    context.moveTo(-300,y1)
    context.lineTo(300,y2)
    context.closePath();
    context.strokeStyle="RGB (189142)"
    context.stroke()
Copy the code

The completion of

 context.beginPath();

    // A skeleton line segment on the left
    let y1= Math.tan(Math.PI/180*30(-) *300)
    let y2= Math.tan(Math.PI/180*30) *300
    context.moveTo(-300,y1)
    context.lineTo(300,y2)
    context.strokeStyle="RGB (189142)"
    context.stroke()

    // Intermediate skeleton line
    context.moveTo(0.300)
    context.lineTo(0, -300)


    // A skeleton segment on the right
    let y11= -Math.tan(Math.PI/180*30(-) *300)
    let y22= -Math.tan(Math.PI/180*30) *300
    context.moveTo(-300,y11)
    context.lineTo(300,y22)
    context.strokeStyle="RGB (189142)"
    context.stroke()
    context.closePath();
Copy the code

Divergence of round

  for (let i = 0; i < 6; i++) {
        context.beginPath();
        / / draw circle
        context.arc(0.0.50*(i+1),0.Math.PI*2.true);
        context.strokeStyle="RGB (189142)";
        context.stroke();
        context.closePath();
    }
Copy the code

3. How does the actual data map to the screen

Similarly, the radius of our circle can be viewed as the length of each skeleton coordinate axis, whereas our actual data is just the length data how do we map the length number to each irregular skeleton coordinate axis? Again, simple math. For example, let’s take a number 250 as shown below where two white dotted lines meet. Our actual 250 represents the length from the dot to the focal point. But we need to position ourselves in the coordinate system and we need to figure out the virtual coordinates of x,y in the coordinate system. In the same simple junior high school math, it is not difficult to conclude that (x,y) =(lengthcson30,lenghtsin30) if you carefully analyze all the coordinates on each skeleton axis satisfy (x,y) =(lengthcson30,lenghtsin30). So let’s go to the code and see what it looks like

 // Draw the network cable
    const datas = [[70.100.20.5.21.99], [100.120.50.75.121.99], [117.211.259.232.190.200], [217.240.259.282.190.120]].for (let i = 0; i < datas.length; i++) {
        for (let index=0; index<datas[i].length; index++){ context.beginPath()// Start drawing clockwise in the upper right corner
            context.lineTo(datas[i][0] *Math.cos(Math.PI/180*30),datas[i][0] *Math.sin(Math.PI/180*30))
            context.lineTo(datas[i][1] *Math.cos(Math.PI/180*30),-datas[i][1] *Math.sin(Math.PI/180*30))
            context.lineTo(0,-datas[i][2])
            context.lineTo(-datas[i][3] *Math.cos(Math.PI/180*30),-datas[i][3] *Math.sin(Math.PI/180*30))
            context.lineTo(-datas[i][4] *Math.cos(Math.PI/180*30),datas[i][4] *Math.sin(Math.PI/180*30))
            context.lineTo(0,datas[i][5])
            context.fillStyle="rgba(189,142,16,0.09)"context.fill() context.closePath(); }}// Draw the edge lines of the network cable
    for (let i = 0; i < datas.length; i++) {
        for (let index=0; index<datas[i].length; index++){ context.beginPath()// Start drawing clockwise in the upper right corner
            context.lineTo(datas[i][0] *Math.cos(Math.PI/180*30),datas[i][0] *Math.sin(Math.PI/180*30))
            context.lineTo(datas[i][1] *Math.cos(Math.PI/180*30),-datas[i][1] *Math.sin(Math.PI/180*30))
            context.lineTo(0,-datas[i][2])
            context.lineTo(-datas[i][3] *Math.cos(Math.PI/180*30),-datas[i][3] *Math.sin(Math.PI/180*30))
            context.lineTo(-datas[i][4] *Math.cos(Math.PI/180*30),datas[i][4] *Math.sin(Math.PI/180*30))
            context.lineTo(0,datas[i][5])
            context.strokeStyle="RGB (189142)"context.stroke() context.closePath(); }}Copy the code

Nuggets text limit, will be another chapter!!!!!

Five, the summary

For me, a mobile user who has barely used HTML5 and JS, this can be done in a day. It’s not easy for the big guys on the front end. Of course, the weird thing is that I’ve asked a lot of front-end developers, and I don’t know much about customizations. When I ask about diagrams, ECharts often says, maybe some companies don’t have strict UI. Development in Echarts basically looking for similar charts or designers directly based on Echarts to choose the design, may be a variety of reasons domestic UI basic tend to be the same, technology is changing,UI design should also have a unique style, some characteristics. Wouldn’t it be better if we had the ability to customize? When you come across unusual designs you can do whatever you want, and that’s a wonderful thing.

I think the following charts are nothing more than giant, radian, etc. Add canvas transform, fill gradient, text drawing? It’s time to show off your skills

Of course there is a lot of customization in ECharts. If you’re serious about this post, you should know how to customize your content. As for good operation, of course, it is inseparable from gesture and animation, and then we will start gesture and animation to gradually transition to the K line, yes, the K line.