diff options
author | Navan Chauhan <navanchauhan@gmail.com> | 2023-12-10 17:39:13 -0700 |
---|---|---|
committer | Navan Chauhan <navanchauhan@gmail.com> | 2023-12-10 17:39:13 -0700 |
commit | a593dfa50b5847c98105d8de14969eab790f60b5 (patch) | |
tree | bbb26f9882c9a30e50b69780c6e2aec803b747f7 | |
parent | 4cac86ab4a2e66fc7e1a6ce2c80d169073881936 (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.py | 178 | ||||
-rw-r--r-- | templates/index.html | 211 |
2 files changed, 325 insertions, 64 deletions
@@ -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); |