Boyle's Law of Gases, P v V

Stephen Lukacs (2) iquanta.org/instruct/python
Enter Pressure vs. Volume trial below...
(V(mL), P(kPa), Data Entry.. .


, or, just Upload to run the demonstration.
"""
reference: https://iquanta.org/instruct/python ::: Chemistry 9: P v V, Boyle's Law of Gases, P v V ::: Stephen Lukacs, Ph.D. ©2021-11-28
"""
from py4web import request
from yatl.helpers import *
from iquanta.mcp import is_str_float, str_to_float
from numpy import linspace, mean, std
from scipy.optimize import curve_fit
import plotly.graph_objects as go

BR, B = TAG['br/'], TAG['b']

demo_data = "5.8, 198.70\n6.8, 167.62\n7.8, 142.98\n8.8, 130.45\n9.8, 116.39\n10.8, 103.2\n11.8, 93.52\n12.8, 87.33\n13.8, 79.53\n14.8, 74.28\n15.8, 69.33\n16.8, 65.38\n17.8, 62.60\n18.8, 59.28\n19.8, 57.38\n20.8, 54.76"

rtn = FORM(_action=None, _method="post")
if ('txtfile' in request.forms):
    txtfile = request.forms['txtfile']
    if (txtfile.count('\t') > 0):
        txtfile = '\n'.join([l.strip().replace('\t', ', ') for l in txtfile.replace('\r','').split('\n')])
        rtn.append(CAT("Converted.. .", PRE(r"{}".format(txtfile.encode('utf-8'))), BR()))
    txt = txtfile
    data = [ ]
    for l in txt.split('\n'):
        if (l.find(',') > -1) and is_str_float(l.split(',')[0]):
            data.append(tuple([str_to_float(d.strip()) for d in l.strip().split(',')]))
    #rtn.append(CAT(data, BR()))

rtn.append(STYLE("input[type=text] { width: 70px; text-align: center; border-radius: 7px; } textarea { margin: 0px; width: 295px; height: 200px; border-radius: 5px; } p { margin: 2px 0px; padding: 8px; border-radius: 10px; border: 2px solid silver; }"))
rtn.append(CAT(DIV("Enter Pressure vs. Volume trial below...", BR(), "(V(mL), P(kPa), Data Entry.. .", BR(), TEXTAREA(txtfile if ('txtfile' in locals()) else demo_data, _name="txtfile"), _style="float:left;"), DIV(*[BR()]*2, INPUT(_type="submit", _value="Upload"), ", or, just Upload to run the demonstration.", _style="float:left; margin: 5px;"), DIV(_style="float:none;clear:both;")))

extra_x = lambda dset, extra: linspace(dset[0], dset[-1], num=(extra*(len(dset)-1)+len(dset)))
power = lambda x, a, b: a*x**b
def doFit(x, y):
    fit = curve_fit(power, x, y, p0=(1000., -1.))
    params, ymean = fit[0], mean(y)
    SSres, SStot = sum([(yy - power(xx, *params))**2 for xx, yy in zip(x,y)]), sum([(d - ymean)**2 for d in y])
    return params, (1 - SSres / SStot)

if ('data' in locals()):
    x, y = [d[0] for d in data], [d[1] for d in data]
    fit1st, R2 = doFit(x, y)
    #rtn.append(SPAN(fit1st, BR(), RSquared1st))
    fxn1st = lambda V: fit1st[0]*V**fit1st[1]
    rtn.append(SPAN("power fit: ", XML('P = {0:.1f} V<sup>{1:.4f}</sup>, R<sup>2</sup>: {2:.5f}'.format(*fit1st, R2)), _style="font-size:24pt;"))

    fig = go.Figure()
    xss = extra_x(x, 4)
    fig.add_trace(go.Scatter(x=xss, y=[fxn1st(d) for d in xss], mode="lines", name="fit"))
    fig.add_trace(go.Scatter(x=x, y=y, mode='markers', name="data", marker=go.scatter.Marker(color="Black")))
    fig.update_layout(xaxis=go.XAxis(title="Volume (mL)"), yaxis=go.YAxis(title="Pressure (kPa)", anchor='x', side='left'))
    fig.update_layout(title_text="Boyle's Law of Gases, Pressure versus Volume at Constant Temperature", height=750)
    html = fig.to_html()
    rtn.append(XML(html[html.find('<div>'):html.rfind('</div>')+6].replace('<div>', '<div id="plotly">')))