diff --git a/mapper/package.json b/mapper/package.json
index da41c87..8d0ec7d 100644
--- a/mapper/package.json
+++ b/mapper/package.json
@@ -15,6 +15,7 @@
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-leaflet": "^4.2.1",
+ "react-range-slider-input": "^3.0.7",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
@@ -31,8 +32,8 @@
]
},
"browserslist": [
- ">0.2%",
- "not dead",
- "not op_mini all"
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
]
}
diff --git a/mapper/src/App.css b/mapper/src/App.css
index 65f533d..73aa191 100644
--- a/mapper/src/App.css
+++ b/mapper/src/App.css
@@ -39,6 +39,16 @@ h2 {
z-index: 9999;
}
+.time-slider {
+ padding: 1em 0.5em;
+}
+
+.range {
+ color: white;
+ text-align: center;
+ padding: 0.5rem;
+}
+
.submenu {
background-color: #666;
max-width: 40em;
diff --git a/mapper/src/App.js b/mapper/src/App.js
index 58cc4cf..300b414 100644
--- a/mapper/src/App.js
+++ b/mapper/src/App.js
@@ -5,35 +5,23 @@ import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';
import axios from 'axios';
import moment from 'moment-timezone';
+import RangeSlider from 'react-range-slider-input';
import './App.css';
import 'leaflet/dist/leaflet.css';
+import 'react-range-slider-input/dist/style.css';
+
let tzcache = {};
+// num: number of steps per duration
+// secs: number of seconds per step
const durations = [
- {id: 0, len: 'Day', win: '1m', full: '1 min', delta: [1, 'days'], format: 'HH'},
- {id: 1, len: 'Week', win: '3m', full: '3 min', delta: [7, 'days'], format: 'HH'},
- {id: 2, len: 'Week', win: '10m', full: '10 min', delta: [7, 'days'], format: 'HH'},
- {id: 3, len: 'Month', win: '10m', full: '10 min', delta: [1, 'months'], format: 'D'},
- {id: 4, len: 'Month', win: '1h', full: '1 hour', delta: [1, 'months'], format: 'D'},
- {id: 5, len: 'Year', win: '2h', full: '2 hours', delta: [1, 'years'], format: 'M/D'},
- {id: 6, len: 'Year', win: '1d', full: '1 day', delta: [1, 'years'], format: 'M/D'},
+ {id: 0, len: 'Day', win: '1m', full: '1 min', delta: [1, 'days'], format: 'HH', num: 1440, secs: 60},
+ {id: 1, len: 'Week', win: '3m', full: '3 min', delta: [7, 'days'], format: 'HH', num: 3360, secs: 180},
+ {id: 2, len: 'Month', win: '10m', full: '10 min', delta: [1, 'months'], format: 'D', num: 4380, secs: 600},
+ {id: 3, len: 'Year', win: '2h', full: '2 hour', delta: [1, 'years'], format: 'M/D', num: 4380, secs: 7200},
];
-const units = {
- 'PM10': ' ug/m³',
- 'PM2.5': ' ug/m³',
- 'VOC': ' / 500',
- 'CO2': ' ppm',
- 'Energy': ' kWh',
- 'Power': ' W',
- 'Temperature': ' °C',
- 'Humidity': '%',
- 'Setpoint': ' °C',
- 'State': '',
- 'Lux': ' lx',
-};
-
function useSensor(measurement, name, end, duration) {
const [data, setData] = useState(false);
const [loading, setLoading] = useState(false);
@@ -63,10 +51,10 @@ function useSensor(measurement, name, end, duration) {
-function Owntracks({end, duration}) {
+function Owntracks({end, duration, range}) {
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
- const coords = data.length ? data.map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
+ const coords = data.length ? data.filter(x => !range || (x.time >= range[0] && x.time <= range[1])).map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
const handleSubmit = (e) => {
e.preventDefault();
@@ -102,79 +90,122 @@ function Owntracks({end, duration}) {
}
-function Graphs({end, duration}) {
+function Graphs({end, duration, range}) {
return (
-
+
);
}
-function Menu({duration, setDuration, end, setEnd}) {
+function Menu({duration, setDuration, end, setEnd, range, setRange}) {
const [submenu, setSubmenu] = useState(false);
+ const [showRange, setShowRange] = useState(false);
const chooseDuration = (x) => {
setSubmenu(false);
+ setRange(false);
setDuration(x);
};
const chooseEnd = (x) => {
setSubmenu(false);
const newEnd = x.add(...duration.delta);
+ setRange(false);
setEnd(newEnd);
};
const chooseNow = (x) => {
setSubmenu(false);
+ setRange(false);
setEnd(moment());
};
const next = () => {
setSubmenu(false);
+ setRange(false);
setEnd(prevEnd => moment(prevEnd).add(...duration.delta));
}
const prev = () => {
setSubmenu(false);
+ setRange(false);
setEnd(prevEnd => moment(prevEnd).subtract(...duration.delta));
}
+ const onSlider = (slider) => {
+ console.log(slider);
+ // good luck remembering how this works
+ const lowOffset = slider[0] * duration.secs - duration.num * duration.secs;
+ const highOffset = slider[1] * duration.secs - duration.num * duration.secs;
+
+ const low = moment.unix(end.unix() + lowOffset);
+ const high = moment.unix(end.unix() + highOffset);
+
+ const lowStr = low.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
+ const highStr = high.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
+
+ console.log(lowStr, highStr);
+ setRange([lowStr, highStr]);
+
+ }
+
return (
- {!!submenu &&
- {submenu === 'end' &&
- <>
-
-
Choose start date:
-
-
-
-
- chooseEnd(x)}
- />
-
-
-
- >
- }
-
- {submenu === 'duration' &&
- <>
-
-
Choose duration:
-
-
-
- {durations.map(x =>
-
- )}
- >
- }
+ {showRange &&
+ {moment(range[0]).format('lll')} - {moment(range[1]).format('lll')}
}
+ {submenu ?
+
+ {submenu === 'end' &&
+ <>
+
+
Choose start date:
+
+
+
+
+ chooseEnd(x)}
+ />
+
+
+
+ >
+ }
+
+ {submenu === 'duration' &&
+ <>
+
+
Choose duration:
+
+
+
+ {durations.map(x =>
+
+ )}
+ >
+ }
+
+ :
+
+ setShowRange(true)}
+ onThumbDragEnd={() => setShowRange(false)}
+ onRangeDragStart={() => setShowRange(true)}
+ onRangeDragEnd={() => setShowRange(false)}
+ />
+
+
+ }
+
@@ -201,6 +232,7 @@ function Menu({duration, setDuration, end, setEnd}) {
function App() {
const [duration, setDuration] = useState(durations[0]);
const [end, setEnd] = useState(moment());
+ const [range, setRange] = useState(false);
return (
@@ -209,11 +241,14 @@ function App() {
setDuration={setDuration}
end={end}
setEnd={setEnd}
+ range={range}
+ setRange={setRange}
/>
);
diff --git a/mapper/yarn.lock b/mapper/yarn.lock
index a4f8850..07809cc 100644
--- a/mapper/yarn.lock
+++ b/mapper/yarn.lock
@@ -3471,6 +3471,11 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+clsx@^1.1.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+ integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3710,6 +3715,11 @@ core-js@^2.4.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
+core-js@^3.22.4:
+ version "3.37.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9"
+ integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==
+
core-js@^3.6.5:
version "3.33.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40"
@@ -9512,6 +9522,14 @@ react-leaflet@^4.2.1:
dependencies:
"@react-leaflet/core" "^2.1.0"
+react-range-slider-input@^3.0.7:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/react-range-slider-input/-/react-range-slider-input-3.0.7.tgz#88ceb118b33d7eb0550cec1f77fc3e60e0f880f9"
+ integrity sha512-yAJDDMUNkILOcZSCLCVbwgnoAM3v0AfdDysTCMXDwY/+ZRNRlv98TyHbVCwPFEd7qiI8Ca/stKb0GAy//NybYw==
+ dependencies:
+ clsx "^1.1.1"
+ core-js "^3.22.4"
+
react-refresh@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"