Search code examples
vue.jsrestchart.jsvue-componentfastapi

How to display a vertical bar chart with multiple datasets that coming from an FastAPI endpoint in Vue.js?


I would like to display multiple datasets using the Vue.js framework. The datasets come from the FastAPI server backend, containing mocked stock data.

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import polars as pl
from pydantic import BaseModel
from typing import List

app = FastAPI()

# Allow requests from the Vue.js frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_methods=["*"],
    allow_headers=["*"],
)

class StockDevelopment(BaseModel):
    date: str
    actual_stock: float
    predicted_stock: float

mock_data = pl.DataFrame({
    "date": ["2023-09-01", "2023-09-02", "2023-09-03", "2023-09-04", "2023-09-05"],
    "actual_stock": [100, 150, 120, 90, 115],
    "predicted_stock": [110, 140, 130, 100, 113],
})

@app.get("/data")
async def get_data() -> List[StockDevelopment]:
    stock_rows = mock_data.to_dicts()
    stocks = [StockDevelopment(**row) for row in stock_rows]
    return stocks

The date, actual_stock, and the predicted_stock data frame columns are converted as rows. The stock data can be requested through the <fastapi-server-ipaddress>/data HTTP endpoint.

How to display the actual and the predicted stock next to each other? Similar to the following picture:

enter image description here


Solution

  • Use the vue-chartjs alongside with the chart.js package. Install them with the following command:

    npm install vue-chartjs chart.js
    

    After that, import it into the Vue component. However, keep in mind that there is a problem while displaying the API data on the bar chart. Namely, Chart.js tries to render your chart and access the chart data synchronously, so your chart mounts before the API data arrives.

    Prevent this by using the v-if conditional rendering. For more information check out the documentation. Now, create the bar chart component with a loaded, a data, and an option prop.

    <template>
      <div>
        <h1>Stock Data</h1>
        <!-- The v-if is used to conditionally render the block -->
        <Bar id="my-chart-id" v-if="loaded" :options="chartOptions" :data="chartData" :width="600" />
      </div>
    </template>
    
    <script>
    import { Bar } from 'vue-chartjs'
    import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
    
    ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
    
    export default {
      name: 'BarChart',
      components: { Bar },
      data: () => ({
        // Prevents chart to mount before the data arrives from API endpoint
        loaded: false,
        chartData: {
          labels: [],
          datasets: [
            {
              label: 'Actual',
              backgroundColor: 'rgba(255, 99, 132, 0.2)',
              data: []
            },
            {
              label: 'Predicted',
              backgroundColor: 'rgba(54, 162, 235, 0.2)',
              data: []
            }
          ]
        },
        chartOptions: {
          responsive: true
        }
      }),
      async mounted() {
        const apiUrl = 'http://localhost:8000/data'
    
        // Make an HTTP request to fetch the data from the FastAPI server
        await fetch(apiUrl)
          .then((response) => response.json())
          .then((data) => {
            // Extract data from the API response and update the chartData
            this.chartData.labels = data.map((stock) => stock.date)
            this.chartData.datasets[0].data = data.map((stock) => stock.actual_stock)
            this.chartData.datasets[1].data = data.map((stock) => stock.predicted_stock)
    
            // Allow the chart to display the data from the API endpoint
            this.loaded = true
          })
          .catch((error) => {
            console.error('Error fetching data:', error)
          })
      }
    }
    </script>
    

    The bar chart is only loaded when the data is arrived without any error. It is achieved by setting the loaded prop to true after fetching the data from the API endpoint. The datasets' values are set during the HTTP request and populated each data using the map function. If you would like to add more datasets then extend the chartData.datasets array with another entry. Then, you can assign data to this.chartData.datasets[2].data array.

    Note: the stock.date, stock.actual_stock, and the stock.predicted_stock return values have to match their corresponding FastAPI response data, described in the StockDevelopment class.