Skip to content

Unable to parse the form part of my multi-content form. #1000

@priyapatel1

Description

@priyapatel1

I'm building an app that allows you to upload a csv and turn it into a graph generated through ggplot. Vue frontend, R backend. I've been trying to parse the form portion of my app for a couple days and I'm not able to get it working. I am, however, able to view my csv data and turn it into a data frame.

Here is my Vue frontend.

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const file = ref(null)
const graphType = ref('scatter')
const xAxis = ref('')
const yAxis = ref('')
const graphData = ref(null)
const loading = ref(false)
const error = ref(null)
const csvPreview = ref(null)
const customText = ref('')
const availableColumns = ref([])

const graphTypes = [
  { value: 'scatter', label: 'Scatter Plot' },
  { value: 'line', label: 'Line Graph' },
  { value: 'bar', label: 'Bar Chart' },
  { value: 'histogram', label: 'Histogram' }
]

const handleFileUpload = async (event) => {
  const uploadedFile = event.target.files[0]
  if (uploadedFile && uploadedFile.type === 'text/csv') {
    file.value = uploadedFile
    // Preview CSV data
    const reader = new FileReader()
    reader.onload = async (e) => {
      const text = e.target.result
      const lines = text.split('\n')
      const headers = lines[0].split(',')
      csvPreview.value = {
        headers,
        firstRow: lines[1].split(',')
      }

      // Get column names from the backend
      const formData = new FormData()
      formData.append('file', uploadedFile)
      formData.append('graphType', graphType.value)
      formData.append('xAxis', '')
      formData.append('yAxis', '')
      formData.append('customText', customText.value)

      try {
        const response = await axios.post('http://localhost:8000/api/generate-graph', formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        })
        availableColumns.value = response.data.columns
      } catch (err) {
        console.error('Error getting column names:', err)
        error.value = 'Error getting column names: ' + (err.response?.data || err.message)
      }
    }
    reader.readAsText(uploadedFile)
  } else {
    error.value = 'Please upload a valid CSV file'
  }
}

