aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNavan Chauhan <navanchauhan@gmail.com>2023-12-10 17:39:13 -0700
committerNavan Chauhan <navanchauhan@gmail.com>2023-12-10 17:39:13 -0700
commita593dfa50b5847c98105d8de14969eab790f60b5 (patch)
treebbb26f9882c9a30e50b69780c6e2aec803b747f7
parent4cac86ab4a2e66fc7e1a6ce2c80d169073881936 (diff)
add support for multiple data-series
* allows removal of data series * allows removal of constant lines * allows changing scale for x and y axes
-rw-r--r--app.py178
-rw-r--r--templates/index.html211
2 files changed, 325 insertions, 64 deletions
diff --git a/app.py b/app.py
index 8fe1de5..7352014 100644
--- a/app.py
+++ b/app.py
@@ -9,30 +9,53 @@ matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
+import re
+
font_path = 'times_new_roman.ttf'
times_new_roman = font_manager.FontProperties(fname=font_path, style='normal')
-def plot_data(
- x_data, y_data, std_dev_data=None,
- title=None, x_label=None, y_label=None,
- data_point_color='#ff0000', plot_background_color='#ffffff', constant_line=[],
- enable_trendline=True, enable_grid=False,
- data_series_title="Data Series",
- trendline_color='#00ff00'
- ):
+"""
+ fig = plot_data(x_data, y_data, std_dev_data, color_picker, title=plot_title,
+ x_label=x_axis_label, y_label=y_axis_label,
+ plot_background_color=plot_background_color,
+ constant_line=constant_lines,
+ enable_trendline=enable_trendline,
+ trendline_color=color_picker_trendline)
+"""
+
+def plot_data(x_data, y_data, std_dev_data, color_picker, labels, df,
+ title = "Plot", x_label = "X Axis", y_label = "Y Axis",
+ plot_background_color="#ffffff", constant_line=[],
+ enable_trendline=True, enable_grid=False,
+ trendline_color="#000000", x_axis_scale="linear", y_axis_scale="linear"):
fig, ax = plt.subplots(dpi=300)
- if (std_dev_data):
- main_plot = ax.errorbar(x_data, y_data, yerr=std_dev_data, fmt='o', ecolor='black', capsize=5, label=data_series_title, color=data_point_color)
- else:
- main_plot = ax.plot(x_data, y_data, 'o', color=data_point_color, label=data_series_title)
+ plots = []
+
+ for idx, _ in enumerate(x_data):
+ x = df[x_data[idx]].astype(float)
+ y = df[y_data[idx]].astype(float)
+ color = color_picker[idx]
+ data_series_title = labels[idx]
+ #print(df[x][0], df[y][0], color, data_series_title)
+ if (std_dev_data[idx] != None):
+ plot = ax.errorbar(x, y, yerr=df[std_dev_data[idx]].astype(float), fmt='o', ecolor='black', capsize=5, color=color, label=data_series_title)
+ else:
+ plot = ax.plot(x, y, 'o', color=color, label=data_series_title)
+
+ if (type(plot) == list):
+ plots.extend(plot)
+ else:
+ plots.append(plot)
- handles = [main_plot]
+ handles = plots
if enable_trendline:
- z = np.polyfit(x_data, y_data, 2)
+ x = df[x_data[0]].astype(float)
+ y = df[y_data[0]].astype(float)
+ z = np.polyfit(x, y, 2)
p = np.poly1d(z)
- h, = ax.plot(x_data,p(x_data), linestyle="dashed", label="Trendline", color=trendline_color)
+ h, = ax.plot(x,p(x), linestyle="dashed", label="Trendline", color=trendline_color)
handles.append(h)
light_grey = 0.9
@@ -66,13 +89,22 @@ def plot_data(
fig.patch.set_facecolor(plot_background_color)
fig.tight_layout(pad=3.0)
#ax.invert_xaxis()
- #ax.set_xscale("log")
+
+
+ ax.set_xscale(x_axis_scale)
+ ax.set_yscale(y_axis_scale)
return fig
app = Flask(__name__)
+def create_df_from_textarea(textarea):
+ rows = textarea.split('\n')
+ data = [row.split("\t") for row in rows]
+ df = pd.DataFrame(data[1:], columns=data[0])
+ return df
+
@app.route('/')
def index():
return render_template('index.html')
@@ -81,9 +113,11 @@ def index():
@app.route('/process_data', methods=['POST'])
def process_data():
print(request.form)
- x_data_json = request.form.get('xData', '[]')
- y_data_json = request.form.get('yData', '[]')
- std_dev_data_json = request.form.get('stdDevData', '[]')
+
+ textarea = request.form['excelData']
+ df = create_df_from_textarea(textarea)
+
+ print(df)
constant_lines = []
for x in range(0,10):
@@ -94,25 +128,93 @@ def process_data():
except KeyError:
break
- x_data = json.loads(x_data_json)
- print(x_data, x_data_json)
- y_data = json.loads(y_data_json)
- std_dev_data = json.loads(std_dev_data_json)
+ data_keys = f"{request.form.keys()}"
+ print(data_keys)
+
+ xPattern = r'xColumn-\d+'
+ yPattern = r'yColumn-\d+'
+ stdDevPattern = r'stdDevColumn-\d+'
+ colorPickerPattern = r'colorPicker-\d+'
+ labelPattern = r'dataSeries-\d+'
+
+
+ # match in data_keys string
+ x_data_matches = re.findall(xPattern, data_keys)
+ y_data_matches = re.findall(yPattern, data_keys)
+ std_dev_data_matches = re.findall(stdDevPattern, data_keys)
+ color_picker_matches = re.findall(colorPickerPattern, data_keys)
+ label_matches = re.findall(labelPattern, data_keys)
+
+ print(x_data_matches, y_data_matches, std_dev_data_matches, color_picker_matches)
+
+ # Not sure if we need to sort these
+ #x_data_matches.sort()
+ #y_data_matches.sort()
+ #std_dev_data_matches.sort()
+
+ x_data = []
+
+ for x in x_data_matches:
+ val = request.form.get(x)
+ if val != '':
+ x_data.append(df.columns[int(val)])
+ else:
+ x_data.append(None)
+
+ y_data = []
+
+ for y in y_data_matches:
+ val = request.form.get(y)
+ if val != '':
+ y_data.append(df.columns[int(val)])
+ else:
+ y_data.append(None)
+
+ std_dev_data = []
+
+ for std_dev in std_dev_data_matches:
+ val = request.form.get(std_dev)
+ if val != '':
+ std_dev_data.append(df.columns[int(val)])
+ else:
+ std_dev_data.append(None)
+
+ color_picker = []
+
+ for color in color_picker_matches:
+ val = request.form.get(color)
+ if val != '':
+ color_picker.append(val)
+ else:
+ color_picker.append(None)
+
+ data_series_label = []
+
+ for label in label_matches:
+ val = request.form.get(label)
+ if val != '':
+ data_series_label.append(val)
+ else:
+ data_series_label.append("Data")
+
+
+ print(x_data)
+ print(y_data)
+ print(std_dev_data)
+ print(color_picker)
+ print(data_series_label)
- # Process data
- x_data = [float(x) for x in x_data]
- y_data = [float(y) for y in y_data]
- std_dev_data = [float(std_dev) for std_dev in std_dev_data] if std_dev_data else []
+
x_axis_label = request.form.get('xTitle', 'X Axis')
y_axis_label = request.form.get('yTitle', 'Y Axis')
plot_title = request.form.get('plotTitle', 'Plot Title')
- data_point_color = request.form.get('colorPickerDP', '#ff0000')
plot_background_color = request.form.get('colorPickerPlotBackground', '#ffffff')
color_picker_trendline = request.form.get('colorPickerTrendline', '#00ff00')
- data_series_title = request.form.get('dataSeriesTitle', 'Data Series')
+ x_axis_scale = request.form.get('xAxisScale', 'linear')
+ y_axis_scale = request.form.get('yAxisScale', 'linear')
calc_trendline = request.form.get('calcTrendline', 'off')
if calc_trendline == 'on':
@@ -120,14 +222,14 @@ def process_data():
else:
enable_trendline = False
- fig = plot_data(
- x_data, y_data, std_dev_data=std_dev_data,
- title=plot_title, x_label=x_axis_label, y_label=y_axis_label,
- data_point_color=data_point_color, plot_background_color=plot_background_color,
- constant_line=constant_lines, data_series_title=data_series_title,
- enable_trendline=enable_trendline,
- trendline_color=color_picker_trendline
- )
+ fig = plot_data(x_data, y_data, std_dev_data, color_picker, data_series_label, df, title=plot_title,
+ x_label=x_axis_label, y_label=y_axis_label,
+ plot_background_color=plot_background_color,
+ constant_line=constant_lines,
+ enable_trendline=enable_trendline,
+ trendline_color=color_picker_trendline,
+ x_axis_scale=x_axis_scale,
+ y_axis_scale=y_axis_scale)
# Return plot as image
from io import BytesIO
@@ -142,4 +244,4 @@ def process_data():
return '<img src="data:image/png;base64,{}">'.format(image_base64)
if __name__ == '__main__':
- app.run(port=8080,debug=True) \ No newline at end of file
+ app.run(port=8080,debug=True)
diff --git a/templates/index.html b/templates/index.html
index 39dc174..e7cf358 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -20,36 +20,158 @@
<li>Click "Generate Plot"</li>
</ol>
<form action="/process_data" method="post" onsubmit="prepareSubmission">
- <textarea id="excelData" placeholder="Paste Excel data here" class="form-control mb-3"></textarea>
- <div class="mb-3 row">
- <div class="col-md-4">
- <label for="xColumn" class="form-label">X Data Column</label>
- <select id="xColumn" class="form-control"></select>
+ <textarea id="excelData" placeholder="Paste Excel data here" class="form-control mb-3" name="excelData"></textarea>
+ <button type="button" onclick="processData()" class="btn btn-outline-primary mb-3">Process Excel Data</button>
+ <div name="dataSeriesDiv" id="data-series-div">
+ <div class="row mt-3 mb-3">
+ <div class="col-md-2">
+ <label for="xColumn-1" class="form-label">X Data Column</label>
+ <select id="xColumn-1" class="form-control table-data" name="xColumn-1"></select>
</div>
- <div class="col-md-4">
- <label for="yColumn" class="form-label">Y Data Column</label>
- <select id="yColumn" class="form-control"></select>
+ <div class="col-md-2">
+ <label for="yColumn-1" class="form-label">Y Data Column</label>
+ <select id="yColumn-1" class="form-control table-data" name="yColumn-1"></select>
+ </div>
+ <div class="col-md-2">
+ <label for="stdDevColumn-1" class="form-label" >Error Bars</label>
+ <select id="stdDevColumn-1" class="form-control table-data" name="stdDevColumn-1">
+ <option value="">None</option>
+ </select>
+ </div>
+ <div class="col-md-2">
+ <label for="dataSeries-1" class="form-label">Data Series Name</label>
+ <input type="text" name="dataSeries-1" id="dataSeries-1" class="form-control" placeholder="Data">
+ </div>
+ <div class="col-md-2">
+ <label for="colorPicker-1" class="form-label">Datapoint Color</label>
+ <input name="colorPicker-1" class="form-control" id="colorPicker-1" name="colorPicker-1" value="#000000" data-coloris>
</div>
- <div class="col-md-4">
- <label for="stdDevColumn" class="form-label">Standard Deviation Column</label>
- <select id="stdDevColumn" class="form-control">
- <option value="">None</option>
- </select>
</div>
</div>
+ <button class="btn btn-outline-primary mb-4" type="button" onclick="addDataSeries()">Add Data Series</button>
+ <script type="text/javascript">
+ function addDataSeries() {
+ let dataSeriesCount = document.getElementById('data-series-div').childElementCount;
+ let dataSeriesDiv = document.getElementById('data-series-div');
+
+ let row = document.createElement('div');
+ row.classList.add('row');
+ row.classList.add('mb-3');
+ row.id = `dataSeriesRow-${dataSeriesCount + 1}`;
+
+ let col1 = document.createElement('div');
+ col1.classList.add('col-md-2');
+
+ let col2 = document.createElement('div');
+ col2.classList.add('col-md-2');
+
+ let col3 = document.createElement('div');
+ col3.classList.add('col-md-2');
+
+ let col4 = document.createElement('div');
+ col4.classList.add('col-md-2');
+
+ let col5 = document.createElement('div');
+ col5.classList.add('col-md-2');
+
+ let col6 = document.createElement('div');
+ col6.classList.add('col-md-2');
+
+ let xColumn = document.createElement('select');
+ xColumn.classList.add('form-control');
+ xColumn.classList.add('table-data');
+ xColumn.id = `xColumn-${dataSeriesCount + 1}`;
+ xColumn.name = `xColumn-${dataSeriesCount + 1}`;
+
+ let yColumn = document.createElement('select');
+ yColumn.classList.add('form-control');
+ yColumn.classList.add('table-data');
+ yColumn.id = `yColumn-${dataSeriesCount + 1}`;
+ yColumn.name = `yColumn-${dataSeriesCount + 1}`;
+
+ let stdDevColumn = document.createElement('select');
+ stdDevColumn.classList.add('form-control');
+ stdDevColumn.classList.add('table-data');
+ stdDevColumn.id = `stdDevColumn-${dataSeriesCount + 1}`;
+ stdDevColumn.name = `stdDevColumn-${dataSeriesCount + 1}`;
+ // Default to None
+ let stdDevOption = document.createElement('option');
+ stdDevOption.value = '';
+ stdDevOption.innerHTML = 'None';
+ stdDevColumn.appendChild(stdDevOption);
+
+ let dataSeries = document.createElement('input');
+ dataSeries.classList.add('form-control');
+ dataSeries.id = `dataSeries-${dataSeriesCount + 1}`;
+ dataSeries.name = `dataSeries-${dataSeriesCount + 1}`;
+ dataSeries.placeholder = 'Data';
+
+ let colorPicker = document.createElement('input');
+ colorPicker.classList.add('form-control');
+ colorPicker.id = `colorPicker-${dataSeriesCount + 1}`;
+ colorPicker.name = `colorPicker-${dataSeriesCount + 1}`;
+ colorPicker.value = '#000000';
+ colorPicker.setAttribute('data-coloris', '');
+
+ let removeButton = document.createElement('button');
+ removeButton.classList.add('btn');
+ removeButton.classList.add('btn-outline-danger');
+ removeButton.classList.add('mb-3');
+ removeButton.type = 'button';
+ removeButton.innerHTML = 'Remove Data Series';
+ removeButton.onclick = function() {
+ let row = document.getElementById(`dataSeriesRow-${dataSeriesCount + 1}`);
+ row.remove();
+ }
+
+
+ // If excel data is present, populate the column selectors
+ let headersDiv = document.getElementById('headersDiv');
+ if (headersDiv) {
+ let headers = JSON.parse(headersDiv.innerHTML);
+ headers.forEach((header, index) => {
+ let option = new Option(header, index);
+ xColumn.add(option.cloneNode(true));
+ yColumn.add(option.cloneNode(true));
+ stdDevColumn.add(option.cloneNode(true));
+ });
+ }
+
+ col1.appendChild(xColumn);
+ col2.appendChild(yColumn);
+ col3.appendChild(stdDevColumn);
+ col4.appendChild(dataSeries);
+ col5.appendChild(colorPicker);
+ col6.appendChild(removeButton);
+
+ row.appendChild(col1);
+ row.appendChild(col2);
+ row.appendChild(col3);
+ row.appendChild(col4);
+ row.appendChild(col5);
+ row.appendChild(col6);
+
+ dataSeriesDiv.appendChild(row);
+
+ }
+ </script>
<input type="hidden" name="xData" id="xData">
<input type="hidden" name="yData" id="yData">
<input type="hidden" name="stdDevData" id="stdDevData">
- <button type="button" onclick="prepareSubmission()" class="btn btn-outline-primary">Load Data from Columns</button>
+ <br><br>
+ <div>
+ <button type="button" onclick="prepareSubmission()" class="btn btn-outline-primary">Load Data from Columns</button>
+ <button type="button" class="btn btn-outline-warning">Reset Data Load</button>
+ </div>
<div class="row mb-3 mt-3">
<label for="xTitle" class="form-label">X-Axis Title</label>
- <input type="text" name="xTitle" id="xTitle" class="form-control">
+ <input type="text" name="xTitle" id="xTitle" class="form-control" placeholder="X-Axis">
</div>
<div class="row mb-3">
<label for="yTitle" class="form-label">Y-Axis Title</label>
- <input type="text" name="yTitle" id="yTitle" class="form-control">
+ <input type="text" name="yTitle" id="yTitle" class="form-control" placeholder="Y-Axis">
</div>
<div class="row mb-3">
@@ -57,10 +179,6 @@
<input type="text" name="plotTitle" id="plotTitle" class="form-control">
</div>
- <div class="row mb-3">
- <label for="dataSeriesTitle">Data Series Title</label>
- <input type="text" name="dataSeriesTitle" id="dataSeriesTitle" class="form-control">
- </div>
<div id="constantLinesContainer">
</div>
@@ -78,25 +196,41 @@
<div class="row mb-3">
<div class="col-md-4">
- <label for="colorPickerDP" class="form-label">Datapoint Color</label>
- <input name="colorPickerDP" class="form-control" id="colorPickerDP" value="#ff0000" data-coloris>
- </div>
- <div class="col-md-4">
<label for="colorPickerPlotBackground" class="form-label">Plot Background Color</label>
<input name="colorPickerPlotBackground" class="form-control" id="colorPickerPlotBackground" value="#ffffff" data-coloris>
</div>
<div class="col-md-4">
<label for="colorPickerTrendline" class="form-label">Trendline Color</label>
- <input name="colorPickerTrendline" class="form-control" id="colorPickerTrendline" value="#00ff00" data-coloris>
+ <input name="colorPickerTrendline" class="form-control" id="colorPickerTrendline" value="#000000" data-coloris>
</div>
</div>
+ <div class="row mb-3">
+ <div class="col-md-4">
+ <label for="xAxisScale" class="form-label">X-Axis Scale</label>
+ <select name="xAxisScale" id="xAxisScale" class="form-control">
+ <option value="linear" default>Linear</option>
+ <option value="log">Logarithmic</option>
+ <option value="symlog">Symmetrical Logarithmic</option>
+ <option value="logit">Logit</option>
+ </select>
+ </div>
+ <div class="col-md-4">
+ <label for="yAxisScale" class="form-label">Y-Axis Scale</label>
+ <select name="yAxisScale" id="yAxisScale" class="form-control">
+ <option value="linear" default>Linear</option>
+ <option value="log">Logarithmic</option>
+ <option value="symlog">Symmetrical Logarithmic</option>
+ <option value="logit">Logit</option>
+ </select>
+ </div>
+ </div>
<input type="submit" value="Generate Plot">
</form>
<div id="tableContainer"></div>
<script type="text/javascript">
// add change handler for textarea and execute processData function
- document.getElementById('excelData').addEventListener('change', processData);
+ //document.getElementById('excelData').addEventListener('change', processData);
function prepareSubmission() {
@@ -135,6 +269,13 @@
// Create and display table
createTable(rows, headers);
+
+ // Create hidden div with headers data
+ const headersDiv = document.createElement('div');
+ headersDiv.id = 'headersDiv';
+ headersDiv.classList.add('d-none');
+ headersDiv.innerHTML = JSON.stringify(headers);
+ document.body.appendChild(headersDiv);
}
function populateColumnSelectors(headers) {
@@ -142,12 +283,27 @@ function populateColumnSelectors(headers) {
const yColumnSelect = document.getElementById('yColumn');
const stdDevSelect = document.getElementById('stdDevColumn');
+ /*
headers.forEach((header, index) => {
let option = new Option(header, index);
xColumnSelect.add(option.cloneNode(true));
yColumnSelect.add(option.cloneNode(true));
stdDevSelect.add(option.cloneNode(true));
});
+ */
+
+ // Fill all data series
+
+ var allSelect = document.getElementsByClassName('table-data');
+ console.log(allSelect)
+
+ for (var i = 0; i < allSelect.length; i++) {
+ for (var j = 0; j < headers.length; j++) {
+ let option = new Option(headers[j], j);
+ allSelect[i].add(option.cloneNode(true));
+ }
+ }
+
}
function createTable(data, headers) {
@@ -176,6 +332,9 @@ function createTable(data, headers) {
<label for="constantLineLabel${constantLineCount}">Line Title</label>
<input type="text" name="constantLineLabel${constantLineCount}" id="constantLineLabel${constantLineCount}" class="form-control">
</div>
+ <div class="col-md-4">
+ <button type="button" class="btn btn-outline-danger mb-3 mt-4" onclick="this.parentElement.parentElement.remove()">Remove Constant Line</button>
+ </div>
</div>
`;
container.appendChild(line);