Skip to content

Commit

Permalink
Fix model plotting
Browse files Browse the repository at this point in the history
  • Loading branch information
mhauru committed Apr 12, 2024
1 parent 9722828 commit 0b25304
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 116 deletions.
139 changes: 73 additions & 66 deletions dtbase/frontend/app/base/static/typescript/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,71 +32,72 @@ Chart.register(
Filler,
)

// Colours for the lines for different model measures.
const COLOR_ALPHA = 0.7
const COLORS = [
`rgba(54, 162, 235, ${COLOR_ALPHA})`, // Blue
`rgba(255, 99, 132, ${COLOR_ALPHA})`, // Red
`rgba(255, 159, 64, ${COLOR_ALPHA})`, // Orange
`rgba(255, 205, 86, ${COLOR_ALPHA})`, // Yellow
`rgba(75, 192, 192, ${COLOR_ALPHA})`, // Green
`rgba(153, 102, 255, ${COLOR_ALPHA})`, // Purple
]

function getColor(i: number) {
return COLORS[i % COLORS.length]
}

export function plot(
top_json: TimeseriesDataPoint<number>[] | null,
mid_json: TimeseriesDataPoint<number>[] | null,
bot_json: TimeseriesDataPoint<number>[] | null,
sensor_json: TimeseriesDataPoint<number>[] | null,
sensor_measure: {
name: string
units: string | null
model_data: {
pred_data: {
measure: {
name: string
units: string | null
}
values: TimeseriesDataPoint<number>[]
}[]
sensor_data: {
measure: {
name: string
units: string | null
}
sensor_uniq_id: string
readings: TimeseriesDataPoint<number>[]
}
},
canvas_name: string,
y_label: string,
show_legend: boolean,
): Chart {
const datasets = []
if (top_json !== null) {
const values_top = top_json.map((e) => e["value"])
const times_top = top_json.map((e) => new Date(e["timestamp"]))
const top_scatter = dictionary_scatter(times_top, values_top)
const pred_data = model_data["pred_data"]
for (let i = 0; i < pred_data.length; i++) {
const results = pred_data[i]
const name = results["measure"]["name"]
const units = results["measure"]["units"]
const values = results["values"].map((e) => e["value"])
const times = results["values"].map((e) => new Date(e["timestamp"]))
const scatter = dictionary_scatter(times, values)
datasets.push({
label: "Upper bound",
data: top_scatter,
borderColor: "#ee978c",
label: `${name} (${units})`,
data: scatter,
borderColor: getColor(i),
fill: false,
borderDash: [1],
pointRadius: 1,
showLine: true,
})
}
if (mid_json !== null) {
const values_mid = mid_json.map((e) => e["value"])
const times_mid = mid_json.map((e) => new Date(e["timestamp"]))
const mid_scatter = dictionary_scatter(times_mid, values_mid)
datasets.push({
label: "Mean",
data: mid_scatter,
borderColor: "#ff0000",
fill: "-1",
borderDash: [2],
pointRadius: 1.5,
showLine: true,
})
}
if (bot_json !== null) {
const values_bot = bot_json.map((e) => e["value"])
const times_bot = bot_json.map((e) => new Date(e["timestamp"]))
const bot_scatter = dictionary_scatter(times_bot, values_bot)
datasets.push({
label: "Lower bound",
data: bot_scatter,
borderColor: "#ee978c",
fill: "-1",
borderDash: [1],
pointRadius: 1,
showLine: true,
})
}

if (sensor_json !== null) {
const values_sensor = sensor_json.map((e) => e["value"])
const times_sensor = sensor_json.map((e) => new Date(e["timestamp"]))
const sensor_scatter = dictionary_scatter(times_sensor, values_sensor)
const sensor_data = model_data["sensor_data"]
if (sensor_data !== null && sensor_data !== undefined) {
const sensor_unique_id = sensor_data["sensor_uniq_id"]
const name = sensor_data["measure"]["name"]
const units = sensor_data["measure"]["units"]
const values = sensor_data["readings"].map((e) => e["value"])
const times = sensor_data["readings"].map((e) => new Date(e["timestamp"]))
const scatter = dictionary_scatter(times, values)
datasets.push({
label: "Sensor reading",
data: sensor_scatter,
borderColor: "#a0a0a0",
label: `${sensor_unique_id} readings: ${name} (${units})`,
data: scatter,
borderColor: `rgba(201, 203, 207, ${COLOR_ALPHA})`,
fill: false,
pointRadius: 1.5,
showLine: true,
Expand All @@ -108,6 +109,7 @@ export function plot(
datasets: datasets,
}

const show_legend = true
const config = {
type: "scatter",
data: data,
Expand All @@ -121,15 +123,13 @@ export function plot(
legend: {
display: show_legend,
},
colors: {
enabled: true,
},
},
scales: {
y: {
display: true,
title: {
display: true,
text: y_label,
font: { size: 18 },
},
},
x: {
type: "time",
Expand Down Expand Up @@ -192,17 +192,24 @@ export function updateScenarioSelector(
declare global {
interface Window {
plot: (
top_json: TimeseriesDataPoint<number>[] | null,
mid_json: TimeseriesDataPoint<number>[] | null,
bot_json: TimeseriesDataPoint<number>[] | null,
sensor_json: TimeseriesDataPoint<number>[] | null,
sensor_measure: {
name: string
units: string | null
model_data: {
pred_data: {
measure: {
name: string
units: string | null
}
values: TimeseriesDataPoint<number>[]
}[]
sensor_data: {
measure: {
name: string
units: string | null
}
sensor_uniq_id: string
readings: TimeseriesDataPoint<number>[]
}
},
canvas_name: string,
y_label: string,
show_legend: boolean,
) => void
updateScenarioSelector: (
scenarios: ModelScenario[],
Expand Down
17 changes: 13 additions & 4 deletions dtbase/frontend/app/models/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def get_run_sensor_data(
run_id: int | str,
earliest_timestamp: dt.datetime,
last_timestamp: Optional[dt.datetime] = None,
) -> Dict[str, Any]:
) -> Optional[Dict[str, Any]]:
"""
Get the real data to which the prediction of a ModelRun should be compared
Expand All @@ -104,8 +104,9 @@ def get_run_sensor_data(
last_timestamp: Timestamp of the last prediction point. Optional, by default now.
Returns:
dict, with keys "sensor_uniq_id", "measure_name", "readings", where "readings" is
a list of (value, timestamp) tuples.
Assuming this model run has sensor data associated with it, the return value is a
dic, with keys "sensor_uniq_id", "measure_name", "readings", where "readings" is
a list of (value, timestamp) tuples. Otherwise None.
"""
response = current_user.backend_call(
"post", "/model/get-model-run-sensor-measure", {"run_id": run_id}
Expand All @@ -114,6 +115,9 @@ def get_run_sensor_data(
raise RuntimeError(f"A backend call failed: {response}")
measure = response.json()["sensor_measure"]
sensor_uniq_id = response.json()["sensor_unique_id"]
if not measure or not sensor_uniq_id:
# No sensor data is associated with this model run.
return None
dt_from = earliest_timestamp
if last_timestamp:
dt_to = last_timestamp
Expand Down Expand Up @@ -163,9 +167,14 @@ def fetch_run_data(run_id: int | str) -> Dict[str, Any]:
if last_timestamp is None or end_timestamp > last_timestamp:
last_timestamp = end_timestamp
if earliest_timestamp and last_timestamp:
# Extend the period for which we have measure data backwards by 10%, to provide
# some context for the model predictions.
earliest_timestamp = earliest_timestamp - 0.1 * (
last_timestamp - earliest_timestamp
)
sensor_data = get_run_sensor_data(run_id, earliest_timestamp, last_timestamp)
else:
sensor_data = {}
sensor_data = None
return {"pred_data": pred_data, "sensor_data": sensor_data}


Expand Down
47 changes: 1 addition & 46 deletions dtbase/frontend/app/models/templates/models.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,54 +86,9 @@ <h3>Model predictions</h3>
{{ super()}}
<script src="{{ url_for('static', filename='javascript/models.js') }}"></script>
<script>

function plot_predictions(model_data) {
const pred_data = model_data["pred_data"];
const sensor_data = model_data["sensor_data"];
const measure = sensor_data["measure"]
const sensor_id = sensor_data["sensor_uniq_id"];
const canvas_name = "model_plot";
const measure_string = `${measure["name"]} (${measure["units"]})`
const title = `${measure_string}, sensor ${sensor_id}`;
document.getElementById(canvas_name).innerHTML = title;
// See static/javascript/predictions.js for the plot function.
const show_legend = true;
const axis_title = measure_string;

let mid_line = null;
let top_line = null;
let bot_line = null;
for (const results of pred_data) {
const name = results["measure"]["name"]
const units = results["measure"]["units"]
if ((name == measure["name"] || name == "Mean " + measure["name"]) && units == measure["units"]) {
mid_line = results["values"]
continue
}
if (name == "Upper Bound " + measure["name"] && units == measure["units"]) {
top_line = results["values"]
}
if (name == "Lower Bound " + measure["name"] && units == measure["units"]) {
bot_line = results["values"]
}
}

window.plot(
top_line,
mid_line,
bot_line,
sensor_data["readings"],
measure,
canvas_name,
axis_title,
show_legend
);

}

const model_data = {{model_data | tojson | safe}};
if (model_data !== null && Object.keys(model_data).length > 0) {
plot_predictions(model_data);
window.plot(model_data, "model_plot");
}

const scenarios = {{scenarios | tojson | safe}}
Expand Down

0 comments on commit 0b25304

Please sign in to comment.