Skip to content

Commit 1756c41

Browse files
authored
Merge pull request #28 from brenzy/feature/undo-redo-stack
Add an undo/redo stack
2 parents 2b7baa2 + 90fe6ca commit 1756c41

File tree

16 files changed

+354
-105
lines changed

16 files changed

+354
-105
lines changed

CHANGELOG.md

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7-
## [Unreleased]
8-
- input validation
9-
- browser compatibility check
10-
- accept more than one file type (fit, tcx, kml)
11-
- try some different UI alternatives for less scrolling
12-
- editing the route and elevation from the route map
13-
- mini elevation chart on the route map
14-
- draggable points on the elevation charts
15-
- undo/redo stack
16-
- charts code clean-up to get rid of repeated code
17-
- Vue 3 migration
18-
- typescript
19-
- snip the route
20-
- reverse the route
21-
- zoom and pan on the graphs
22-
- zoom to route on the map
7+
## [1.8.0] - 2021-05-04
8+
### Added
9+
- added an undo/redo stack
10+
- added an overlay while loading or processing
2311

2412
## [1.7.1] - 2021-01-17
2513
### Changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ This is a work in progress...
2323
- [ ] editing the route and elevation from the route map
2424
- [ ] mini elevation chart on the route map
2525
- [ ] draggable points on the elevation charts
26-
- [ ] undo/redo stack
26+
- [ ] edit and delete on the undo/redo stack
2727
- [ ] charts code clean-up to get rid of repeated code
2828
- [ ] Vue 3 migration
2929
- [ ] typescript
3030
- [ ] snip the route
3131
- [ ] reverse the route
3232
- [ ] zoom and pan on the graphs
3333
- [ ] zoom to route on the map
34+
- [ ] web worker for long-running processes
3435

3536
## Project setup
3637
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"kalmanjs": "^1.1.0",
1515
"leaflet": "^1.6.0",
1616
"ml-savitzky-golay-generalized": "^1.1.1",
17+
"ramda": "^0.27.1",
1718
"vue": "^2.6.10",
1819
"vue-router": "^3.0.3",
1920
"vuetify": "^2.3.8",

src/App.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@
66
<router-view/>
77
</keep-alive>
88
</v-main>
9+
<v-overlay :value="isProcessing" opacity=".35">
10+
<div class="wait-text">PROCESSING...</div>
11+
</v-overlay>
912
</v-app>
1013
</template>
1114

1215
<script>
1316
import Toolbar from './components/Toolbar';
17+
import {mapState} from 'vuex';
1418
export default {
1519
name: 'App',
1620
components: {Toolbar},
1721
data: () => ({
1822
//
1923
}),
24+
computed: {
25+
...mapState(['isSmoothingInProgress']),
26+
...mapState({
27+
isProcessing: state => state.isLoading || state.isSmoothingInProgress,
28+
}),
29+
}
2030
};
2131
</script>
2232

