diff --git a/epdb/models.py b/epdb/models.py
index 00fcd175..cdbcd64c 100644
--- a/epdb/models.py
+++ b/epdb/models.py
@@ -2189,6 +2189,7 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
"uncovered_functional_groups": False,
},
"is_engineered_intermediate": self.kv.get("is_engineered_intermediate", False),
+ "timeseries": self.get_timeseries_data(),
}
@staticmethod
@@ -2226,6 +2227,13 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin):
def as_svg(self):
return IndigoUtils.mol_to_svg(self.default_node_label.smiles)
+ def get_timeseries_data(self):
+ for scenario in self.scenarios.all():
+ for ai in scenario.get_additional_information():
+ if ai.__class__.__name__ == "OECD301FTimeSeries":
+ return ai.model_dump(mode="json")
+ return None
+
def get_app_domain_assessment_data(self):
data = self.kv.get("app_domain_assessment", None)
diff --git a/pyproject.toml b/pyproject.toml
index a19f10d5..c2b14a82 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -36,7 +36,7 @@ dependencies = [
[tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.4" }
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
-envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.2.0" }
+envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.4.0" }
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
[project.optional-dependencies]
diff --git a/static/js/pw.js b/static/js/pw.js
index c06e8668..e3639a03 100644
--- a/static/js/pw.js
+++ b/static/js/pw.js
@@ -361,6 +361,83 @@ function draw(pathway, elem) {
function node_popup(n) {
popupContent = "";
+
+ if (timeseriesViewEnabled && n.timeseries && n.timeseries.measurements) {
+ for (var s of n.scenarios) {
+ popupContent += "" + s.name + "
";
+ }
+
+ popupContent += '
';
+ const tsMeasurements = n.timeseries.measurements;
+ setTimeout(() => {
+ const canvas = document.getElementById('ts-popover-canvas');
+ if (canvas && window.Chart) {
+ const valid = tsMeasurements
+ .filter(m => m.timestamp != null && m.value != null)
+ .map(m => ({ ...m, timestamp: typeof m.timestamp === 'number' ? m.timestamp : new Date(m.timestamp).getTime() }))
+ .sort((a, b) => a.timestamp - b.timestamp);
+
+ const datasets = [];
+
+ // Error band (lower + upper with fill between)
+ const withErrors = valid.filter(m => m.error != null && m.error > 0);
+ if (withErrors.length > 0) {
+ datasets.push({
+ data: withErrors.map(m => ({ x: m.timestamp, y: m.value - m.error })),
+ borderColor: 'rgba(59,130,246,0.3)',
+ backgroundColor: 'rgba(59,130,246,0.15)',
+ pointRadius: 0,
+ fill: false,
+ tension: 0.1,
+ });
+ datasets.push({
+ data: withErrors.map(m => ({ x: m.timestamp, y: m.value + m.error })),
+ borderColor: 'rgba(59,130,246,0.3)',
+ backgroundColor: 'rgba(59,130,246,0.15)',
+ pointRadius: 0,
+ fill: '-1',
+ tension: 0.1,
+ });
+ }
+
+ // Main value line
+ datasets.push({
+ data: valid.map(m => ({ x: m.timestamp, y: m.value })),
+ borderColor: 'rgb(59,130,246)',
+ pointRadius: 0,
+ tension: 0.1,
+ fill: false,
+ });
+
+ new Chart(canvas.getContext('2d'), {
+ type: 'line',
+ data: { datasets },
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: { display: false },
+ tooltip: { enabled: false },
+ },
+ scales: {
+ x: {
+ type: 'linear',
+ ticks: { font: { size: 10 } },
+ title: { display: false },
+ },
+ y: {
+ ticks: { font: { size: 10 } },
+ title: { display: false },
+ },
+ },
+ },
+ });
+ }
+ }, 0);
+
+ return popupContent;
+ }
+
if (n.stereo_removed) {
popupContent += "Removed stereochemistry for prediction";
}
diff --git a/templates/objects/pathway.html b/templates/objects/pathway.html
index 549a6aae..ca7ecd5a 100644
--- a/templates/objects/pathway.html
+++ b/templates/objects/pathway.html
@@ -61,6 +61,11 @@
stroke-opacity: 0.6;
}
+ .has_timeseries {
+ stroke: #3b82f6;
+ stroke-width: 3px;
+ }
+
.highlighted {
stroke: red;
stroke-width: 3px;
@@ -134,30 +139,30 @@
{% include "actions/objects/pathway.html" %}
- {% if pathway.setting.model.app_domain %}
-
- {% endif %}
+ {% endif %}
+
+
+
+ OECD 301F View
+
+
+
+