Создайте график в реальном времени с помощью Vue.js

В последнее время данные стали очень важной частью нашей жизни, и понимание того, что данные одинаково важны. Нет смысла иметь данные, если вы не можете отслеживать или анализировать их, особенно если эти данные имеют какое-либо отношение к финансам.

  • Просмотров |

В последнее время данные стали очень важной частью нашей жизни, и понимание того, что данные одинаково важны. Нет смысла иметь данные, если вы не можете отслеживать или анализировать их, особенно если эти данные имеют какое-либо отношение к финансам.

Вот почему мы будем строить график отслеживания расходов и доходов, используя функции реального времени с помощью Pusher. На нашей интерактивной информационной панели будет отображаться линейная диаграмма, отображающая ваши доходы и расходы на каждый день. Вы сможете добавить новые расходы и доход и посмотреть обновление диаграммы в режиме реального времени.

Диаграмма приборной панели будет питаться от Node.js + Express в качестве внутреннего сервера и Vue + vue-chartjs для интерфейса, загруженного vue-cl.

Подделывание приложения с помощью vue-cli

vue-cli — это простой CLI для проектов Vue.js для лесов. Мы установим vue-cli, а затем используем его для загрузки приложения с помощью шаблона webpack со следующими командами:

npm install -g vue-cli

vue init webpack-simple realtime-chart-pusher
  • Совет: Простой шаблон webpack — это простая настройка webpack + vue-loader для быстрого прототипирования. Вы можете узнать больше об этом здесь.

Настройка сервера Node.js

Следующее, что нужно сделать, это настроить сервер, который поможет нам общаться с Pusher. Я собираюсь предположить, что и Node, и npm установлены в вашей системе. Затем мы установим зависимости, которые мы будем использовать для сервера Node.

npm install body-parser express nodemon pusher
  • Совет: Nodemon будет просматривать файлы в каталоге, в котором запускался nodemon, и если какие-либо файлы будут изменены, nodemon автоматически перезапустит ваше приложение-приложение.

Еще одна вещь, нам понадобится точка входа/файл для нашего Node-сервера. Мы можем сделать это, создав файл server.js в корневом каталоге приложения.

Настройка Pusher

Чтобы реализовать функциональность в реальном времени, нам понадобится мощность Pusher. Если вы еще этого не сделали, зарегистрируйтесь в учетной записи Pusher и создайте новое приложение. Когда ваше новое приложение будет создано, получите свой app_id, ключи и кластер из панели инструментов Pusher.

Настройка приложения

Теперь, когда у нас есть учетная запись Pusher, и установили зависимости, необходимые для бэкэнд Node.js, давайте создадим.

Давайте напишем код для файла server.js.

const express = require('express');
const path = require('path');
const bodyParser = require("body-parser");
const app = express();
const Pusher = require('pusher');

const pusher = new Pusher({
    appId: 'YOUR_APP_ID',
    key: 'YOUR_APP_KEY',
    secret: 'YOUR_APP_SECRET',
    cluster: 'eu',
    encrypted: true
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname + '/app')));

app.set('port', (process.env.PORT || 5000));

app.listen(app.get('port'), function() {
    console.log('Node app is running on port', app.get('port'));
});

Давайте посмотрим, что здесь происходит. Мы требуем Express, path, body-parser и Pusher, и мы инициализировали express() с приложением.

Мы используем body-parser для извлечения всей части входящего потока запросов и выставляем его на req.body.

Пушер также инициализируется учетными данными приложения и кластером из панели управления. Обязательно обновите это, иначе сервер узла не будет подключен к панели управления. Наконец, сервер Node будет работать на 5000 портах.

Следующее, что нужно сделать, это определить маршрут нашего приложения, а также добавить макет данных для диаграммы расходов и доходов. Обновите файл server.js следующим образом.