@@ -31,4 +41,8 @@ export default {
3141
html {
3242
overflow-y: auto;
3343
}
44+
.wait-text {
45+
font-size: 60px;
46+
color: white;
47+
}
3448
</style>

src/components/Charts.vue

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
<v-btn :value="graphTypes.SLOPE_DISTANCE">Slope Chart</v-btn>
1414
<v-btn :value="graphTypes.ELEVATION_PROFILE">Elevation Profile</v-btn>
1515
</v-btn-toggle>
16-
<DistanceChart
17-
v-if="isActive"
18-
:graph-type="graphType"
19-
:color-scale="colorScale"
20-
:graph-units="graphUnits"
21-
></DistanceChart>
16+
<div class="distance-chart">
17+
<DistanceChart
18+
v-if="isActive"
19+
:graph-type="graphType"
20+
:color-scale="colorScale"
21+
:graph-units="graphUnits"
22+
:selection="selection"
23+
></DistanceChart>
24+
</div>
2225
<Legend
2326
:graph-type="graphType"
2427
:color-scale="colorScale"
@@ -38,6 +41,7 @@
3841
import * as d3 from 'd3';
3942
import Legend from './Legend';
4043
import {UnitType} from '@/components/chartModel';
44+
import {mapState} from 'vuex';
4145
4246
export default {
4347
name: 'Charts',
@@ -49,6 +53,9 @@
4953
colorScale: null,
5054
graphUnits: UnitType.METRIC
5155
}),
56+
computed: {
57+
...mapState(['selection']),
58+
},
5259
mounted() {
5360
if (localStorage.graphUnits) {
5461
this.graphUnits = localStorage.graphUnits === UnitType.IMPERIAL ? UnitType.IMPERIAL : UnitType.METRIC ;
@@ -77,4 +84,6 @@
7784
margin: 20px 20px 0 20px
7885
.chart-type-group
7986
margin: 0 0 20px 20px
87+
.distance-chart
88+
height: 500px
8089
</style>

src/components/DistanceChart.vue

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<template>
2-
<div>
3-
<div id="chart">
4-
</div>
2+
<div id="chart">
53
</div>
64
</template>
75

@@ -11,13 +9,13 @@
119
import {GraphType, LineTypes, UnitType} from './chartModel';
1210
import {convertDistance, convertElevation, kmToMiles, metresToFeet} from '@/utilities/unitConversion';
1311
14-
// noinspection JSUnusedGlobalSymbols
1512
export default {
1613
name: 'DistanceChart',
1714
props: {
1815
graphType: String,
1916
colorScale: Function,
20-
graphUnits: UnitType
17+
graphUnits: UnitType,
18+
selection: Array
2119
},
2220
data: () => ({
2321
defaultDistance: 100000,
@@ -49,8 +47,11 @@
4947
},
5048
beforeDestroy() {
5149
window.removeEventListener('resize', this.resize);
50+
if (this.tooltip) {
51+
this.tooltip.remove();
52+
}
5253
},
53-
computed: mapState(['rawValues', 'selection', 'totalDistance', 'smoothedValues']),
54+
computed: mapState(['rawValues', 'totalDistance', 'smoothedValues']),
5455
watch: {
5556
rawValues(newValue) {
5657
if (!this.svg) {
@@ -99,7 +100,7 @@
99100
if (!this.svg) {
100101
return;
101102
}
102-
this.onSelectionUpdate(newValue);
103+
this.redrawSelection(newValue);
103104
}
104105
},
105106
methods: {
@@ -163,6 +164,8 @@
163164
.attr('width', this.width)
164165
.attr('y', -this.margin.top)
165166
.attr('height', this.height + this.margin.top + this.margin.bottom); // Leave some room at the top and bottom
167+
168+
this.redrawSelection(this.selection);
166169
},
167170
draw() {
168171
this.focus.select('.x.axis').call(this.xAxis);
@@ -191,12 +194,11 @@
191194
+ ' ' + this.xScale(datum.previous.totalDistance) + ',' + this.yScale(this.yScale.domain()[0]);
192195
});
193196
},
194-
onSelectionUpdate(selection) {
195-
if (!selection) {
196-
return;
197+
redrawSelection(selection) {
198+
if (selection) {
199+
this.xScale.domain(selection);
200+
this.xScaleImperial.domain(selection.map(distance => kmToMiles(distance)));
197201
}
198-
this.xScale.domain(selection);
199-
this.xScaleImperial.domain(selection.map(distance => kmToMiles(distance)));
200202
this.focus.select('.x.axis').call(this.xAxis);
201203
this.focus.selectAll('path.elevation').attr('d', this.elevationLine);
202204
this.focus.selectAll('path.slope').attr('d', this.slopeLine);
@@ -487,11 +489,17 @@
487489
const extents = this.getExtents();
488490
489491
this.xScale = d3.scaleLinear()
490-
.range([0, this.width])
491-
.domain(extents.xExtent);
492+
.range([0, this.width]);
492493
this.xScaleImperial = d3.scaleLinear()
493-
.range([0, this.width])
494-
.domain(extents.xExtent.map(distance => kmToMiles(distance)));
494+
.range([0, this.width]);
495+
496+
if (this.selection) {
497+
this.xScale.domain(this.selection);
498+
this.xScaleImperial.domain(this.selection.map(distance => kmToMiles(distance)));
499+
} else {
500+
this.xScale.domain(extents.xExtent);
501+
this.xScaleImperial.domain(extents.xExtent.map(distance => kmToMiles(distance)));
502+
}
495503
496504
this.yScale = d3.scaleLinear()
497505
.range([this.height, 0])
@@ -543,8 +551,7 @@
543551
this.focus.append('g')
544552
.attr('clip-path', 'url(#clip)');
545553
546-
// Get the position of the graph so we can set the
547-
// the offset of the tooltip
554+
// Get the position of the graph element so we can set the offset of the tooltip
548555
this.graphElement = this.svg.node();
549556
550557
this.tooltip = d3.select('body').append('div')
@@ -586,7 +593,7 @@
586593
#chart
587594
font: 10px sans-serif
588595
width: 100%
589-
height: 500px
596+
height: 100%
590597
591598
.axis path,
592599
.axis line

src/components/Legend.vue

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,13 @@
4040
colorScale: Function
4141
},
4242
data: () => ({
43-
graphTypes: GraphType,
44-
formattedRawAverage: '',
45-
formattedSmoothedAverage: ''
43+
graphTypes: GraphType
4644
}),
4745
computed: {
48-
...mapState(['rawAverageSlope', 'smoothedAverageSlope']),
49-
},
50-
watch: {
51-
rawAverageSlope() {
52-
this.formattedRawAverage = this.rawAverageSlope ? `${this.rawAverageSlope}%` : '';
53-
},
54-
smoothedAverageSlope() {
55-
this.formattedSmoothedAverage = this.smoothedAverageSlope ? `${this.smoothedAverageSlope}%` : '';
56-
}
46+
...mapState({
47+
formattedRawAverage: state => state.rawAverageSlope ? `${state.rawAverageSlope}%` : '',
48+
formattedSmoothedAverage: state => state.smoothedAverageSlope ? `${state.smoothedAverageSlope}%` : '',
49+
}),
5750
},
5851
mounted() {
5952
this.$nextTick(() => {

src/components/SelectionChart.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {mapState} from 'vuex';
1010
import {GraphType, LineTypes, UnitType} from './chartModel';
1111
import store from '../store/store';
1212
import {convertElevation, kmToMiles, metresToFeet} from '@/utilities/unitConversion';
13+
import {equals} from 'ramda';
1314
1415
export default {
1516
name: 'SelectionChart',
@@ -220,7 +221,11 @@ export default {
220221
brushed() {
221222
if (d3.event && d3.event.selection) {
222223
const newSelection = d3.event.selection.map(this.miniXScale.invert);
223-
store.dispatch('select', newSelection);
224+
// This gets called on a resize to redraw the brush, so we need to check
225+
// if the selection has actually changed before updating the selection
226+
if (!equals(this.selection, newSelection)) {
227+
store.dispatch('select', newSelection);
228+
}
224229
this.drawSelectionHandles(newSelection);
225230
}
226231
},
@@ -429,7 +434,6 @@ export default {
429434
})
430435
.curve(d3.curveStepBefore);
431436
432-
store.dispatch('select', this.miniXScale.domain());
433437
this.brush = d3
434438
.brushX()
435439
.extent([
@@ -465,7 +469,8 @@ export default {
465469
.attr('cursor', 'ew-resize')
466470
.attr('d', triangleShape);
467471
468-
this.gBrush.call(this.brush.move, this.xScale.range());
472+
const selection = (this.selection === null) ? this.xScale.range() : this.selection.map(this.miniXScale);
473+
this.gBrush.call(this.brush.move, selection);
469474
}
470475
}
471476
};

src/components/Toolbar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<v-app-bar app dark color="primary">
33
<v-toolbar-items>
44
<v-btn text to="/">GPX Smoother Version {{ appVersion }}</v-btn>
5+
<v-btn text to="/history">Smoothing History</v-btn>
56
<v-btn text to="/route-map">Route Map</v-btn>
67
</v-toolbar-items>
78
<v-spacer/>

src/router.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Vue from 'vue';
22
import Router from 'vue-router';
33
import GpxSmoother from '@/views/GpxSmoother';
44
import RouteMap from '@/views/RouteMap';
5+
import History from '@/views/History';
56

67
Vue.use(Router);
78

@@ -17,6 +18,11 @@ export default new Router({
1718
name: 'route-map',
1819
component: RouteMap
1920
},
21+
{
22+
path: '/history',
23+
name: 'history',
24+
component: History
25+
},
2026
{
2127
path: '/about',
2228
name: 'about',

0 commit comments

Comments
 (0)