Vue+canvas realizes the effect of refreshing waterfall chart from top to bottom in real time (similar to QT)

Vue+canvas realizes the effect of refreshing waterfall chart from top to bottom in real time (similar to QT)

Without further ado, here is a demo picture. The functions implemented are: legend on the left, waterfall chart on the right, and data information corresponding to the current coordinates popping up when the mouse is moved (there is room for optimization, please feel free to play with it).

Plugins used in the illustration

It is recommended to install the npm plugin colormap

Waterfall chart body

I won’t explain it here. They are all native tags and vue-bound events. You can encapsulate them into components according to the actual project situation. I have written them together here.

<template>
    <div>
        <div class="content">
            <div class="neirong">
                <!--Legend-->
                <div class="legend">
                    <canvas ref="legend"></canvas>
                </div>
                <!--Waterfall Chart-->
                <div class="waterFall" ref="waterFallContent"
                     @mousemove="waterFallMove($event)"
                     @mouseleave="waterFallLeave"
                >
                    <canvas ref="waterFall"></canvas>
                    <!--Move the mouse into the pop-up box-->
                    <div ref="tip" class="tip"></div>
                </div>
            </div>
        </div>
    </div>
</template>

Here is the Data used

  • colormap: color library
  • legend: legend
  • waterFall: Waterfall Chart
  • waterFallList: waterfall chart source data
  • waterFallIndex: The counting index used by the waterfall chart timer
  • waterFallCopyList: waterfall chart two-dimensional array (used for temporary storage of display data)
  • waterFallIntervals: waterfall timer
  • waterFallWidth: width of the waterfall chart (length of data returned by the backend)
  • waterFallHeight: The height of the waterfall (can also be understood as the number of renderings, for example, 30 renderings are completed)
  • maxNum: maximum value of the legend
  • minNum: minimum value of the legend
