Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article also participated in the “Digitalstar Project” to win a creative gift package and creative incentive money

preface

Blessed by Buddha, there is no bug. Hello everyone! I am across the sea!

Sometimes in a project we have to take a prototype from the UI and turn it into a concrete page that uses components that are not readily available. At this point, you need to implement these specific components yourself.

These are components that you can use, that are generic to you, that you can reuse.

Earlier, when we were still using H5, we realized this function once. Now our company’s technology stack has been transferred to Vue. Recently, a project needs this function, so we need to convert the previous code into Vue again

Introduction to implementation process

The effect is as follows:

Don't copy the code just yet, I'll show you the whole thing at the end

Let’s start with the implementation

The effect to be done is: after the dot popbox appears, an animated dashboard is displayed.

Break down the implementation of this feature:

  1. When a value changes to a new value, it is a gradual process;
  2. There is an elastic animation effect as the dashboard values change
  3. There is a vertical line at the end of the arc, as a pointer to the dashboard, when the dashboard value changes, there is an elastic animation effect.

Because I have checked the data before, D3 was used for H5 earlier, so I will continue to use D3 this time

D3 portal

Ps: MY D3 version at the time was D3_V5

Draw the dashboard

  1. So let’s define asvgChemical element:
<svg id="myGauge" width="150" height="150" ></svg>
Copy the code
  1. Then, declare some variables for initialization:
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = defined parameters need to use the dashboard = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// The height and width of SVG can also be obtained from the width and height attributes of SVG
let width = 158;
let height = 158;
// Inner and outer radius of the arc
let innerRadius = 35;
let outerRadius = 60;
// Start and end angles of the arc
let arcMin = -Math.PI * 2 / 3;
let arcMax = Math.PI * 2 / 3;
let valueLabel = 0; // The value of AQI displayed on the dashboard
let foreground; // Represents the length of an AQI arc, which is another arc object. The undefined value defaults to undefined
let tick; // Indicates the pointer to the end of the QI arc. Undefined value defaults to undefined
let arc; // Represents the arc drawing function, undefined value default undefined
let background; // Set the color of the background arc. Undefined values default to undefined
let valueLevel; // Sets the severity level of AQI arcs. Undefined values default to undefined
let percentage; // Indicates the arc length corresponding to the AQI value, which is between 0 and 1. Undefined value defaults to undefined
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = defined parameters need to use the dashboard = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Copy the code
  1. When it comes to the Angle of rotation, prepare an Angle of radiansangleToDegree
// The Angle turns radians
function angleToDegree(angle) {
  return angle * 180 / Math.PI;
}
Copy the code
  1. D3 property tween (gradient) animation method that causes an arc to gradient from the current Angle to a new Angle
// Define a tween (gradient) animation method for the d property to gradient an arc from the current Angle to a new Angle.
function arcTween(newAngle) {
  // let self = this;
  return (d) = > {
    const interpolate = d3.interpolate(d.endAngle, newAngle); // Find an interpolation between the two values
    return (t) = > {
      d.endAngle = interpolate(t); // Calculate interpolation based on transition time t and assign it to endAngle
      // Returns the new "d" attribute value
      return arc(d);
    };
  };
}
Copy the code
  1. My dashboard will display the value of AQI, different AQI values show different colors, define a style function