let expensesList = {
    data: [
        {
            date: "April 15th 2017",
            expense: 100,
            income: 4000
        },
        {
            date: "April 22nd 2017",
            expense: 500,
            income: 2000
        },
        {
            date: "April 24th 2017",
            expense: 1000,
            income: 2300
        },
        {
            date: "April 29th 2017",
            expense: 2000,
            income: 1234
        },
        {
            date: "May 1st 2017",
            expense: 500,
            income: 4180
        },
        {
            date: "May 5th 2017",
            expense: 4000,
            income: 5000
        },
    ]
}

Во-первых, у нас есть объект списка расходов с данными, содержащими расходы и доход за определенные дни.

app.get('/finances', (req,res) => {

res.send(expensesList);

});

Этот маршрут просто отправляет объект списка расходов как JSON. Мы используем этот маршрут для получения данных и отображения на интерфейсе.

app.post('/expense/add', (req, res) => {
  let expense = Number(req.body.expense)
  let income = Number(req.body.income)
  let date = req.body.date;
  
  let newExpense  = {
    date: date,
    expense: expense,
    income: income
  };

  expensesList.data.push(newExpense);

  pusher.trigger('finance', 'new-expense', {
    newExpense: expensesList
  });

  res.send({
    success : true,
    income: income,
    expense: expense,
    date: date,
    data: expensesList
  })
});

/expense/add добавление уверенно делает много. Это POST-маршрут, что означает, что мы будем ожидать некоторых входящих данных (в данном случае, суммы расходов и суммы дохода).

Затем мы подталкиваем этот новый доход и расходы к существующему, после чего мы также подталкиваем обновленный список расходов в Pusher.

Наконец, мы отправляем JSON в качестве ответа на маршрут, содержащий последние данные о доходах, расходах, датах и обновленных расходах.

Ваш окончательный server.js должен выглядеть следующим образом:

const express = require('express');
const path = require('path');
const bodyParser = require("body-parser");
const app = express();
const Pusher = require('pusher');

const pusher = new Pusher({
    appId: 'APP_ID',
    key: 'YOUR_KEY',
    secret: 'YOUR_SECRET',
    cluster: 'YOUR_CLUSTER',
    encrypted: true
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname + '/app')));

app.set('port', (process.env.PORT || 5000));

let expensesList = {
    data: [
        {
            date: "April 15th 2017",
            expense: 100,
            income: 4000
        },
        {
            date: "April 22nd 2017",
            expense: 500,
            income: 2000
        },
        {
            date: "April 24th 2017",
            expense: 1000,
            income: 2300
        },
        {
            date: "April 29th 2017",
            expense: 2000,
            income: 1234
        },
        {
            date: "May 1st 2017",
            expense: 500,
            income: 4180
        },
        {
            date: "May 5th 2017",
            expense: 4000,
            income: 5000
        },
    ]
}

app.get('/finances', (req,res) => {
    res.send(expensesList);
});

app.post('/expense/add', (req, res) => {
    let expense = Number(req.body.expense)
    let income = Number(req.body.income)
    let date = req.body.date;

    let newExpense  = {
        date: date,
        expense: expense,
        income: income
    };

    expensesList.data.push(newExpense);

    pusher.trigger('finance', 'new-expense', {
        newExpense: expensesList
    });

    res.send({
        success : true,
        income: income,
        expense: expense,
        date: date,
        data: expensesList
    })
});

app.listen(app.get('port'), function() {
    console.log('Node app is running on port', app.get('port'));
});

Построение Frontend (Vue + vue-chartjs)

Большая часть работы с интерфейсом будет выполнена внутри папки src/components. Перейдите в этот каталог, и вы увидите файл Hello.vue. Вы можете либо удалить этот файл, либо переименовать его в Home.vue, поскольку нам понадобится файл Home.vue внутри папки компонентов.

Прежде чем мы начнем с построения диаграммы и отображения ее, мы должны сделать пару вещей. Откройте файл App.vue в папке src и замените следующим кодом:

