Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
52bb090
send back mock response from view
Oct 9, 2020
03ddbd0
remove json snippet annotation for github
Oct 9, 2020
b1433ee
add a status endpoint for ping request
Oct 9, 2020
eec8c48
use with statement to open file
Oct 16, 2020
242ada0
Merge branch 'master' of https://github.com/PolyCortex/polydodo into …
Oct 16, 2020
144d8ce
Create specific style.css with BEM class names for css
Oct 16, 2020
2856d40
Customize drag and drop button OpenBCI file
Oct 16, 2020
42a31d5
add type validation to upload file
Oct 17, 2020
1ec6e7b
Add some constants and form validation
Oct 17, 2020
445ef45
Complete date time validation
Oct 17, 2020
8c218f9
Create loading page and parse request results to SleepAnalysisResults
Oct 18, 2020
917c90f
create a global state hook && pass epochs to hypnogram
Oct 18, 2020
f7da5d8
Evolutive chart link to back-end
Oct 18, 2020
2c9fab2
remove annotations field from server response && link spectrogram
Oct 18, 2020
bdcb6e6
Fix mock response sent by backend
Oct 18, 2020
feb9578
uncomment form
Oct 18, 2020
cfdcf8a
Display an error alert when post does not work
Oct 18, 2020
19b15f7
Merge branch 'master' of https://github.com/PolyCortex/polydodo into …
Oct 18, 2020
ab7015c
Downloadable json
Oct 18, 2020
d3aac69
useGlobalState hook instead of local state
Oct 18, 2020
a7ecaa7
replace moment for pur JS Date
Oct 18, 2020
165fcce
Replace moment by Luxon
Oct 18, 2020
f8bbc7b
Hide components in place of conditional rendering in order to preserv…
Oct 18, 2020
c53894c
Deactivates browser native validation
Oct 19, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,001 changes: 1,363 additions & 1,638 deletions backend/assets/mock_response.json

Large diffs are not rendered by default.