// Set the AQI dashboard style in the popbox according to the AQI value
/** * isAQI_arc: true to return the value of AQI from 1 to 500, false to return the value of AQI from 1, 2, 3, 4, 5
function AQIStyle(isAQI_arc, value) {
  let level = 0;
  let AQI_arc = '# 717171';
  const valueNum = Number(value);
  switch (true) {
    case valueNum > 0 && valueNum <= 50:
      if (isAQI_arc) {
        AQI_arc = '#01e401';
      } else {
        level = 1;
      }
      break;
    case valueNum > 50 && valueNum <= 100:
      if (isAQI_arc) {
        AQI_arc = '#ffff00';
      } else {
        level = 2;
      }
      break;
    case valueNum > 100 && valueNum <= 150:
      if (isAQI_arc) {
        AQI_arc = '#ff7e00';
      } else {
        level = 3;
      }
      break;
    case valueNum > 150 && valueNum <= 200:
      if (isAQI_arc) {
        AQI_arc = '#ff0000';
      } else {
        level = 4;
      }
      break;
    case valueNum > 200 && valueNum <= 300:
      if (isAQI_arc) {
        AQI_arc = '#99004c';
      } else {
        level = 5;
      }
      break;
    case valueNum > 300:
      if (isAQI_arc) {
        AQI_arc = '#7e0023';
      } else {
        level = 6;
      }
      break;
    default:
      if (isAQI_arc) {
        AQI_arc = '# 717171';
      } else {
        level = 0;
      }
      break;
  }
  if (isAQI_arc) {
    return AQI_arc;
  } else {
    returnlevel; }}Copy the code
  1. And then there’sD3 API operation, as well asOperations in SVGThe API used in D3 and SVG is introduced briefly
  • D3 operates the portal
// Construct a new arc generator using the default Settings: create an instance of an arc
d3.arc()

const arc = d3.arc(); // Create an arc instance and set some properties
arc({
  innerRadius: 0.// Set the radius of the arc
  outerRadius: 100.// Set the radius of the arc
  startAngle: 0.// Set the beginning of the arc
  endAngle: Math.PI / 2 // Set the arc bottom cutoff radian
}); 
Copy the code
// Find the DOM element you need to manipulate to get SVG
d3.select('#id')
Copy the code
  • Perform operations on SVG
// Get the SVG elements and convert the origin to the center of the canvas so that we do not need to specify their positions separately when we create arcs later
let svg = d3.select("#myGauge")
let g = svg.append("g").attr("transform"."translate(" + width / 2 + "," + height / 2 + ")");
Copy the code
  • Add text (title, value, unit) to dashboard
// Add the dashboard title
g.append("text").attr("class"."gauge-title")
    .style("alignment-baseline"."central") // Align relative to the parent element
    .style("text-anchor"."middle") // Text anchor point, centered
    .attr("y", -45)   // The distance to the center
    .text("CPU Usage");
// Add the value displayed on the dashboard, declare a variable because it will be updated later
var valueLabel = g.append("text").attr("class"."gauge-value")
    .style("alignment-baseline"."central") // Align relative to the parent element
    .style("text-anchor"."middle") // Text anchor point, centered
    .attr("y".25)    // The distance to the center
    .text(12.65); 
// Add a unit of value to the dashboard display
g.append("text").attr("class"."gauge-unity")
    .style("alignment-baseline"."central") // Align relative to the parent element
    .style("text-anchor"."middle") // Text anchor point, centered
    .attr("y".40)    // The distance to the center
    .text("%");
Copy the code
  • D3To make theSVGFigure, andEchartsDraw theCanvasA very important advantage is that it can be usedCSSdefineSVGStyle. For example, the dashboard title here looks like this:
.gauge-title{
    font-size: 10px;
    fill: #A1A6AD;
}
Copy the code
  • Add background arc
// Add background arc
var background = g.append("path")
    .datum({endAngle:arcMax})  // Pass the endAngle argument to the arc method
    .style("fill"."# 444851")
    .attr("d", arc);
Copy the code
  • Add the arc representing the percentage wherepercentageIt’s the percentage that I want to represent, decimal from 0 to 1.
// Calculate the end Angle of the arc
var currentAngle = percentage*(arcMax-arcMin) + arcMin
// Add another layer of arc to indicate the percentage
var foreground = g.append("path")
    .datum({endAngle:currentAngle})
    .style("fill"."# 444851")
    .attr("d", arc);
Copy the code
  • Add a pointer marker to the end of the arc
var tick = g.append("line")
    .attr('class'.'gauge-tick')
    .attr("x1".0)
    .attr("y1", -innerRadius)
    .attr("x2".0)
    .attr("y2", -(innerRadius + 12))  // Define the line position, default is in the center of the arc, 12 is the length of the pointer
    .style("stroke"."#A1A6AD")
    .attr('transform'.'rotate('+ angleToDegree(currentAngle) +') ')
Copy the code

The rotate parameter is a degree. Math.PI corresponds to 180, so you need to define an angleToDegree method to convert currentAngle. This is the angleToDegree method in step 3 above

How do I jump the value of this dashboard from one value to another?

Update the arc

Modify the value below the arc:

valueLabel.text(newValue)
Copy the code

Updating the arc is a bit more troublesome. The specific idea is to modify the endAngle of the arc and the transform value of the pointer at the end of the arc. In the implementation process, need to use the API:

  • selection.transition:Github.com/d3/d3-trans…
  • transition.attrTween:Github.com/d3/d3-trans…
  • d3.interpolate:Github.com/d3/d3-inter…
  1. Update the arc, whereangleIs the end Angle of the new arc
// Update the arc and set the gradient effect
foreground.transition()
    .duration(750)
    .ease(d3.easeElastic)   // Set the bouncing effect
    .attrTween("d", arcTween(angle)); 
Copy the code

The arcTween method is defined as follows. It returns a tween (gradient) animation method of the D property that makes an arc gradient from the current Angle to a new Angle.

arcTween(newAngle) {
    let self=this
    return function(d) {
        var interpolate = d3.interpolate(d.endAngle, newAngle); // Find an interpolation between the two values
        return function(t) {
            d.endAngle = interpolate(t);    // Calculate interpolation based on transition time t and assign it to endAngle
            return arc(d); // Returns the new "d" attribute value
        };  
    };
}
Copy the code

Refer to the Arc Tween comment for a more detailed explanation of this method.

  1. The principle of updating the pointer at the end of the arc is the same as above, whereinoldAngleIs the end Angle of the old arc.
// Update the pointer marker at the end of the arc and set the gradient effect
tick.transition()
    .duration(750)
    .ease(d3.easeElastic)   // Set the bouncing effect
    .attrTween('transform'.function(){ // Set the gradient of the "transform" property as arcTween method above
        var i = d3.interpolate(angleToDegree(oldAngle), angleToDegree(newAngle));    / / get the interpolation
        return function(t) {
            return 'rotate('+ i(t) +') '
        };
    })
Copy the code

Theoretically this way, we’re done. Next, we will implement the landing!

The implementation code

I implemented it based on Vue2

  1. inindex.htmlThe introduction ofd3.js

  1. inAPP.vueTo set my custom style
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = real-time AQI bounced panel style start = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
.gauge-title-0{
    font-size: 20px;
    fill: # 717171;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-1{
    font-size: 20px;
    fill: #01e401;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-2{
    font-size: 20px;
    fill: #ffff00;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-3{
    font-size: 20px;
    fill: #ff7e00;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-4{
    font-size: 20px;
    fill: #ff0000;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-5{
    font-size: 20px;
    fill: #99004c;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-title-6{
    font-size: 20px;
    fill: #7e0023;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-0{
    font-size: 28px;
    fill: # 717171;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-1{
    font-size: 28px;
    fill: #01e401;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-2{
    font-size: 28px;
    fill: #ffff00;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-3{
    font-size: 28px;
    fill: #ff7e00;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-4{
    font-size: 28px;
    fill: #ff0000;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-5{
    font-size: 28px;
    fill: #99004c;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
.gauge-value-6{
    font-size: 28px;
    fill: #7e0023;
    border-radius: 5px;
    padding: 3px;
    text-shadow: # 000 0.5 px. 0.5 px. 0.5 px..# 000 0 0.5 px. 0.# 000 -0.5 px. 0 0.# 000 0 -0.5 px. 0;
}
/ * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = real-time AQI bounced panel style end = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
Copy the code
  1. Custom ComponentsDynamicDashboard.vue
<template>
  <div>
    <svg id="myGauge" width="150" height="150" ></svg>
  </div>
</template>

<script>
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = defined parameters need to use the dashboard = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// The height and width of SVG can also be obtained from the width and height attributes of SVG
let width = 158;
let height = 158;
// Inner and outer radius of the arc
let innerRadius = 35;
let outerRadius = 60;
// Start and end angles of the arc
let arcMin = -Math.PI * 2 / 3;
let arcMax = Math.PI * 2 / 3;
let valueLabel = 0; // The value of AQI displayed on the dashboard
let foreground; // Represents the length of an AQI arc, which is another arc object. The undefined value defaults to undefined
let tick; // Indicates the pointer to the end of the QI arc. Undefined value defaults to undefined
let arc; // Represents the arc drawing function, undefined value default undefined
let background; // Set the color of the background arc. Undefined values default to undefined
let valueLevel; // Sets the severity level of AQI arcs. Undefined values default to undefined
let percentage; // Indicates the arc length corresponding to the AQI value, which is between 0 and 1. Undefined value defaults to undefined
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = defined parameters need to use the dashboard = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// The Angle turns radians
function angleToDegree(angle) {
  return angle * 180 / Math.PI;
}
// Define a tween (gradient) animation method for the d property to gradient an arc from the current Angle to a new Angle.
function arcTween(newAngle) {
  // let self = this;
  return (d) = > {
    const interpolate = d3.interpolate(d.endAngle, newAngle); // Find an interpolation between the two values
    return (t) = > {
      d.endAngle = interpolate(t); // Calculate interpolation based on transition time t and assign it to endAngle
      // Returns the new "d" attribute value
      return arc(d);
    };
  };
}

// Set the AQI dashboard style in the popbox according to the AQI value
/** * isAQI_arc: true to return the value of AQI from 1 to 500, false to return the value of AQI from 1, 2, 3, 4, 5
function AQIStyle(isAQI_arc, value) {
  let level = 0;
  let AQI_arc = '# 717171';
  const valueNum = Number(value);
  switch (true) {
    case valueNum > 0 && valueNum <= 50:
      if (isAQI_arc) {
        AQI_arc = '#01e401';
      } else {
        level = 1;
      }
      break;
    case valueNum > 50 && valueNum <= 100:
      if (isAQI_arc) {
        AQI_arc = '#ffff00';
      } else {
        level = 2;
      }
      break;
    case valueNum > 100 && valueNum <= 150:
      if (isAQI_arc) {
        AQI_arc = '#ff7e00';
      } else {
        level = 3;
      }
      break;
    case valueNum > 150 && valueNum <= 200:
      if (isAQI_arc) {
        AQI_arc = '#ff0000';
      } else {
        level = 4;
      }
      break;
    case valueNum > 200 && valueNum <= 300:
      if (isAQI_arc) {
        AQI_arc = '#99004c';
      } else {
        level = 5;
      }
      break;
    case valueNum > 300:
      if (isAQI_arc) {
        AQI_arc = '#7e0023';
      } else {
        level = 6;
      }
      break;
    default:
      if (isAQI_arc) {
        AQI_arc = '# 717171';
      } else {
        level = 0;
      }
      break;
  }
  if (isAQI_arc) {
    return AQI_arc;
  } else {
    returnlevel; }}/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = set dashboard start = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
function drawDashboard_AQI(info) {
  arc = d3.arc().innerRadius(innerRadius).outerRadius(outerRadius).startAngle(arcMin);
  // Get the DOM element to manipulate
  let svg = d3.select('#myGauge');
  // Set attributes for the DOM element
  let g = svg.append('g').attr('transform'.'translate(' + width / 2 + ', ' + height / 2 + ') ');

  // Add the value displayed on the dashboard, declare a variable because it will be updated later
  valueLabel = g.append('text').attr('class'.'gauge-value-' + AQIStyle(false, info.AQI))
    .style('alignment-baseline'.'central') // Align relative to the parent element
    .style('text-anchor'.'middle') // Text anchor point, centered
    .attr('y'.0) // The distance to the center
    .text(The '-');
  // Add the level of the values displayed on the dashboard
  valueLevel = g.append('text').attr('class'.'gauge-title-' + AQIStyle(false, info.AQI))
    .style('alignment-baseline'.'central') // Align relative to the parent element
    .style('text-anchor'.'middle') // Text anchor point, centered
    .attr('y'.45) // The distance to the center
    .text(The '-'); // There is no unit yet

  // Add background arc
  background = g.append('path')
    .datum({ endAngle: arcMax }) // Pass the endAngle argument to the arc method
    .style('fill'.'# 717171')
    .attr('d', arc);

  // Calculate the end Angle of the arc
  percentage = 0; // percentage is the percentage to be represented. It is a decimal from 0 to 1.
  const currentAngle = (percentage * (arcMax - arcMin) + arcMin);
  // Add another layer of arc to indicate the percentage
  foreground = g.append('path')
    .datum({ endAngle: currentAngle })
    .style('fill', AQIStyle(true, info.AQI))
    .attr('d', arc);

  tick = g.append('line')
    .attr('class'.'gauge-tick')
    .attr('x1'.0)
    .attr('y1', -innerRadius)
    .attr('x2'.0)
    .attr('y2', -(innerRadius + 32)) // Define the line position, default is in the center of the arc, 12 is the length of the pointer
    .style('stroke'.'#A1A6AD')
    .attr('transform'.'rotate(' + angleToDegree(currentAngle) + ') ');
}

/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = set dashboard end = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// Preload open dialog box
function initPopAirAQI(info) {
  // Draw an arc
  drawDashboard_AQI(info);

  setTimeout(() = > {
    let endAngle_AQI = 1;
    // if (info.AQI == undefined || info.AQI === '--') {
    // endAngle_AQI = 0;
    // } else {
    // endAngle_AQI = (Number(info.AQI) / 500) <= 1 ? (Number(info.AQI) / 500) : 1; // The arc length corresponding to the aQI value
    // }
    let newAngle = endAngle_AQI * (arcMax - arcMin) + arcMin; // End Angle of the new arc
    valueLabel.text(info.AQI); // Set the AQI value
    valueLevel.text(info.Level); // Set the level of AQI
    // Update the arc and set the gradient effect
    foreground.transition()
      .duration(750)
      .ease(d3.easeElastic) // Set the bouncing effect
      .attrTween('d', arcTween(newAngle)); // newAngle: end Angle of the new arc
    // Update the pointer marker at the end of the arc and set the gradient effect
    tick.transition()
      .duration(750)
      .ease(d3.easeElastic) // Set the bouncing effect
      .attrTween('transform'.() = > { // Set the gradient of the "transform" property as arcTween method above
        // 0: is the end Angle of the old arc. NewAngle: End Angle of the new arc
        const i = d3.interpolate(angleToDegree(0), angleToDegree(newAngle)); / / get the interpolation
        return (t) = > {
          return 'rotate(' + i(t) + ') ';
        };
      });
  }, 1000);
}

export default {
  data() {
    return {
      info: {
        AQI: 0.Level: 'best',}}; },methods: {},mounted() {
    this.$event.$on('AqiData'.(v) = > { // The arrow function must be used because this accepts the passed value
      this.info = v;
      // console.log(this.info);

      setTimeout(() = > {
        initPopAirAQI(this.info);
      }, 250);
    });
  },
  created() {
    // The bus unbinds the previous event before receiving it
    this.$event.$off('AqiData'); }};</script>

<style lang="scss" scoped>

</style>

Copy the code

Note:

  1. becaused3.jsIs in theindex.htmlIf you type code to set syntax checking, then in this component,d3This variable it’s going to give you a bang for your buck and prompt you to say thisd3Undefined, never mind that, the code will work fine if you make a strictEslint detectionSo you’re using itd3Add a separate one to itSkip comments that esLint detectsCan be

  1. You can see that I’m finally passingbusBecause of business needs, I want to display this component in a popup, and the popup is based onleafletTo do it. Yeah, that’s rightleafletThe map guy. My project iswebgisThe items I found traditionalpropsThe value andvuexI couldn’t get the values through, and then I found outbusYes, so I’ll use it herebusTo do.

The cartridge component extends through vue.extend (cartridge component); Method loading, and then through the Leaflet popup method loaded in, and this dashboard component is loaded in the frame component, maybe Vue extend() and Leaflet API between some THINGS I haven’t understood, only bus can go through, later free to study

If you’re in a normal situation, THEN I suggest you use props or vuex

Now that you’ve seen this, please give me a thumbs up before you go, because your thumbs up means a lot to me

Refer to the reading

Feel the guidance of Evelynzzz big man’s H5 implementation