Upon interacting with range slider in my scatter plot the data keeps shifting vertically towards the top and I do not know how to stop it. Previously, the entire plot kept stretching vertically downwards and after setting a fixed height this is the behaviour now. This ONLY happens when i try to plot just one column from a pandas dataframe, it all works as expected with more than one column. Moreover, the behaviour fixes itself after I minimize and maximize the browser.
Right at this moment I can not provide any code snippets as Iβd need to rewrite a clean version of the code for it (do not want to paste something work sensitive by accident).
This is how the basic plot looks initially:
Then after interacting with the rangeslider:
The more you interact with it the more the data shifts:
As I was taking these screenshots I found another two ways to stop this behaviour:
- move the left rangeslider edge past the right one,
- select a VERY small portion of data.
None of these work in my case as on default the data should be loaded as βallβ.
Are there some common mistakes Iβve been a subject to or should I take some time and provide a clean code snippet?
EDIT: Decided to take some time and write a quick and dirty code snippet with identical behaviour:
app.py
from flask import Flask, render_template, Response, request, jsonify
import pandas as pd
import numpy as np
import plotly.graph_objects as go
app = Flask(__name__)
def line_stacked(table_name: str, columns: list[str], distribution: str, inflation: bool = False) -> go.Figure():
"""
Makes a stacked line plot where each additional value is put on top of the previous one, it can display it either
in nominal values or as a proportional of total.
Parameters
----------
table_name: str
Name of a SQL table to query
columns: list[str]
A list of columns to display in the plot
distribution: str
Keyword for either nominal or distributional value display
inflation: bool, Default False
Adjust historic data by Retail Price Index to display it in modern values
Returns
-------
fig: go.Figure()
Plotly stacked line plot figure
"""
np.random.seed(0)
date_range = pd.date_range(start='2018-01-01', end='2023-12-01', freq='MS')
data = {
"date": date_range,
"var1": np.random.randint(1000, 5000, size=len(date_range)),
"var2": np.random.randint(1000, 5000, size=len(date_range)),
"var3": np.random.randint(1000, 5000, size=len(date_range)),
"var4": np.random.randint(1000, 5000, size=len(date_range)),
}
df = pd.DataFrame(data, index=None)
df["grp1"] = df["var1"] + df["var2"]
df["grp2"] = df["var3"] + df["var4"]
df["total"] = df["grp1"] + df["grp2"]
# Usually this is used to query data.
# df = fetch_data_as_df(table_name, current_user.owner)
# Commented out since does not impact the behaviour
# if inflation:
# df = adjust_df_for_inflation(df)
fig = go.Figure()
if distribution == "proportional":
for col in columns:
fig.add_trace(go.Scatter(x=df["date"],
y=(df[col] / df["total"]).fillna(0),
mode="lines",
fill="tonexty",
name=col.title(),
stackgroup="one",
hoverinfo="x+y+text",
text=f"{col.title()}",
showlegend=True))
fig.update_layout(
yaxis=dict(tickvals=[i / 10 for i in range(11)], range=[0, 1], title="Proportion (100%)")
)
elif distribution == "nominal":
for col in columns:
fig.add_trace(go.Scatter(x=df["date"], y=df[col],
mode="lines",
fill="tonexty",
name=col.title(),
stackgroup="one",
hoverinfo="x+y+text",
text=f"{col.title()}",
showlegend=True))
fig.update_layout(
xaxis=dict(
title="Date",
rangeselector=dict(
buttons=list([
dict(count=1, label="1y", step="year", stepmode="backward"),
dict(count=2, label="2y", step="year", stepmode="backward"),
dict(count=5, label="5y", step="year", stepmode="backward"),
dict(step="all", stepmode="backward")
])
),
rangeslider=dict(visible=True)
),
hovermode="x unified")
return fig
@app.route("/lineplot", methods=["POST"])
def lineplot() -> Response | None:
data = request.json
table_name = data["table_name"]
columns = data["column_names"]
distribution = data["distribution"]
inflation = data["inflation"]
fig = line_stacked(table_name, columns, distribution, inflation)
return jsonify(fig.to_json())
@app.route("/")
def index() -> str:
return render_template("index.html")
if __name__ == "__main__":
app.run(debug=True)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
<script src="https://cdn.plot.ly/plotly-latest.min.js" charset="utf-8"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
.slim-select {
max-width: 200px;
}
.plot-container {
margin-top: 20px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #f9f9f9;
}
.form-check-label {
margin-left: 8px;
}
.lineplot{
height: 500px;
}
</style>
<title>Plot Bug</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container mt-5">
<div class="plot-container">
<form id="plotForm" class="row g-3 align-items-center">
<div class="col-md-3 slim-select">
<label for="sortby" class="form-label">Sort by</label>
<select id="sortby" name="sortby" class="form-select">
<option value="sector">Sector</option>
<option value="category">Category</option>
</select>
</div>
<div class="col-md-3 slim-select">
<label for="columns" class="form-label">Selection</label>
<select id="columns" name="columns" class="form-select">
<!-- To be populated by JS -->
</select>
</div>
<div class="col-md-3 slim-select">
<label for="distribution" class="form-label">Distribution</label>
<select id="distribution" name="distribution" class="form-select">
<option value="nominal">Nominal</option>
<option value="proportional">Proportional</option>
</select>
</div>
<div class="col-md-2">
<div class="form-check pt-4">
<input type="checkbox" id="inflation" name="inflation" class="form-check-input">
<label for="inflation" class="form-check-label">Inflation</label>
</div>
</div>
</form>
<div id="plot" class="mt-5 lineplot"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.min.js"></script>
<script>
$(document).ready(function() {
const sectorOptions = {
detailed: ["var1", "var2", "var3", "var4"],
grouped: ["grp1", "grp2"],
total: ["total"]
};
const categoryOptions = {
detailed: ["var1", "var2", "var3", "var4"],
grouped: ["grp1", "grp2"],
total: ["total"]
};
const config = {responsive: true};
function updateColumnsDropdown(sortbyDropdown) {
const columnsDropdown = $("#columns");
columnsDropdown.empty();
let options;
if (sortbyDropdown === "sector") {
options = sectorOptions;
} else if (sortbyDropdown === "category") {
options = categoryOptions;
}
if (options) {
columnsDropdown.append("<option value='total'>Total</option>>");
columnsDropdown.append("<option value='grouped'>Grouped</option>");
columnsDropdown.append("<option value='detailed'>Detailed</option>")
}
submitForm();
}
function submitForm() {
var table_name = $("#sortby").val();
var columnsSelection = $("#columns").val();
var columns = table_name === "category" ? categoryOptions[columnsSelection] : sectorOptions[columnsSelection];
var distribution = $('#distribution').val();
var inflation = $('#inflation').is(':checked');
var formData = {
table_name: table_name,
column_names: columns,
distribution: distribution,
inflation: inflation
};
$.ajax({
type: 'POST',
url: '/lineplot',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function (response) {
var plotData = JSON.parse(response);
Plotly.purge("plot");
Plotly.newPlot('plot', plotData.data, plotData.layout, config);
},
});
}
$("#plotForm").submit(function(event){
event.preventDefault();
submitForm();
});
$("#sortby").change(function () {
const sortbySelection = $(this).val();
updateColumnsDropdown(sortbySelection);
});
$("#columns, #distribution, #inflation").change(function () {
submitForm();
});
updateColumnsDropdown($("#sortby").val())});
</script>
</body>