10 changes: 0 additions & 10 deletions backend/assets/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@
... // Stage of each timestamp. This array has the same size that timestamps. Possible values are "W", "REM", "N1", "N2", "N3"
]
},
"annotations": [
[1602210380, "W"], // Unix timestamp of the epoch that transitioned to the "W" state
[1602211400, "N1"],
[1602211910, "N2"],
[1602212900, "N3"],
...
[1602239180, "W"],
[1602239210, "N1"],
[1602239240, "W"]
],
"spectrograms": {
"frequencies": [...],
"fpz-cz": [ // Contains an array for each timestamps in epochs.timestamps for the fpz-cz eeg channel
Expand Down
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
"d3": "^5.16.0",
"d3-tip": "^0.9.1",
"headroom.js": "^0.11.0",
"moment": "^2.27.0",
"luxon": "^1.25.0",
"node-sass": "4.14.1",
"prop-types": "^15.7.2",
"react": "16.12.0",
"react-dom": "16.12.0",
"react-hook-form": "^6.8.4",
"react-hooks-global-state": "^1.0.1",
"react-router": "5.1.2",
"react-router-dom": "5.1.2",
"react-scripts": "3.4.0",
Expand Down
74 changes: 0 additions & 74 deletions web/src/assets/css/visualisation.css

This file was deleted.

47 changes: 32 additions & 15 deletions web/src/d3/evolving_chart/chart_states.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import * as d3 from 'd3';
import _ from 'lodash';
import moment from 'moment';
import { DateTime } from 'luxon';

import { BAR_HEIGHT, DIMENSION } from './constants';
import { EPOCH_DURATION_MS, TRANSITION_TIME_MS, STAGES_ORDERED } from '../constants';

import '../style.css';

export const createTimelineChartCallbacks = (g, xTime, xTimeAxis, color, tooltip) =>
Object({
fromInitial: () => {
const annotationRects = g.selectAll('.rect-stacked').interrupt();

setAttrOnAnnotationRects(annotationRects, xTime, 0, color, tooltip);

g.append('g').attr('class', 'x axis').attr('transform', `translate(0, ${BAR_HEIGHT})`).call(xTimeAxis);
g.append('g')
.attr('class', 'x visualization__axis')
.attr('transform', `translate(0, ${BAR_HEIGHT})`)
.call(xTimeAxis);
},
fromInstance: () => {
const annotationRects = g.selectAll('.rect-stacked').interrupt();

g.selectAll('.y.axis').remove();
g.selectAll('.y.visualization__axis').remove();

setAttrOnAnnotationRects(annotationRects, xTime, 0, color, tooltip);

Expand All @@ -41,7 +46,7 @@ export const createInstanceChartCallbacks = (g, data, xTime, xTimeAxis, yAxis, c

g.selectAll('text.proportion').remove();

g.select('.x.axis').interrupt().transition().call(xTimeAxis);
g.select('.x.visualization__axis').interrupt().transition().call(xTimeAxis);
transitionHorizontalAxis(g, STAGES_ORDERED.length * BAR_HEIGHT);

setAttrOnAnnotationRects(annotationRects, xTime, getVerticalPositionCallback, color, tooltip);
Expand All @@ -55,7 +60,7 @@ export const createBarChartCallbacks = (g, data, xAxisLinear, yAxis, color, tip)
const annotationRects = g.selectAll('.rect-stacked').interrupt();
const xProportionCallback = getOffsetSleepStageProportionCallback(data);

g.select('.x.axis').transition().call(xAxisLinear);
g.select('.x.visualization__axis').transition().call(xAxisLinear);
transitionHorizontalAxis(g, STAGES_ORDERED.length * BAR_HEIGHT);

setTooltip(annotationRects, tip)
Expand All @@ -65,14 +70,14 @@ export const createBarChartCallbacks = (g, data, xAxisLinear, yAxis, color, tip)
.attr('x', xProportionCallback)
.on('end', () => {
// Only keep the first rectangle of each stage to be visible
g.selectAll('.rect-stacked').attr('x', 0).attr('width', getFirstRectangleProportionWidthCallback(firstStageIndexes, stageTimeProportions));
g.selectAll('.rect-stacked')
.attr('x', 0)
.attr('width', getFirstRectangleProportionWidthCallback(firstStageIndexes, stageTimeProportions));
createProportionLabels(g, data);
});
},
fromStackedBarChart: () => {
const annotationRects = g.selectAll('.rect-stacked').interrupt();

g.selectAll('text.label-sleepType').remove();
g.selectAll('text.proportion').remove();

createVerticalAxis(g, yAxis, color);
Expand All @@ -95,10 +100,11 @@ export const createStackedBarChartCallbacks = (g, data) =>
const { annotations, firstStageIndexes, stageTimeProportions, epochs } = data;
const firstAnnotationsByStage = _.filter(annotations, ({ stage }, index) => firstStageIndexes[stage] === index);
const getHorizontalPositionSleepStage = ({ stage }) =>
(getCumulativeProportionOfNightAtStart(stage, stageTimeProportions) + stageTimeProportions[stage] / 2) * DIMENSION.WIDTH;
(getCumulativeProportionOfNightAtStart(stage, stageTimeProportions) + stageTimeProportions[stage] / 2) *
DIMENSION.WIDTH;
const annotationRects = g.selectAll('.rect-stacked').interrupt();

g.selectAll('.y.axis').remove();
g.selectAll('.y.visualization__axis').remove();
g.selectAll('text.proportion').remove();

transitionHorizontalAxis(g, BAR_HEIGHT);
Expand All @@ -119,7 +125,11 @@ export const createStackedBarChartCallbacks = (g, data) =>
.attr('class', 'proportion')
.style('text-anchor', 'middle')
.append('tspan')
.text(({ stage }) => moment.utc(stageTimeProportions[stage] * epochs.length * EPOCH_DURATION_MS).format('HH:mm'))
.text(({ stage }) =>
DateTime.fromMillis(stageTimeProportions[stage] * epochs.length * EPOCH_DURATION_MS)
.toUTC()
.toFormat('hh:mm:ss'),
)
.attr('x', getHorizontalPositionSleepStage)
.attr('y', 40)
.append('tspan')
Expand Down Expand Up @@ -158,7 +168,7 @@ const getFirstRectangleProportionWidthCallback = (firstStageIndexes, stageTimePr
const createVerticalAxis = (g, yAxis, color) =>
g
.append('g')
.attr('class', 'y axis')
.attr('class', 'y visualization__axis')
.style('font-size', '1.5rem')
.transition()
.duration(TRANSITION_TIME_MS)
Expand All @@ -172,7 +182,11 @@ const createVerticalAxis = (g, yAxis, color) =>
.style('alignment-baseline', 'middle');

const transitionHorizontalAxis = (g, yPosition) =>
g.select('.x.axis').transition().duration(TRANSITION_TIME_MS).attr('transform', `translate(0, ${yPosition})`);
g
.select('.x.visualization__axis')
.transition()
.duration(TRANSITION_TIME_MS)
.attr('transform', `translate(0, ${yPosition})`);

const createProportionLabels = (g, data) =>
g
Expand All @@ -181,7 +195,9 @@ const createProportionLabels = (g, data) =>
.enter()
.append('text')
.attr('class', 'proportion')
.text(({ stage }, i) => (i === data.firstStageIndexes[stage] ? `${_.round(data.stageTimeProportions[stage] * 100, 2)}%` : ''))
.text(({ stage }, i) =>
i === data.firstStageIndexes[stage] ? `${_.round(data.stageTimeProportions[stage] * 100, 2)}%` : '',
)
.attr('x', DIMENSION.WIDTH / 20)
.attr('y', ({ stage }) => BAR_HEIGHT * STAGES_ORDERED.indexOf(stage) + BAR_HEIGHT / 2)
.style('fill', 'black');
Expand Down Expand Up @@ -209,7 +225,8 @@ const getOffsetSleepStageProportionCallback = (data) => {
);
});

return (d, index) => DIMENSION.WIDTH * stageTimeProportions[d.stage] * cumulSumProportions[d.stage][annotationIndexSleepStage[index]];
return (d, index) =>
DIMENSION.WIDTH * stageTimeProportions[d.stage] * cumulSumProportions[d.stage][annotationIndexSleepStage[index]];
};

const cumulSum = (annotationsProportionByStage) =>
Expand Down
16 changes: 11 additions & 5 deletions web/src/d3/evolving_chart/evolving_chart.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import * as d3 from 'd3';
import _ from 'lodash';
import moment from 'moment';
import { DateTime } from 'luxon';

import { preprocessData } from './preproc';
import { createLegend } from './legend';
import { createTimelineChartCallbacks, createInstanceChartCallbacks, createBarChartCallbacks, createStackedBarChartCallbacks } from './chart_states';
import {
createTimelineChartCallbacks,
createInstanceChartCallbacks,
createBarChartCallbacks,
createStackedBarChartCallbacks,
} from './chart_states';
import { MARGIN, CANVAS_DIMENSION, BAR_HEIGHT, DIMENSION } from './constants';
import { STAGES_ORDERED, STAGE_TO_COLOR } from '../constants';
import { initializeTooltips } from './mouse_over';
Expand All @@ -27,10 +32,10 @@ const initializeScales = () => {
const setDomainOnScales = (xTime, xLinear, y, colors, epochs) => {
const start = _.first(epochs).timestamp;
const end = _.last(epochs).timestamp;
const nightDuration = moment.duration(moment(end).diff(start));
const nightDuration = DateTime.fromJSDate(end).diff(DateTime.fromJSDate(start), ['hours']);

xTime.domain([start, end]);
xLinear.domain([0, nightDuration.asHours()]);
xLinear.domain([0, nightDuration.hours]);
y.domain(STAGES_ORDERED);
colors.domain(STAGES_ORDERED);
};
Expand All @@ -45,7 +50,8 @@ const initializeAxes = (xTime, xLinear, y) => {

const createDrawingGroup = (svg) => svg.append('g').attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`);

const bindAnnotationsToRects = (g, annotations) => g.selectAll('.rect').data(annotations).enter().append('rect').attr('class', 'rect-stacked');
const bindAnnotationsToRects = (g, annotations) =>
g.selectAll('.rect').data(annotations).enter().append('rect').attr('class', 'rect-stacked');

const createEvolvingChart = (containerNode, data) => {
const svg = d3.select(containerNode).attr('viewBox', `0, 0, ${CANVAS_DIMENSION.WIDTH}, ${CANVAS_DIMENSION.HEIGHT}`);
Expand Down
27 changes: 15 additions & 12 deletions web/src/d3/evolving_chart/mouse_over.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import tip from 'd3-tip';
import moment from 'moment';
import './style.css';

import { EPOCH_DURATION_MS } from '../constants';
import { DateTime, Duration } from 'luxon';

export const initializeTooltips = (svg, data) => {
const barToolTip = initializeTooltip(svg, getBarToolTipText);
const stackedToolTip = initializeTooltip(svg, (d) => getStackedToolTipText(d, data.stageTimeProportions, data.epochs.length));
const stackedToolTip = initializeTooltip(svg, (d) =>
getStackedToolTipText(d, data.stageTimeProportions, data.epochs.length),
);

return { barToolTip, stackedToolTip };
};

const initializeTooltip = (svg, getToolTipText) => {
const tooltip = tip().attr('class', 'd3-tip').offset([-10, 0]);
const tooltip = tip().attr('class', 'evolving_chart__tooltip').offset([-10, 0]);
svg.call(tooltip);
tooltip.html(getToolTipText);

return tooltip;
};

const getBarToolTipText = (d) => {
return `Stage : <strong> ${d.stage} </strong> <br>
Range : <strong> ${moment(d.start).format('HH:mm:ss')} </strong>
- <strong> ${moment(d.end).format('HH:mm:ss')} </strong> <br>
Duration: <strong> ${moment.utc(moment(d.end).diff(d.start)).format('HH:mm:ss')} </strong>`;
};
const getBarToolTipText = (d) => `Stage : <strong> ${d.stage} </strong> <br>
Range : <strong> ${DateTime.fromJSDate(d.start).toFormat('hh:mm:ss')} </strong>
- <strong> ${DateTime.fromJSDate(d.end).toFormat('hh:mm:ss')} </strong> <br>
Duration: <strong> ${DateTime.fromJSDate(d.end)
.diff(DateTime.fromJSDate(d.start))
.toFormat('hh:mm:ss')} </strong>`;

const getStackedToolTipText = (d, stageTimeProportions, nbEpochs) =>
`Stage : <strong> ${d.stage} </strong><br> Duration : <strong> ${moment
.utc(stageTimeProportions[d.stage] * nbEpochs * EPOCH_DURATION_MS)
.format('HH:mm:ss')} </strong><br>`;
`Stage : <strong> ${d.stage} </strong><br> Duration : <strong> ${Duration.fromMillis(
stageTimeProportions[d.stage] * nbEpochs * EPOCH_DURATION_MS,
).toFormat('hh:mm:ss')} </strong><br>`;
3 changes: 2 additions & 1 deletion web/src/d3/evolving_chart/preproc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import _ from 'lodash';

import { convertTimestampsToDates, convertEpochsToAnnotations } from '../utils';
import { convertAPIFormatToCSVFormat, convertTimestampsToDates, convertEpochsToAnnotations } from '../utils';
import { STAGES_ORDERED } from '../constants';

export const preprocessData = (data) => {
data = convertAPIFormatToCSVFormat(data);
data = convertTimestampsToDates(data);
const annotations = convertEpochsToAnnotations(data);
const stageTimeProportions = getStageTimeProportions(data);
Expand Down
7 changes: 7 additions & 0 deletions web/src/d3/evolving_chart/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.evolving_chart__tooltip {
line-height: 1;
padding: 12px;
border-radius: 2px;
color: #0d0d0d;
background: rgba(235, 235, 235, 0.9);
}
4 changes: 2 additions & 2 deletions web/src/d3/hypnogram/hypnogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import _ from 'lodash';
import createHypnogramChart from './line_charts';
import { DIMENSION, MARGIN, COMPARATIVE_COLORS, CANVAS_DIMENSION } from './constants';
import { STAGES_ORDERED } from '../constants';
import { convertTimestampsToDates } from '../utils';
import { convertAPIFormatToCSVFormat, convertTimestampsToDates } from '../utils';

const initializeScales = (comparativeColors) =>
Object({
Expand All @@ -22,8 +22,8 @@ const initializeAxes = (x, y) =>
const createDrawingGroup = (svg) => svg.append('g').attr('transform', `translate(${MARGIN.LEFT}, ${MARGIN.TOP})`);

const preprocessData = (data, hypnogramNames) => {
data = data.map(convertAPIFormatToCSVFormat);
data = data.map((hypno) => convertTimestampsToDates(hypno));

return _.zip(data, hypnogramNames).map((x) =>
Object({
name: x[1],
Expand Down
Loading