<template>
  <div id="app">
    <home></home>
  </div>
</template>

<script>
import Home from './components/Home' //We are importing the Home component
export default {
  name: 'app',
  components: {
    Home
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Затем мы установим vue-chartjs,momentjspusher-js (библиотека Javascript Pusher) и axios (мы будем использовать axios для создания запросов API). а затем добавьте их в приложение Vue.js.

npm install axios vue-chartjs pusher-js moment

Как только это будет сделано, мы импортируем axios и зарегистрируем его глобально в нашем приложении. Мы можем это сделать, отредактировав файл main.js в папке src.

// src/main.js
import Vue from 'vue'
import App from './App'
import axios from 'axios' // we import axios from installed dependencies

Vue.config.productionTip = false

Vue.use(axios) // we register axios globally

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

Затем создадим компонент Vue.js, который поможет отобразить нашу диаграмму. Мы собираемся использовать это, чтобы указать, какой тип диаграммы мы хотим, настроить его внешний вид и как он себя ведет.

Затем мы импортируем этот компонент в компонент Home.vue и используем его там. Это одно из преимуществ vue-chartjs, оно работает путем импорта базового класса диаграммы, который мы затем можем расширить. Давайте продолжим и создадим этот компонент. Создайте новый файл с именем LineChart.vue внутри папки src/components и отредактируйте с помощью приведенного ниже кода.

<script>
  import {Line, mixins} from 'vue-chartjs' // We specify what type of chart we want from vue-chartjs and the mixins module
  const { reactiveProp } = mixins
  export default Line.extend({ //We are extending the base chart class as mentioned above
    mixins: [reactiveProp],
    data () {
      return {
        options: { //Chart.js options
          scales: {
            yAxes: [{
              ticks: {
                beginAtZero: true
              },
              gridLines: {
                display: true
              }
            }],
            xAxes: [ {
              gridLines: {
                display: false
              }
            }]
          },
          legend: {
            display: true
          },
          responsive: true,
          maintainAspectRatio: false
        }
      }
    },
    mounted () {
      // this.chartData is created in the mixin
      this.renderChart(this.chartData, this.options)
    }
  })
</script>

В приведенном выше блоке кода мы импортировали линейную диаграмму из vue-chartjs и модуля mixins. Chart.js обычно не предоставляет возможность автоматического обновления всякий раз, когда набор данных изменяется, но это можно сделать в vue-chartjs с помощью следующих миксинов:

  1. reactiveProp
  2. reactiveData

Эти миксины автоматически создают chartData в качестве опоры или данных и добавляют наблюдателя. Если данные изменились, диаграмма обновится. Подробнее читайте .

Кроме того, функция this.renderChart() внутри mounted функции отвечает за отображение диаграммы. this.chartData — это объект, содержащий набор данных, необходимый для диаграммы, и мы получим его, включив его как опору в шаблон Home.vue. this.options содержит объект опций, который определяет внешний вид и конфигурацию диаграммы.

Теперь у нас есть компонент LineChart, но как мы можем увидеть нашу диаграмму и проверить ее функциональность в реальном времени? Мы делаем это, добавляя LineChart к нашему компоненту Home.vue, а также подписываясь на наш канал Pusher через pusher-js.

Откройте файл Home.vue и измените его следующим образом:

<template>
  <div class="hello">
    <div class="container">
      <div class="row">
        <h2 class="title">Realtime Chart with Vue and Pusher</h2>
        <h3 class="subtitle">Expense and Income Tracker</h3>
        <!--We are using the LineChart component imported below in the script and also setting the chart-data prop to the datacollection object-->
        <line-chart :chart-data="datacollection"></line-chart>
      </div>
    </div>
    <div class="container">
      <div class="row">
        <form class="form" @submit.prevent="addExpenses">
          <h4>Add New Entry</h4>
          <div class="form-group">
            <label>Expenses</label>
            <input class="form-control" placeholder="How much did you spend?" type="number" v-model="expenseamount" required>
          </div>
          <div class="form-group">
            <label>Income</label>
            <input class="form-control" placeholder="How much did you earn?" type="number" v-model="incomeamount" required>
          </div>
          <div class="form-group">
            <label>Date</label>
            <input class="form-control" placeholder="Date" type="date" v-model="entrydate" required>
          </div>
          <div class="form-group">
            <button class="btn btn-primary">Add New Entry</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
  import axios from 'axios'
  import moment from 'moment'
  import Pusher from 'pusher-js'
  import LineChart from '@/components/LineChart'
  const socket = new Pusher('APP_KEY', {
    cluster: 'eu',
    encrypted: true
  })
  const channel = socket.subscribe('finance')
  export default {
    name: 'home',
    components: {LineChart},
    data () {
      return {
        expense: null,
        income: null,
        date: null,
        expenseamount: null,
        incomeamount: null,
        datacollection: null,
        entrydate: null
      }
    },
    created () {
      this.fetchData()
      this.fillData()
    },
    mounted () {
      this.fillData()
    },
    methods: {
      fillData () {
      },
      addExpenses () {
      },
      fetchData () {
      }
    }
  }
</script>


<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .title {
    text-align: center;
    margin-top: 40px;
  }
  .subtitle {
    text-align: center;
  }
  .form {
    max-width: 600px;
    width: 100%;
    margin: 20px auto 0 auto;
  }
  .form h4 {
    text-align: center;
    margin-bottom: 30px;
  }
  h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

fillData

Эта функция вызывается немедленно, когда приложение mounted, и в основном делает запрос API к бэкэнду узла ( /finances) и получает список расходов.

fillData () {
    axios.get('/finances')
      .then(response => {
        let results = response.data.data
    
        let dateresult = results.map(a => a.date)
        let expenseresult = results.map(a => a.expense)
        let incomeresult = results.map(a => a.income)
    
        this.expense = expenseresult
        this.income = incomeresult
        this.date = dateresult
    
        this.datacollection = {
          labels: this.date,
          datasets: [
            {
              label: 'Expense',
              backgroundColor: '#f87979',
              data: this.expense
            },
            {
              label: 'Income',
              backgroundColor: '#5bf8bf',
              data: this.income
            }
          ]
        }
      })
      .catch(error => {
        console.log(error)
      })
  }

Запрос GET делается для маршрута /finances Node.js, который, в свою очередь, возвращает последние expensesList, и затем мы обрабатываем эти данные с помощью code>.map Javascript и присваиваем его различным переменным.

addExpenses

addExpenses () {
  //We first get the new entries via the v-model we defined on the income and expense input tag
  let expense = this.expenseamount
  let income = this.incomeamount
  let today = moment(this.entrydate).format('MMMM Do YYYY') //Formats the date via momentJS
  
  //Sends a POST request to /expense/new along with the expense, income and date.
  axios.post('/expense/add', {
      expense: expense,
      income: income,
      date: today
  })
    .then(response => {
      this.expenseamount = ''
      this.incomeamount = ''
      //We are bound to new-expense on Pusher and once it detects a change via the new entry we just submitted, we use it to build the Line Chart again.
      channel.bind('new-expense', function(data) {
          let results = data.newExpense.data
  
          let dateresult = results.map(a => a.date);
          let expenseresult = results.map(a => a.expense);
          let incomeresult = results.map(a => a.income);
          
          //The instance data are updated here with the latest data gotten from Pusher
          this.expense = expenseresult
          this.income = incomeresult
          this.date = dateresult
  
          //The Chart's dataset is updated with the latest data gotten from Pusher
          this.datacollection = {
              labels: this.date,
              datasets: [
                  {
                      label: 'Expense Charts',
                      backgroundColor: '#f87979',
                      data: this.expense
                  },
                  {
                      label: 'Income Charts',
                      backgroundColor: '#5bf8bf',
                      data: this.income
                  }
              ]
          }
      });
  })
}

В приведенном выше блоке кода используется метод POST-метода для /expense/add для обновления expensesList («Помнить /expense/add маршрут на сервере Node» отправляет обновленный expensesList на панель управления Pusher) вместе с данными о доходах, расходах и дате.

Затем он использует данные, полученные от Pusher через channel.bind, чтобы снова построить линейную диаграмму и автоматически добавляет новую запись в диаграмму.

fetchData

Эта функция вызывается после создания экземпляра Vue, а также созданные изменения в наборе данных диаграммы через Pusher и автоматически обновляет линейную диаграмму.

fetchData () {
   //We are bound to new-expense on Pusher and it listens for changes to the dataset so it can automatically rebuild the Line Chart in realtime.    
    channel.bind('new-expense', data => {
        let _results = data.newExpense.data
        let dateresult = _results.map(a => a.date);
        let expenseresult = _results.map(a => a.expense);
        let incomeresult = _results.map(a => a.income);
        
        //The instance data are updated here with the latest data gotten from Pusher
        this.expense = expenseresult
        this.income = incomeresult
        this.date = dateresult

        //The Chart's dataset is updated with the latest data gotten from Pusher
        this.datacollection = {
            labels: this.date,
            datasets: [
                {
                    label: 'Expense Charts',
                    backgroundColor: '#f87979',
                    data: this.expense
                },
                {
                    label: 'Income Charts',
                    backgroundColor: '#5bf8bf',
                    data: this.income
                }
            ]
        }
    });
}

Ваш последний файл Home.vue должен выглядеть следующим образом:

<template>
  <div class="hello">
    <div class="container">
      <div class="row">
        <h2 class="title">Realtime Chart with Vue and Pusher</h2>
        <h3 class="subtitle">Expense and Income Tracker</h3>
        <line-chart :chart-data="datacollection"></line-chart>
      </div>
    </div>
    <div class="container">
      <div class="row">
        <form class="form" @submit.prevent="addExpenses">
          <h4>Add New Entry</h4>
          <div class="form-group">
            <label>Expenses</label>
            <input class="form-control" placeholder="How much did you spend today?" type="number" v-model="expenseamount" required>
          </div>
          <div class="form-group">
            <label>Income</label>
            <input class="form-control" placeholder="How much did you earn today?" type="number" v-model="incomeamount" required>
          </div>
          <div class="form-group">
            <button class="btn btn-primary">Add New Entry</button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
  import axios from 'axios'
  import moment from 'moment'
  import Pusher from 'pusher-js'
  import LineChart from '@/components/LineChart'
  const socket = new Pusher('3e6b0e8f2442b34330b7', {
    cluster: 'eu',
    encrypted: true
  })
  const channel = socket.subscribe('finance')
  export default {
    name: 'home',
    components: {LineChart},
    data () {
      return {
        expense: null,
        income: null,
        date: null,
        expenseamount: null,
        incomeamount: null,
        datacollection: null
      }
    },
    created () {
      this.fetchData()
      this.fillData()
    },
    mounted () {
      this.fillData()
    },
    methods: {
      fillData () {
        axios.get('/finances')
          .then(response => {
            let results = response.data.data
            let dateresult = results.map(a => a.date)
            let expenseresult = results.map(a => a.expense)
            let incomeresult = results.map(a => a.income)
            this.expense = expenseresult
            this.income = incomeresult
            this.date = dateresult
            this.datacollection = {
              labels: this.date,
              datasets: [
                {
                  label: 'Expense',
                  backgroundColor: '#f87979',
                  data: this.expense
                },
                {
                  label: 'Income',
                  backgroundColor: '#5bf8bf',
                  data: this.income
                }
              ]
            }
          })
          .catch(error => {
            console.log(error)
          })
      },
      addExpenses () {
        let expense = this.expenseamount
        let income = this.incomeamount
        let today = moment().format('MMMM Do YYYY')
        axios.post('/expense/add', {
          expense: expense,
          income: income,
          date: today
        })
          .then(response => {
            this.expenseamount = ''
            this.incomeamount = ''
            channel.bind('new-expense', function (data) {
              let results = data.newExpense.data
              let dateresult = results.map(a => a.date)
              let expenseresult = results.map(a => a.expense)
              let incomeresult = results.map(a => a.income)
              this.expense = expenseresult
              this.income = incomeresult
              this.date = dateresult
              this.datacollection = {
                labels: this.date,
                datasets: [
                  {
                    label: 'Expense',
                    backgroundColor: 'transparent',
                    pointBorderColor: '#f87979',
                    data: this.expense
                  },
                  {
                    label: 'Income',
                    backgroundColor: 'transparent',
                    pointBorderColor: '#5bf8bf',
                    data: this.income
                  }
                ]
              }
            })
          })
          .catch(error => {
            console.log(error)
          })
      },
      fetchData () {
        channel.bind('new-expense', data => {
          let results = data.newExpense.data
          let dateresult = results.map(a => a.date)
          let expenseresult = results.map(a => a.expense)
          let incomeresult = results.map(a => a.income)
          this.expense = expenseresult
          this.income = incomeresult
          this.date = dateresult
          this.datacollection = {
            labels: this.date,
            datasets: [
              {
                label: 'Expense Charts',
                backgroundColor: '#f87979',
                data: this.expense
              },
              {
                label: 'Income Charts',
                backgroundColor: '#5bf8bf',
                data: this.income
              }
            ]
          }
        })
      }
    }
  }
</script>


<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .title {
    text-align: center;
    margin-top: 40px;
  }
  .subtitle {
    text-align: center;
  }
  .form {
    max-width: 600px;
    width: 100%;
    margin: 20px auto 0 auto;
  }
  .form h4 {
    text-align: center;
    margin-bottom: 30px;
  }
  h1, h2 {
  font-weight: normal;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Еще кое-что!

Прежде чем мы сможем запустить наше приложение, нам нужно сделать что-то, называемое API proxying. API-прокси позволяет нам интегрировать наше приложение vue-cli с серверным сервером (в нашем случае — Node-сервер). Это означает, что мы можем запускать сервер dev и бэкэнд API бок о бок и позволить серверу-разработчику проксировать все запросы API на фактический сервер.

Мы можем включить API-проксирование, отредактировав параметр dev.proxyTable в config/index.js. Вы можете редактировать с помощью кода ниже.

proxyTable: {
  '/expense/add': {
    target: 'http://localhost:5000',
    changeOrigin: true
  },
  '/finances': {
    target: 'http://localhost:5000',
    changeOrigin: true
  },
}

После этого мы, наконец, готовы увидеть наше приложение, и вы можете запустить npm run dev, чтобы запустить приложение.

Это оно! На этом этапе вы должны иметь диаграмму приборной панели реального времени, которая обновляется в реальном времени.

Вы можете проверить демо-версию здесь или перейти к коду для всего приложения, которое размещено на Github для вашего прочтения.

Вывод

Мы видели, как создать базовую линейную диаграмму с ChartJS в Vue с помощью vue-chartjs, а также добавить функции в реальном времени благодаря Pusher.

Затем мы увидели, как использовать реактивные приложения, чтобы сделать ChartJS обновлением своего набора данных, если произошел сбой в наборе данных. Мы также видели, как использовать Pusher для запуска событий на сервере и прослушивания их на стороне клиента с помощью JS.

Вы недавно создали что-нибудь интересное с Pusher, может быть, диаграмма? Дайте знать в ответах ниже.

ТЕГИ:

Добавить комментарий

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

Авторизация
*
*
Регистрация
*
*
*
Генерация пароля