<script>
    export default {
        name: "index",
        data() {
            return {
                colormap: [],
                legend: null,
                waterFall: null,
                waterFallList: [],
                waterFallIndex: 0,
                waterFallCopyList: [],
                waterFallIntervals: null,
                waterFallWidth: 0,
                waterFallHeight: 0,
                maxNum: 10,
                minNum: 0
            }
        },

The following is a specific method. It is written roughly. Please read it carefully. If you find it useful, please take it away. If there are any shortcomings, you can modify it freely.

I won't explain the method call here, leaving the page will destroy the timer.

mounted() {
            let dx = this
            dx.setColormap()
            dx.createLegendCanvas()
            dx.queryChartList()
        },
        destroyed() {
            let dx = this
            clearInterval(dx.waterFallIntervals)
        },

Create a color library

For details, please refer to the official website of the above plug-in for detailed introduction.

setColormap() {
      let dx = this
      let colormap = require('colormap')
      dx.colormap = colormap({
          colormap: 'jet',
          nshades: 150,
          format: 'rba',
          alpha: 1,
   })
},

Creating a Legend

createLegendCanvas() {
                let dx = this
                let legendRefs = dx.$refs.legend
                dx.legend = legendRefs.getContext('2d')
                let legendCanvas = document.createElement('canvas')
                legendCanvas.width = 1
                let legendCanvasTemporary = legendCanvas.getContext('2d')
                const imageData = legendCanvasTemporary.createImageData(1, dx.colormap.length)
                for (let i = 0; i < dx.colormap.length; i++) {
                    const color = dx.colormap[i]
                    imageData.data[imageData.data.length - i * 4 + 0] = color[0]
                    imageData.data[imageData.data.length - i * 4 + 1] = color[1]
                    imageData.data[imageData.data.length - i * 4 + 2] = color[2]
                    imageData.data[imageData.data.length - i * 4 + 3] = 255
                }
                legendCanvasTemporary.putImageData(imageData, 0, 0)
                dx.legend.drawImage(legendCanvasTemporary.canvas, 
                0, 0, 1, dx.colormap.length, 50, 0, 200, dx.legend.canvas.height)
            },

Creating a waterfall chart

 createWaterFallCanvas() {
                let dx = this
                let waterFall = dx.$refs.waterFall
                dx.waterFall = waterFall.getContext('2d')
                waterFall.width = dx.waterFallWidth
                waterFall.height = dx.$refs.waterFallContent.offsetHeight
            },

Draw a single line image

 rowToImageData(data) {
                let dx = this
                if (dx.$refs.waterFallContent !== undefined) {
                    let canvasHeight = Math.floor(dx.$refs.waterFallContent.offsetHeight / dx.waterFallHeight)
                    let imgOld = dx.waterFall.getImageData(0, 0, dx.waterFallWidth, canvasHeight * dx.waterFallIndex + 1)
                    const imageData = dx.waterFall.createImageData(data.length, 1)
                    for (let i = 0; i < imageData.data.length; i += 4) {
                        const cindex = dx.colorMapData(data[i / 4], 0, 130)
                        const color = dx.colormap[cindex]
                        imageData.data[i + 0] = color[0]
                        imageData.data[i + 1] = color[1]
                        imageData.data[i + 2] = color[2]
                        imageData.data[i + 3] = 255
                    }
                    for (let i = 0; i < canvasHeight; i++) {
                        dx.waterFall.putImageData(imageData, 0, i)
                    }
                    dx.waterFall.putImageData(imgOld, 0, canvasHeight)
                }
            },

Returns the Colormap color corresponding to the data

colorMapData(data, outMin, outMax) {
                let dx = this
                if (data <= dx.minNum) {
                    return outMin
                } else if (data >= dx.maxNum) {
                    return outMax
                }
                return Math.round(((data - dx.minNum) / (dx.maxNum - dx.minNum)) * outMax)
            },

Move the mouse into the waterfall chart

            waterFallMove(event) {
                let dx = this
                let dataWidth = (dx.$refs.waterFallContent.offsetWidth / dx.waterFallWidth).toFixed(2)
                let dataHeight = (dx.$refs.waterFallContent.offsetHeight / dx.waterFallHeight).toFixed(2)
                let x = Math.floor(event.offsetX / dataWidth)
                let y = Math.floor(event.offsetY / dataHeight)
                try {
                    dx.$refs.tip.innerHTML = 'Value:' + JSON.parse(JSON.stringify(dx.waterFallCopyList[y][x]))
                    let xx = event.offsetX + 5
                    let yy = event.offsetY - 20
                    if (event.offsetX > 1300) {
                        xx = event.offsetX - 160
                        yy = event.offsetY - 20
                    }
                    dx.$refs.tip.style.position = 'absolute'
                    dx.$refs.tip.style.left = xx + 'px'
                    dx.$refs.tip.style.top = yy + 'px'
                    dx.$refs.tip.style.display = 'block'
                } catch (e) {
                    dx.$refs.tip.style.display = 'none'
                }
            },

Move the mouse out of the waterfall chart

waterFallLeave() {
                let dx = this
                dx.$refs.tip.style.display = 'none'
            },

Waterfall chart fake data simulation

queryChartList() {
                let dx = this
                dx.waterFallWidth = 1500
                dx.waterFallHeight = 30
                let data = []
                for (let i = 0; i < 1500; i++) {
                    data.push(Math.floor(Math.random() * (20 - 1)) + 1)
                }
                if (dx.waterFall === null) {
                    dx.createWaterFallCanvas(data.length)
                }
                dx.rowToImageData(data)
                dx.waterFallCopyList.unshift(data)
                dx.waterFallIndex++
                if (dx.waterFallIndex > dx.waterFallHeight) {
                    dx.waterFallCopyList.pop()
                }
                dx.waterFallIntervals = setTimeout(() => {
                    dx.queryChartList()
                }, 1000)
            },

Style Code

.neirong {
        width: 1800px;
        height: 100%;
        margin: 80px auto;
        display: flex;
        justify-content: center;
    }

    .legend {
        width: 25px;
        height: 500px;
    }

    canvas {
        width: 100%;
        height: 100%;
    }

    .waterFall {
        width: 1500px;
        height: 500px;
        position: relative;
    }

    .tip {
        pointer-events: none;
        display: none;
        background-color: #0404049e;
        border-radius: 10px;
        color: #fff;
        padding: 10px;
        box-sizing: border-box;
    }

At this point, the Demo can basically run without any errors. The code is not very advanced. I am also a beginner and this is my first time writing an article. I hope the big guys can give me some better suggestions and I will study hard. I also hope that friends who encounter similar requirements and have no ideas can learn from my experience of stepping into the pit and grow faster.

This is the end of this article about how vue+canvas achieves real-time data refresh from top to bottom waterfall chart effect (similar to QT). For more related vue+canvas real-time refresh waterfall chart content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Detailed explanation of the implementation principle of Vue2.0/3.0 two-way data binding
  • Analysis on the problem of data loss caused by forced refresh of vuex
  • How does Vue track data changes?
  • Solution to Vue data assignment problem
  • Vue resets data to its initial state
  • Analysis and solution of data loss during Vue component value transfer
  • SpringBoot+Vue realizes data adding function
  • Handwritten Vue2.0 data hijacking example
  • Vue data two-way binding implementation method
  • Avoid abusing this to read data in data in Vue
  • Design a data collector with vue

<<:  Detailed explanation of how to solve the conflict of project URLs caused by setting the default path of Tomcat

>>:  Mysql 5.7.18 Using MySQL proxies_priv to implement similar user group management

Recommend

MySQL 8.0.21 installation steps and problem solutions

Download the official website First go to the off...

How to implement the prototype pattern in JavaScript

Overview The prototype pattern refers to the type...

In-depth understanding of Vue's plug-in mechanism and installation details

Preface: When we use Vue, we often use and write ...

Solution to 404 Problem of Tomcat Installation in Docker

Find the containerID of tomcat and enter the toma...

Example of usage of keep-alive component in Vue

Problem description (what is keep-alive) keep-ali...

Summary of new usage of vi (vim) under Linux

I have used the vi editor for several years, but ...

Vue+Websocket simply implements the chat function

This article shares the specific code of Vue+Webs...

Three ways to communicate between React components (simple and easy to use)

Table of contents 1. Parent-child component commu...

Install Centos7 using Hyper-v virtual machine

Table of contents introduce Prepare Download syst...

Detailed steps for adding hosts you need to monitor in zabbix

Add monitoring host Host 192.168.179.104 is added...

Detailed explanation of global parameter persistence in MySQL 8 new features

Table of contents Preface Global parameter persis...

A brief analysis of MySQL locks and transactions

MySQL itself was developed based on the file syst...

Sharing tips on using scroll bars in HTML

Today, when we were learning about the Niu Nan new...

Directory permissions when creating a container with Docker

When I was writing a project yesterday, I needed ...