aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/admin/routes.py4
-rw-r--r--app/main/demo.py31
-rw-r--r--app/main/routes.py5
-rw-r--r--app/static/css/admin/view_annotation.css6
-rw-r--r--app/static/css/demo/evaluate.css5
-rw-r--r--app/static/css/main/annotate.css5
-rw-r--r--app/static/js/buttons.js14
-rw-r--r--app/static/js/makeChart.js1
-rw-r--r--app/static/js/makeChartMulti.js319
-rw-r--r--app/static/js/updateTable.js74
-rw-r--r--app/templates/admin/annotations_by_dataset.html4
-rw-r--r--app/templates/annotate/index.html6
-rw-r--r--app/templates/demo/evaluate.html37
-rw-r--r--demo_data/demo_800.json224
14 files changed, 709 insertions, 26 deletions
diff --git a/app/admin/routes.py b/app/admin/routes.py
index b8ca81b..9b7688b 100644
--- a/app/admin/routes.py
+++ b/app/admin/routes.py
@@ -129,7 +129,7 @@ def manage_users():
@admin_required
def manage_datasets():
dataset_list = [(d.id, d.name) for d in Dataset.query.all()]
- dataset_list.sort(key=lambda x : x[1])
+ dataset_list.sort(key=lambda x: x[1])
form = AdminManageDatasetsForm()
form.dataset.choices = dataset_list
@@ -281,11 +281,13 @@ def view_annotations_by_dataset(dset_id):
anno_clean.append(dict(user=uid, index=ann.cp_index))
data = load_data_for_chart(dataset.name, dataset.md5sum)
+ is_multi = len(data["chart_data"]["values"]) > 1
data["annotations"] = anno_clean
return render_template(
"admin/annotations_by_dataset.html",
title="View Annotations for dataset",
data=data,
+ is_multi=is_multi,
)
diff --git a/app/main/demo.py b/app/main/demo.py
index afb7f61..2edae04 100644
--- a/app/main/demo.py
+++ b/app/main/demo.py
@@ -252,6 +252,32 @@ DEMO_DATA = {
)
},
},
+ 8: {
+ "dataset": {"name": "demo_800"},
+ "learn": {
+ "text": markdown.markdown(
+ textwrap.dedent(
+ """
+ In practice time series datasets are not just one
+ dimensional, but can be multidimensional too. A change
+ point in such a time series does not necessarily occur in
+ all dimensions simultaneously. It is therefore important to
+ evaluate the behaviour of each dimension individually, as
+ well as in relation to the others."""
+ )
+ )
+ },
+ "annotate": {"text": RUBRIC},
+ "evaluate": {
+ "text": markdown.markdown(
+ textwrap.dedent(
+ """
+ In this example of a multidimensional time series, the
+ change only occurred in a single dimension."""
+ )
+ )
+ },
+ },
}
@@ -373,13 +399,16 @@ def demo_annotate(demo_id):
"error",
)
return redirect(url_for("main.index"))
+
chart_data = load_data_for_chart(dataset.name, dataset.md5sum)
+ is_multi = len(chart_data["chart_data"]["values"]) > 1
return render_template(
"annotate/index.html",
title="Introduction – %i" % demo_id,
data=chart_data,
rubric=demo_data["text"],
identifier=demo_id,
+ is_multi=is_multi,
)
@@ -398,6 +427,7 @@ def demo_evaluate(demo_id, phase_id, form):
name=DEMO_DATA[demo_id]["dataset"]["name"]
).first()
chart_data = load_data_for_chart(dataset.name, dataset.md5sum)
+ is_multi = len(chart_data["chart_data"]["values"]) > 1
true_changepoints = get_demo_true_cps(dataset.name)
if true_changepoints is None:
flash(
@@ -415,6 +445,7 @@ def demo_evaluate(demo_id, phase_id, form):
annotations_true=annotations_true,
text=demo_data["text"],
form=form,
+ is_multi=is_multi
)
diff --git a/app/main/routes.py b/app/main/routes.py
index ad78f27..02ec7c9 100644
--- a/app/main/routes.py
+++ b/app/main/routes.py
@@ -18,7 +18,8 @@ logger = logging.getLogger(__name__)
RUBRIC = """
Please mark the point(s) in the time series where an <b>abrupt change</b> in
the behaviour of the series occurs. The goal is to define segments of the time
- series that are separated by places where these abrupt changes occur.
+ series that are separated by places where these abrupt changes occur. Recall
+ that it is also possible for there to be no such changes.
<br>
"""
@@ -112,10 +113,12 @@ def annotate(task_id):
flash(
"An internal error occurred loading this dataset, the admin has been notified. Please try again later. We apologise for the inconvenience."
)
+ is_multi = len(data["chart_data"]["values"]) > 1
return render_template(
"annotate/index.html",
title=task.dataset.name.title(),
identifier=task.id,
data=data,
rubric=RUBRIC,
+ is_multi=is_multi,
)
diff --git a/app/static/css/admin/view_annotation.css b/app/static/css/admin/view_annotation.css
index 11bf3c8..09d5a4d 100644
--- a/app/static/css/admin/view_annotation.css
+++ b/app/static/css/admin/view_annotation.css
@@ -9,6 +9,12 @@
clip-path: url(#clip);
}
+.z-line line {
+ stroke: #ccc;
+ shape-rendering: crispEdges;
+}
+
+
.ann-line {
fill: none;
stroke-dasharray: 5;
diff --git a/app/static/css/demo/evaluate.css b/app/static/css/demo/evaluate.css
index 0c7dbc2..aea3162 100644
--- a/app/static/css/demo/evaluate.css
+++ b/app/static/css/demo/evaluate.css
@@ -19,6 +19,11 @@
clip-path: url(#clip);
}
+.z-line line {
+ stroke: #ccc;
+ shape-rendering: crispEdges;
+}
+
circle {
clip-path: url(#clip);
fill: blue;
diff --git a/app/static/css/main/annotate.css b/app/static/css/main/annotate.css
index 595f2a2..d1ff2b3 100644
--- a/app/static/css/main/annotate.css
+++ b/app/static/css/main/annotate.css
@@ -9,6 +9,11 @@
clip-path: url(#clip);
}
+.z-line line {
+ stroke: #ccc;
+ shape-rendering: crispEdges;
+}
+
circle {
clip-path: url(#clip);
fill: blue;
diff --git a/app/static/js/buttons.js b/app/static/js/buttons.js
index 75adad2..edd3f56 100644
--- a/app/static/js/buttons.js
+++ b/app/static/js/buttons.js
@@ -47,15 +47,19 @@ function submitOnClick(identifier) {
var obj = {};
obj["identifier"] = identifier;
obj["changepoints"] = [];
- var i, cp;
+
+ var i, cp, xval, seen = [];
for (i=0; i<changepoints.length; i++) {
cp = changepoints[i];
+ xval = cp.getAttribute("data_X");
elem = {
id: i,
- x: cp.getAttribute("data_X"),
- y: cp.getAttribute("data_Y")
+ x: xval
};
+ if (seen.includes(xval))
+ continue;
obj["changepoints"].push(elem);
+ seen.push(xval);
}
var xhr = new XMLHttpRequest();
@@ -66,11 +70,7 @@ function submitOnClick(identifier) {
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
window.location.href = xhr.responseText;
- console.log("XHR Success: " + xhr.responseText);
- } else {
- console.log("XHR Error: " + xhr.status);
}
};
xhr.send(JSON.stringify(obj));
}
-
diff --git a/app/static/js/makeChart.js b/app/static/js/makeChart.js
index 1a0283d..5d759bf 100644
--- a/app/static/js/makeChart.js
+++ b/app/static/js/makeChart.js
@@ -119,7 +119,6 @@ function baseChart(selector, data, clickFunction, annotations, annotationFunctio
// 3. rectangle to keep the drawing
// 4. line
// 5. circles with a click event
- //
// clip path
svg.append("defs")
diff --git a/app/static/js/makeChartMulti.js b/app/static/js/makeChartMulti.js
new file mode 100644
index 0000000..4b7168f
--- /dev/null
+++ b/app/static/js/makeChartMulti.js
@@ -0,0 +1,319 @@
+// Based on: https://tylernwolf.com/corrdisp/index.html
+
+/*
+# TODO NOTES:
+- we now have two definitions of the data (data and labelData). The best thing
+would be to preprocess the data such that it is formatted as labelData (probably)
+*/
+
+/*
+ * Our data is a struct with top-level keys "time" (optional) and "values"
+ * (required). The "values" object is an array with a variable length of
+ * variables.
+ */
+
+function preprocess(data) {
+ var cleanData = [];
+ var nVar = data.values.length;
+
+ if (data["time"] != null) {
+ console.warn("Time axis is not implemented yet. Ignoring.");
+ }
+ for (i=0; i<data.values[0].raw.length; i++) {
+ var item = {"X": i}
+ for (j=0; j<nVar; j++) {
+ item["Y" + j] = data.values[j].raw[i];
+ }
+ cleanData.push(item);
+ }
+ return cleanData;
+}
+
+
+function getLabelData(data, lbl) {
+ var lblData = [];
+ for (i=0; i<data.length; i++) {
+ var item = {"X": data[i]["X"], "Y": data[i][lbl]};
+ lblData.push(item);
+ }
+ return lblData;
+}
+
+function makeLabelData(data, numCharts) {
+ var labelData = {};
+ for (j=0; j<numCharts; j++) {
+ labelData[j] = getLabelData(data, "Y"+j);
+ }
+ return labelData;
+}
+
+function Axes(data, numCharts, width, lineHeight, chartPadding) {
+ this.chartColors = d3.scaleOrdinal();
+
+ var xRange = data.length;
+ var xDomainMin = 0 - xRange * 0.02;
+ var xDomainMax = data.length + xRange * 0.02;
+
+ // NOTE: domain needs to be adapted if we provide a time axis
+ this.xScale = d3.scaleLinear()
+ .domain([xDomainMin, xDomainMax])
+ .range([0, width]);
+
+ this.xScaleOrig = d3.scaleLinear()
+ .domain([xDomainMin, xDomainMax])
+ .range([0, width]);
+
+ this.xAxis = d3.axisBottom(this.xScale);
+
+ var labels = [];
+ var chartRange = [];
+
+ for (j=0; j<numCharts; j++) {
+ var v = [];
+ for (i=0; i<data.length; i++)
+ v.push(data[i]["Y" + j]);
+
+ var extent = d3.extent(v);
+ var range = extent[1] - extent[0];
+ var minVal = extent[0] - range * 0.05;
+ var maxVal = extent[1] + range * 0.05;
+
+ this["yScale" + j] = d3.scaleLinear();
+ this["yScale" + j].domain([minVal, maxVal]);
+ this["yScale" + j].range([lineHeight, 0]);
+
+ labels.push("Y" + j);
+ chartRange.push(j * (lineHeight + chartPadding));
+ }
+
+ this.charts = d3.scaleOrdinal();
+ this.charts.domain(labels);
+ this.charts.range(chartRange);
+};
+
+
+function baseChart(selector, data, clickFunction, annotations, annotationFunction) {
+ var lineObjects = {};
+ var pointSets = {}
+
+ var lineHeight = 150;
+ var lineWidth = 800;
+
+ var chartPadding = 30;
+ var chartWidth = 1000;
+
+ var visPadding = {
+ top: 10,
+ right: 20,
+ bottom: 10,
+ left: 70,
+ middle: 50
+ };
+
+ // Data preprocessing (TODO: remove need to have labelData *and*
+ // preprocess!)
+ var numCharts = data.values.length;
+ data = preprocess(data);
+ var labelData = makeLabelData(data, numCharts);
+
+ var width = lineWidth - visPadding.middle;
+ var height = (chartPadding + lineHeight) * numCharts + chartPadding + 50;
+
+ var axes = new Axes(data, numCharts, width, lineHeight, chartPadding);
+
+ var zoomObj = d3.zoom()
+ .scaleExtent([1, 100])
+ .translateExtent([[0, 0], [width, height]])
+ .extent([[0, 0], [width, height]])
+ .on("zoom", zoomTransform);
+
+ function zoomTransform() {
+ transform = d3.event.transform;
+
+ // transform the axes
+ axes.xScale.domain(transform.rescaleX(axes.xScaleOrig).domain());
+
+ // transform the data lines
+ for (j=0; j<numCharts; j++) {
+ container.select("#line-"+j).attr("d", lineObjects[j]);
+ }
+
+ // transform the points
+ for (j=0; j<numCharts; j++) {
+ pointSets[j].data(labelData[j])
+ .attr("cx", function(d) { return axes.xScale(d.X); })
+ .attr("cy", function(d) { return axes["yScale" + j](d.Y); })
+ }
+
+ svg.select(".x-axis").call(axes.xAxis);
+
+ // transform the annotation lines (if any)
+ annoLines = container.selectAll(".ann-line")
+ annoLines._groups[0].forEach(function(l) {
+ l.setAttribute("x1", axes.xScale(l.getAttribute("cp_idx")));
+ l.setAttribute("x2", axes.xScale(l.getAttribute("cp_idx")));
+ });
+ }
+
+ var svg = d3.select(selector).append('svg')
+ .attr("width", chartWidth)
+ .attr("height", height);
+
+ svg.append("defs")
+ .append("clipPath")
+ .attr("id", "clip")
+ .append("rect")
+ .attr("width", width)
+ .attr("height", height)
+ .attr("transform", "translate(0, 0)");
+
+ var container = svg.append("g")
+ .attr("class", "container")
+ .attr('transform', 'translate(' + visPadding.left + ',' + visPadding.top + ')');
+
+ var ytrans = numCharts * (lineHeight + chartPadding) - chartPadding / 2;
+
+ // x axis
+ container.append("g")
+ .attr("class", "x-axis")
+ .attr("transform", "translate(0, " + ytrans + ")")
+ .call(axes.xAxis);
+
+ // x axis label
+ container.append("text")
+ .attr("text-anchor", "middle")
+ .attr("class", "axis-label")
+ .attr("transform", "translate(" + (width - 20) + "," +
+ (ytrans + 40) + ")")
+ .text("Time");
+
+ // wrapper for zoom
+ var gZoom = container.append("g").call(zoomObj);
+
+ // rectangle for the graph area
+ gZoom.append("rect")
+ .attr("width", width)
+ .attr("height", height);
+
+ // wrapper for charts
+ var chartWrap = gZoom.append('g')
+ .attr('class', 'chart-wrap');
+
+ for (j=0; j<numCharts; j++) {
+ var lbl = "Y" + j;
+
+ // wrapper for the line, includes translation.
+ var lineWrap = chartWrap.append('g')
+ .attr('class', 'line-wrap')
+ .attr('transform', 'translate(0,' + axes.charts(lbl) + ")");
+
+ // line for the minimum
+ var minLine = lineWrap.append('g')
+ .attr('class', 'z-line');
+ var minVal = d3.min(labelData[j], function(d) { return d.Y; });
+ minLine.append('line')
+ .attr('x1', 0)
+ .attr('x2', lineWidth - visPadding.middle)
+ .attr('y1', axes['yScale' + j](minVal))
+ .attr('y2', axes['yScale' + j](minVal));
+
+ // create the line object
+ var lineobj = d3.line()
+ .x(function(d) { return axes.xScale(d.X); })
+ .y(function(d) { return axes['yScale'+j](d.Y); });
+
+ lineObjects[j] = lineobj;
+
+ var line = lineWrap.append('path')
+ .datum(labelData[j])
+ .attr('class', 'line')
+ .attr('id', 'line-'+j)
+ .attr('d', lineobj);
+
+ // add the points
+ pointSets[j] = lineWrap.selectAll('circle')
+ .data(labelData[j])
+ .enter()
+ .append('circle')
+ .attr('cx', function(d) { return axes.xScale(d.X); })
+ .attr('cy', function(d) { return axes['yScale'+j](d.Y); })
+ .attr('data_X', function(d) { return d.X; })
+ .attr('data_Y', function(d) { return d.Y; })
+ .attr('r', 5)
+ .attr('id', function(d) { return 'circle-x' + d.X + '-y' + j; })
+ .on('click', function(d, i) {
+ d.element = this;
+ return clickFunction(d, i, numCharts);
+ });
+
+ // handle the annotations
+ // annotations is a dict with keys j = 0..numCharts-1.
+ if (annotations === null)
+ continue;
+ annotations.forEach(function(a) {
+ for (i=0; i<pointSets[j]._groups[0].length; i++) {
+ p = pointSets[j]._groups[0][i];
+ if (p.getAttribute("data_X") != a.index)
+ continue;
+ var elem = d3.select(p);
+ annotationFunction(a, elem, lineWrap, axes, j);
+ }
+ });
+ }
+}
+
+function annotateChart(selector, data) {
+ handleClick = function(d, i, numCharts) {
+ if (d3.event.defaultPrevented) return;
+
+ var X = d.element.getAttribute('data_X');
+ for (j=0; j<numCharts; j++) {
+ var id = '#circle-x' + X + '-y' + j;
+ var elem = d3.select(id);
+
+ if (elem.classed("changepoint")) {
+ elem.style("fill", null);
+ elem.classed("changepoint", false);
+ } else {
+ elem.style("fill", "red");
+ elem.classed("changepoint", true);
+ }
+ }
+
+ updateTableMulti(numCharts);
+ }
+ baseChart(selector, data, handleClick, null, null);
+}
+
+function viewAnnotations(selector, data, annotations) {
+ function handleAnnotation(ann, elem, view, axes, j) {
+ elem.classed("marked", true);
+ ymin = axes['yScale' + j].domain()[0];
+ ymax = axes['yScale' + j].domain()[1];
+ view.append("line")
+ .attr("cp_idx", ann.index)
+ .attr("y1", axes['yScale' + j](ymax))
+ .attr("y2", axes['yScale' + j](ymin))
+ .attr("x1", axes["xScale"](ann.index))
+ .attr("x2", axes["xScale"](ann.index))
+ .attr("class", "ann-line");
+ }
+
+ baseChart(selector, data, function() {}, annotations, handleAnnotation);
+}
+
+function adminViewAnnotations(selector, data, annotations) {
+ function handleAnnotation(ann, elem, view, axes, j) {
+ elem.classed("marked", true);
+ ymin = axes['yScale' + j].domain()[0];
+ ymax = axes['yScale' + j].domain()[1];
+ view.append("line")
+ .attr("cp_idx", ann.index)
+ .attr("y1", axes['yScale' + j](ymax))
+ .attr("y2", axes['yScale' + j](ymin))
+ .attr("x1", axes["xScale"](ann.index))
+ .attr("x2", axes["xScale"](ann.index))
+ .attr("class", "ann-line" + " " + ann.user);
+ }
+ baseChart(selector, data, function() {}, annotations, handleAnnotation);
+}
diff --git a/app/static/js/updateTable.js b/app/static/js/updateTable.js
index 7d68aad..321114f 100644
--- a/app/static/js/updateTable.js
+++ b/app/static/js/updateTable.js
@@ -60,3 +60,77 @@ function updateTable() {
table.appendChild(body);
myTableDiv.appendChild(table);
}
+
+function updateTableMulti(numCharts) {
+ var changepoints = document.getElementsByClassName('changepoint');
+ var myTableDiv = document.getElementById('changepoint-table');
+ var oldTable = document.getElementById('cp-table');
+ oldTable.remove();
+
+ var table = document.createElement('TABLE');
+ table.id = 'cp-table';
+ table.className = 'table table-striped';
+ if (changepoints.length == 0) {
+ myTableDiv.appendChild(table);
+ return;
+ }
+
+ var heading = new Array();
+ heading[0] = "#";
+ heading[1] = "X";
+ for (j=0; j<numCharts; j++)
+ heading[2+j] = "Y" + (j + 1);
+
+ // Table Columns
+ var thead = document.createElement('THEAD');
+ thead.className = 'thead-dark';
+ table.appendChild(thead);
+ for (i=0; i<heading.length; i++) {
+ var th = document.createElement('TH');
+ th.appendChild(document.createTextNode(heading[i]));
+ th.setAttribute("scope", "col");
+ thead.appendChild(th);
+ }
+
+ var consolidated = {};
+ var keys = [];
+ for (i=0; i<changepoints.length; i++) {
+ cp = changepoints[i];
+ data = d3.select(cp).data()[0];
+ if (!(data.X in consolidated)) {
+ consolidated[data.X] = {}
+ keys.push(data.X);
+ }
+ id_parts = cp.id.split('-')
+ yindex = id_parts[id_parts.length - 1];
+ consolidated[data.X][yindex] = data.Y;
+ }
+ keys.sort(function(a, b) { return parseInt(a) - parseInt(b); });
+
+ var body = document.createElement("TBODY");
+ for (i=0; i<keys.length; i++) {
+ X = keys[i];
+ cp = consolidated[keys[i]];
+
+ var tr = document.createElement('TR');
+
+ var th = document.createElement('TH');
+ th.setAttribute("scope", "row");
+ th.appendChild(document.createTextNode(i+1));
+ tr.appendChild(th);
+
+ var td = document.createElement('TD');
+ td.appendChild(document.createTextNode(X));
+ tr.appendChild(td);
+
+ for (j=0; j<numCharts; j++) {
+ var td = document.createElement('TD');
+ td.appendChild(document.createTextNode(cp['y' + j]));
+ tr.appendChild(td);
+ }
+
+ body.appendChild(tr);
+ }
+ table.appendChild(body);
+ myTableDiv.appendChild(table);
+}
diff --git a/app/templates/admin/annotations_by_dataset.html b/app/templates/admin/annotations_by_dataset.html
index 3885d7f..fd64673 100644
--- a/app/templates/admin/annotations_by_dataset.html
+++ b/app/templates/admin/annotations_by_dataset.html
@@ -13,7 +13,11 @@
{% block scripts %}
{{ super() }}
<script src="//d3js.org/d3.v5.min.js"></script>
+ {% if is_multi %}
+ <script src="{{ url_for('static', filename='js/makeChartMulti.js') }}"></script>
+ {% else %}
<script src="{{ url_for('static', filename='js/makeChart.js') }}"></script>
+ {% endif %}
<script>adminViewAnnotations(
'#graph',
{{ data.chart_data | tojson }},
diff --git a/app/templates/annotate/index.html b/app/templates/annotate/index.html
index 61233a8..5360bf8 100644
--- a/app/templates/annotate/index.html
+++ b/app/templates/annotate/index.html
@@ -52,9 +52,13 @@
{% block scripts %}
{{ super() }}
<script src="//d3js.org/d3.v5.min.js"></script>
- <script src="{{ url_for('static', filename='js/makeChart.js') }}"></script>
<script src="{{ url_for('static', filename='js/updateTable.js') }}"></script>
<script src="{{ url_for('static', filename='js/buttons.js') }}"></script>
+ {% if is_multi %}
+ <script src="{{ url_for('static', filename='js/makeChartMulti.js') }}"></script>
+ {% else %}
+ <script src="{{ url_for('static', filename='js/makeChart.js') }}"></script>
+ {% endif %}
<script>annotateChart("#graph", {{ data.chart_data | tojson }});</script>
<script>
// reset button
diff --git a/app/templates/demo/evaluate.html b/app/templates/demo/evaluate.html
index 225dade..3f20c94 100644
--- a/app/templates/demo/evaluate.html
+++ b/app/templates/demo/evaluate.html
@@ -31,20 +31,27 @@
{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
</div>
</div>
+{% endblock %}
-<script src="//d3js.org/d3.v5.min.js"></script>
-<script src="{{ url_for('static', filename='js/makeChart.js') }}"></script>
-<script>viewAnnotations(
- "#graph_user",
- {{ data.chart_data | tojson }},
- {{ annotations_user | tojson }}
-);
-</script>
-<script>
- viewAnnotations(
- "#graph_true",
- {{ data.chart_data | tojson }},
- {{ annotations_true | tojson }}
-);
-</script>
+{% block scripts %}
+ {{ super() }}
+ <script src="//d3js.org/d3.v5.min.js"></script>
+ {% if is_multi %}
+ <script src="{{ url_for('static', filename='js/makeChartMulti.js') }}"></script>
+ {% else %}
+ <script src="{{ url_for('static', filename='js/makeChart.js') }}"></script>
+ {% endif %}
+ <script>viewAnnotations(
+ "#graph_user",
+ {{ data.chart_data | tojson }},
+ {{ annotations_user | tojson }}
+ );
+ </script>
+ <script>
+ viewAnnotations(
+ "#graph_true",
+ {{ data.chart_data | tojson }},
+ {{ annotations_true | tojson }}
+ );
+ </script>
{% endblock %}
diff --git a/demo_data/demo_800.json b/demo_data/demo_800.json
new file mode 100644
index 0000000..4151e74
--- /dev/null
+++ b/demo_data/demo_800.json
@@ -0,0 +1,224 @@
+{
+ "name": "demo_800",
+ "n_obs": 100,
+ "n_dim": 2,
+ "demo": {
+ "true_CPs": [
+ 65
+ ]
+ },
+ "series": [
+ {
+ "label": "V1",
+ "type": "float",
+ "raw": [
+ 2.4048337302746465,
+ 2.7168349605639315,
+ 0.9041074861194818,
+ 0.14437038954839643,
+ -0.5221323890548066,
+ -1.7599042604063269,
+ -2.548293050233459,
+ -2.208426298362067,
+ -1.256954097725121,
+ 1.2673541962415547,
+ 1.1733075975968035,
+ 2.572511100788756,
+ 4.835403025842459,
+ 4.5522544564588365,
+ 3.1314009643946106,
+ 2.9117530434659322,
+ 1.989596205887107,
+ 0.12295916283122152,
+ -1.572080765613325,
+ -1.4020154454558442,
+ -0.5797659111967578,
+ 1.3052476134525937,
+ 2.463483078997914,
+ 2.6208640977905917,
+ 5.276333136526055,
+ 6.189478034635046,
+ 4.749709400197241,
+ 4.332421204937167,
+ 3.5869975973229327,
+ 2.418661492344107,
+ -0.6660467926540834,
+ 0.558797974643422,
+ 0.42616473119351966,
+ 0.7669826506319701,
+ 1.9841902051028015,
+ 4.037621600143654,
+ 4.716794663084997,
+ 6.351483458807052,
+ 8.15722901100052,
+ 6.799628004587378,
+ 4.679336321650161,
+ 4.620601716252461,
+ 2.035199758371592,
+ 1.436517310229426,
+ 1.1046729586309985,
+ 1.7317671802316381,
+ 3.7804685922880745,
+ 4.904957692680897,
+ 6.577527584844899,
+ 7.772812515037654,
+ 8.7669639673688,
+ 7.7275133582092925,
+ 8.081106500414604,
+ 5.633331811986405,
+ 4.320656385064652,
+ 3.675975491302624,
+ 2.239294039901483,
+ 3.752799894422126,
+ 3.578790640480911,
+ 4.956034869436094,
+ 6.672164066165842,
+ 7.360081623700845,
+ 9.562433533804628,
+ 10.265674738007569,
+ 9.101981636121108,
+ -2.194041154997131,
+ -3.7012586157863887,
+ -7.653582763774726,
+ -9.292337495769667,
+ -8.902372732926226,
+ -9.767801399199508,
+ -8.340357720658075,
+ -5.0078952785839865,
+ -4.048857544065237,
+ -1.3177865542217697,
+ -0.9020124604731076,
+ -2.243740257406788,
+ -2.917482586327878,
+ -4.543662767507734,
+ -7.306029963030574,
+ -10.384296518475317,
+ -12.165420998763718,
+ -12.178529260880655,
+ -10.812463114033422,
+ -10.252447277392156,
+ -7.776749620293404,
+ -4.635861860775694,
+ -3.767672729617822,
+ -3.487478378396219,
+ -4.924371469527412,
+ -5.449262162042549,
+ -8.8927776405326,
+ -11.5780076846028,
+ -13.993805845864184,
+ -14.50818663568779,
+ -15.426098632186385,
+ -13.788291197384886,
+ -11.466791970497958,
+ -8.09824714195754,
+ -6.507185697165474
+ ]
+ },
+ {
+ "label": "V2",
+ "type": "float",
+ "raw": [
+ -0.512144907328231,
+ 1.180877094568944,
+ 3.21495688649086,
+ 3.30100039164296,
+ 3.131495877850537,
+ 2.7083848725753303,
+ 0.7046621090030554,
+ -0.3722455317527476,
+ -2.763208097405329,
+ -2.851611407399316,
+ -1.7838053015227253,
+ -1.1835756790551795,
+ -0.05856749542015677,
+ 1.0602337342952541,
+ 2.5560931755237783,
+ 4.177640415008337,
+ 5.523261596773609,
+ 4.800082701701678,
+ 3.4350373458935746,
+ 1.2402178666145889,
+ 0.049773845309929565,
+ -1.1708812671512536,
+ -1.593035979997087,
+ -0.2755433654490299,
+ 0.6089266866075381,
+ 1.0410460807006094,
+ 5.082064906086399,
+ 5.505648973005408,
+ 7.120925673938372,
+ 5.483312226619,
+ 5.950385821097255,
+ 4.2473500772756445,
+ 2.297363185979542,
+ 0.015007388288893253,
+ 0.4463898055824315,
+ 0.38522786716705426,
+ 1.80126348142217,
+ 2.8821847137728254,
+ 4.781507667944923,
+ 5.425458351881453,
+ 6.146347067030369,
+ 7.202635814182365,
+ 6.337887131797799,
+ 5.616181429738069,
+ 4.970747236155149,
+ 2.8316763970841063,
+ 1.039352795004348,
+ 1.1794953229977527,
+ 2.1759611489770676,
+ 3.5479131243271196,
+ 3.8850902219360375,
+ 6.719505790505435,
+ 6.73812227273263,
+ 8.735255788221282,
+ 9.109738825694748,
+ 7.288225019795107,
+ 6.532489310182096,
+ 4.626223961087513,
+ 4.086838254635324,
+ 3.6636721899874596,
+ 3.396811467240758,
+ 3.332395646124288,
+ 6.04695253681218,
+ 7.7372895641498625,
+ 8.78651148176425,
+ 9.823884515175894,
+ 9.491162963215556,
+ 10.60522937026037,
+ 7.6503138685368945,
+ 7.060110792801475,
+ 5.377920287327379,
+ 4.471776085391286,
+ 4.873833216101378,
+ 3.5502975827744505,
+ 6.981666375706383,
+ 7.77963248562725,
+ 8.358288716655693,
+ 10.16088761127809,
+ 10.503949710979114,
+ 9.802832103790408,
+ 10.117476586020533,
+ 9.351456920006646,
+ 7.955556636697319,
+ 5.013496099614551,
+ 5.993411963284857,
+ 5.572772912761436,
+ 6.629075184090689,
+ 6.8058184040419105,
+ 9.35773531204678,
+ 10.718106089686358,
+ 10.635573210445399,
+ 11.818990011454368,
+ 11.103782047235141,
+ 10.911261978781269,
+ 10.683238595620933,
+ 8.45732816202584,
+ 6.884877092827968,
+ 6.826985186303127,
+ 5.915951292057806,
+ 7.975570042788154
+ ]
+ }
+ ]
+} \ No newline at end of file