const generateGraph = async () => {
  if (!file.value) {
    error.value = 'Please upload a CSV file first'
    return
  }

  if (!xAxis.value || !yAxis.value) {
    error.value = 'Please specify both X and Y axis columns'
    return
  }

  loading.value = true
  error.value = null


  // I think the issue is that there are two types of data being sent to the backend, one is the csv and the other is the form data
  // I need to send the csv as a file and the form data as a form data object
  const formData = new FormData()
  formData.append('file', file.value)
  formData.append('graphType', graphType.value)
  formData.append('xAxis', xAxis.value)
  formData.append('yAxis', yAxis.value)
  formData.append('customText', customText.value)

  // Debug logging
  console.log('Form data being sent:')
  for (let pair of formData.entries()) {
    console.log(pair[0] + ': ' + pair[1])
  }
  console.log('xAxis:', xAxis.value)
  console.log('yAxis:', yAxis.value)
  console.log('graphType:', graphType.value)
  console.log('customText:', customText.value)

  try {

    const response = await axios.post('http://localhost:8000/api/generate-graph', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
    graphData.value = response.data
  } catch (err) {
    console.error('Error details:', err)
    error.value = 'Error generating graph: ' + (err.response?.data || err.message)
  } finally {
    loading.value = false
  }
}

const downloadGraph = async (format) => {
  if (!graphData.value) return

  try {
    const response = await axios.get(`http://localhost:8000/api/download`, {
      params: {
        format,
        path: graphData.value[`${format}_path`]
      },
      responseType: 'blob'
    })

    const url = window.URL.createObjectURL(new Blob([response.data]))
    const link = document.createElement('a')
    link.href = url
    link.setAttribute('download', `graph.${format}`)
    document.body.appendChild(link)
    link.click()
    link.remove()
  } catch (err) {
    error.value = 'Error downloading graph: ' + err.message
  }
}

</script>

<template>
  <div class="container">
    <h1>EasyGraph - Data Visualization Made Simple</h1>

    <div class="upload-section">
      <input type="file" accept=".csv" @change="handleFileUpload" class="file-input">
      <p v-if="error" class="error">{{ error }}</p>

      <div class="control-group">
        <label for="customText">Custom Text:</label>
        <input type="text" v-model="customText" id="customText" placeholder="Enter custom text">
      </div>

      <div v-if="csvPreview" class="csv-preview">
        <h3>CSV Preview</h3>
        <p>Available columns: {{ csvPreview.headers.join(', ') }}</p>
        <p>First row: {{ csvPreview.firstRow.join(', ') }}</p>
      </div>
    </div>

    <div class="controls" v-if="file">
      <div class="control-group">
        <label for="graphType">Graph Type:</label>
        <select v-model="graphType" id="graphType">
          <option v-for="type in graphTypes" :key="type.value" :value="type.value">
            {{ type.label }}
          </option>
        </select>
      </div>

      <div class="control-group">
        <label for="xAxis">X-Axis:</label>
        <select v-model="xAxis" id="xAxis" class="column-select">
          <option value="">Select a column</option>
          <option v-for="column in availableColumns" :key="column" :value="column">
            {{ column }}
          </option>
        </select>
      </div>

      <div class="control-group">
        <label for="yAxis">Y-Axis:</label>
        <select v-model="yAxis" id="yAxis" class="column-select">
          <option value="">Select a column</option>
          <option v-for="column in availableColumns" :key="column" :value="column">
            {{ column }}
          </option>
        </select>
      </div>

      <button @click="generateGraph" :disabled="loading" class="generate-btn">
        {{ loading ? 'Generating...' : 'Generate Graph' }}
      </button>
    </div>

    <div class="graph-container" v-if="graphData">
      <img :src="`http://localhost:8000/api/download?format=png&path=${graphData.png_path}`" alt="Generated Graph"
        class="graph-image">
      <div class="download-buttons">
        <button @click="downloadGraph('png')">Download PNG</button>
        <button @click="downloadGraph('pdf')">Download PDF</button>
      </div>
    </div>
  </div>
</template>

Here is my R backend:
app.R

library(plumber)
pr <- plumber::plumb("server/plumber.R")
pr$run(port = 8000, host = "0.0.0.0") 

plumber.R

#* @filter cors
function(req, res) {
  res$setHeader("Access-Control-Allow-Origin", "*")
  res$setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
  res$setHeader("Access-Control-Allow-Headers", "Content-Type")
  plumber::forward()
  print(req)
  print(req$postBody)
}

#* @post /api/generate-graph
#* @param file:file
#* @param graphType:string
#* @param xAxis:string
#* @param yAxis:string
#* @param customText:string
function(req, file, graphType, xAxis, yAxis, customText) {
  library(ggplot2)
  library(readr)
  
  # Debug prints
  print("Request contents:")
  print("POST body:")
  print(req$postBody)
  print("Parsed parameters:")
  print(paste("graphType:", graphType))
  print(paste("xAxis:", xAxis))
  print(paste("yAxis:", yAxis))
  print(paste("customText:", customText))
  
  # Remove Byte Order Mark (BOM) if present and fix newlines
  clean_string <- gsub("^\ufeff", "", file)
  clean_string <- gsub("\r\n", "\n", clean_string)
  
  # Read into a data frame
  df <- read_csv(I(clean_string))
  print(df)
  
  # Return all received parameters to verify
  list(
    data = df,
    columns = colnames(df),
    receivedParams = list(
      graphType = if(is.null(graphType)) "" else graphType,
      xAxis = if(is.null(xAxis)) "" else xAxis,
      yAxis = if(is.null(yAxis)) "" else yAxis,
      customText = if(is.null(customText)) "" else customText
    )
  )
  
#   # Create the plot based on graph type
#   p <- switch(graphType,
#     "scatter" = ggplot(data, aes_string(x = xAxis, y = yAxis)) + geom_point() + theme_minimal(),
#     "line" = ggplot(data, aes_string(x = xAxis, y = yAxis)) + geom_line() + theme_minimal(),
#     "bar" = ggplot(data, aes_string(x = xAxis, y = yAxis)) + geom_bar(stat = "identity") + theme_minimal(),
#     "histogram" = ggplot(data, aes_string(x = xAxis)) + geom_histogram() + theme_minimal()
#   )
  
#   # Save the plot in both PNG and PDF formats
#   png_path <- tempfile(fileext = ".png")
#   ggsave(png_path, p, width = 10, height = 6)
#   pdf_path <- tempfile(fileext = ".pdf")
#   ggsave(pdf_path, p, width = 10, height = 6)
  
#   # Return the paths to the generated files
#   list(png_path = png_path, pdf_path = pdf_path)
}

#* @get /api/download
#* @param format:string
#* @param path:string

# function(format, path) {
#   readBin(path, "raw", n = file.info(path)$size)
# } 

Here is the output of the code as it stands right now:

(base) priyapatel@Priyas-Air easygraph % Rscript server/app.R
Running plumber API at http://0.0.0.0:8000
Running swagger Docs at http://127.0.0.1:8000/__docs__/
<environment: 0x10f0d1a48>
[1] "------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"file\"; filename=\"test.csv\"\nContent-Type: text/csv\n\nx,y\n1,10\n2,3\n3,5\n4,7\n5,9\n6,5\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"graphType\"\n\nscatter\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"xAxis\"\n\nx\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"yAxis\"\n\ny\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"customText\"\n\n\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE--"
[1] "Request contents:"
[1] "POST body:"
[1] "------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"file\"; filename=\"test.csv\"\nContent-Type: text/csv\n\nx,y\n1,10\n2,3\n3,5\n4,7\n5,9\n6,5\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"graphType\"\n\nscatter\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"xAxis\"\n\nx\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"yAxis\"\n\ny\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE\nContent-Disposition: form-data; name=\"customText\"\n\n\n------WebKitFormBoundaryYm0qDy9DRDsfUGyE--"
[1] "Parsed parameters:"
[1] "graphType: "
[1] "xAxis: "
[1] "yAxis: "
[1] "customText: "
Rows: 6 Columns: 2
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (2): x, y

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# A tibble: 6 × 2
      x     y
  <dbl> <dbl>
1     1    10
2     2     3
3     3     5
4     4     7
5     5     9
6     6     5

What am I doing wrong?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions