
Создайте график в реальном времени с помощью 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,momentjs, pusher-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 с помощью следующих миксинов:
- reactiveProp
- 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, может быть, диаграмма? Дайте знать в ответах ниже.