我有一个 Flask/javascript Web 应用程序,但在通过事件侦听器传递信息时遇到问题。
这是我的代码:
fetch('/get_data')
.then(response => response.json())
.then(data => {
const toggleValue1 = document.getElementById("toggleValue1");
const toggleValue2 = document.getElementById("toggleValue2");
// Initial render with both values displayed
renderChart(data, toggleValue1.checked, toggleValue2.checked);
// Update chart when checkboxes change
toggleValue1.addEventListener("change", () => renderChart(data, toggleValue1.checked, toggleValue2.checked));
toggleValue2.addEventListener("change", () => renderChart(data, toggleValue1.checked, toggleValue2.checked));
})
.catch(error => console.error("Error fetching data:", error));
/get_data
是一个烧瓶端点,定义如下:
@app.route('/get_data')
def timeseries():
# Sample time series data
data = [
{"date": "2024-01-01", "value1": 10, "value2": 15},
{"date": "2024-02-01", "value1": 20, "value2": 10},
{"date": "2024-03-01", "value1": 15, "value2": 20},
{"date": "2024-04-01", "value1": 30, "value2": 25},
{"date": "2024-05-01", "value1": 25, "value2": 30},
]
return jsonify(data)
我打印出了
data
的值,发现在初次调用renderChart()
时以及初次调用renderChart()
之后,一切都是正确的。但是,在切换复选框并且事件侦听器调用 renderChart()
后,data
的日期为空值,如下所示:
[
{"date": null, "value1": 10, "value2": 15},
{"date": null, "value1": 20, "value2": 10},
{"date": null, "value1": 15, "value2": 20},
{"date": null, "value1": 30, "value2": 25},
{"date": null, "value1": 25, "value2": 30},
]
我不明白为什么日期被变成空。有谁知道为什么会这样?
这是我的完整代码:
from flask import Flask, render_template, jsonify
app = Flask(__name__)
@app.route('/data_visualizations', methods=["GET", "POST"])
def get_data_visualizations():
return render_template('data_visualizations.html')
@app.route('/get_data')
def timeseries():
# Sample time series data
data = [
{"date": "2024-01-01", "value1": 10, "value2": 15},
{"date": "2024-02-01", "value1": 20, "value2": 10},
{"date": "2024-03-01", "value1": 15, "value2": 20},
{"date": "2024-04-01", "value1": 30, "value2": 25},
{"date": "2024-05-01", "value1": 25, "value2": 30},
]
return jsonify(data)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=True)
#app.run(ssl_context=('cert.pem', 'key.pem'))
// Chart area and margins
const margin = { top: 20, right: 30, bottom: 50, left: 60 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const color = { value1: "blue", value2: "orange" };
// Scales and axes
const x = d3.scaleTime().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
const xAxisGroup = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${height})`);
const yAxisGroup = svg.append("g")
.attr("class", "y-axis");
// Line generators
const lineValue1 = d3.line()
.x(d => x(d.date))
.y(d => y(d.value1));
const lineValue2 = d3.line()
.x(d => x(d.date))
.y(d => y(d.value2));
// Function to render the chart
function renderChart(data, displayValue1, displayValue2) {
console.log("Data 1: ", data)
// Parse dates and set up data bounds
const parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(d => {
d.date = parseTime(d.date);
d.value1 = +d.value1;
d.value2 = +d.value2;
});
console.log("Data 2: ", data)
// Set domains for scales
x.domain(d3.extent(data, d => d.date));
y.domain([0, d3.max(data, d => Math.max(d.value1, d.value2))]);
// Draw the axes
xAxisGroup.call(d3.axisBottom(x));
yAxisGroup.call(d3.axisLeft(y));
// Clear existing lines
svg.selectAll(".line-value1").remove();
svg.selectAll(".line-value2").remove();
// Draw lines based on checkbox selections
if (displayValue1) {
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", color.value1)
.attr("stroke-width", 2)
.attr("class", "line-value1")
.attr("d", lineValue1);
}
if (displayValue2) {
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", color.value2)
.attr("stroke-width", 2)
.attr("class", "line-value2")
.attr("d", lineValue2);
}
console.log("Data 3: ", data)
}
// Apply zoom behavior to the SVG container
svg.on(".zoom", null);
// Fetch data from the Flask API and initialize the chart
fetch('/get_data')
.then(response => response.json())
.then(data => {
const toggleValue1 = document.getElementById("toggleValue1");
const toggleValue2 = document.getElementById("toggleValue2");
// Initial render with both values displayed
renderChart(data, toggleValue1.checked, toggleValue2.checked);
console.log("Data 4: ", data)
// Update chart when checkboxes change
toggleValue1.addEventListener("change", () => renderChart(data, toggleValue1.checked, toggleValue2.checked));
toggleValue2.addEventListener("change", () => renderChart(data, toggleValue1.checked, toggleValue2.checked));
})
.catch(error => console.error("Error fetching data:", error));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Time Series Line Graph with Toggle</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<div class="container">
<h2>Time Series Data</h2>
<div class="controls">
<label><input type="checkbox" id="toggleValue1" checked> Value 1</label>
<label><input type="checkbox" id="toggleValue2" checked> Value 2</label>
</div>
<div id="chart"></div>
</div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>
我认为您的
script.js
文件中发生了错误。
在函数
renderChart
上,您使用 data
for 循环,但更改了原始源 data
,因此当您切换复选框时,参数 data
已被循环中先前的操作更改(因此 parseTime
方法失败了null
)。
解决方案:
// by destructure the array
toggleValue1.addEventListener("change", () => renderChart([...data], toggleValue1.checked, toggleValue2.checked));
toggleValue2.addEventListener("change", () => renderChart([...data], toggleValue1.checked, toggleValue2.checked));
// or not modify the origin data
function renderChart(data, displayValue1, displayValue2) {
const _data = [...data];
console.log("Data 1: ", _data)
// Parse dates and set up data bounds
const parseTime = d3.timeParse("%Y-%m-%d");
_data.forEach(d => {
d.date = parseTime(d.date);
d.value1 = +d.value1;
d.value2 = +d.value2;
});
// replace all data to _data
...
}