-
Notifications
You must be signed in to change notification settings - Fork 263
Open
Description
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
Labels
No labels