Comparing version
@@ -0,4 +1,185 @@ | ||
const matrix2d = require('./matrix2d'); | ||
module.exports = altpro; | ||
function altpro() { | ||
function prepare(data) { | ||
return data.reduce((r, { elevation, distance }) => { | ||
if (elevation < r.minElevation) { | ||
r.minElevation = elevation; | ||
} | ||
if (elevation > r.maxElevation) { | ||
r.maxElevation = elevation; | ||
} | ||
r.totalDistance += distance; | ||
r.items.push({ elevation, distance: r.totalDistance }); | ||
return r; | ||
}, { | ||
items: [], | ||
totalDistance: 0, | ||
minElevation: 0, | ||
maxElevation: 0 | ||
}); | ||
} | ||
function initMatrix({ w, h }, { x, y, min }) { | ||
const horizontalPadding = 0; | ||
const verticalPadding = 15; | ||
w -= 2 * horizontalPadding; | ||
h -= 2 * verticalPadding; | ||
const horizontalScaling = w / x; | ||
const verticalScaling = h / y; | ||
return matrix2d() | ||
.translate(horizontalPadding, verticalPadding) | ||
.scale(horizontalScaling, -verticalScaling) | ||
.translate(0, -(y + min)); | ||
} | ||
function drawPath(ctx, items) { | ||
ctx.beginPath(); | ||
const first = items[0]; | ||
const last = items[items.length - 1]; | ||
ctx.moveTo(0, first.elevation); | ||
for(let i = 1; i < items.length; i++) { | ||
const { elevation, distance } = items[i]; | ||
ctx.lineTo(distance, elevation); | ||
} | ||
ctx.stroke(); | ||
ctx.lineTo(last.distance, 0); | ||
ctx.lineTo(0, 0); | ||
ctx.closePath(); | ||
ctx.fill(); | ||
} | ||
function drawSelected(ctx, { distance: d1, elevation: e1 }, { distance: d2, elevation: e2 }) { | ||
ctx.beginPath(); | ||
ctx.moveTo(d1, 0); | ||
ctx.lineTo(d1, e1); | ||
ctx.lineTo(d2, e2); | ||
ctx.lineTo(d2, 0); | ||
ctx.closePath(); | ||
ctx.fill(); | ||
} | ||
function clear(ctx, { w, h }) { | ||
ctx.save(); | ||
ctx.setTransform(1, 0, 0, 1, 0, 0); | ||
ctx.clearRect(0, 0, w, h); | ||
ctx.restore(); | ||
} | ||
function createCanvas(parent) { | ||
function canvas(wrapper, w, h) { | ||
const c = document.createElement('canvas'); | ||
c.style.position = 'absolute'; | ||
c.style.left = 0; | ||
c.style.height = 0; | ||
c.style.width = '100%'; | ||
c.style.height = '100%'; | ||
c.width = w; | ||
c.height = h; | ||
wrapper.appendChild(c); | ||
return c; | ||
} | ||
const wrapper = document.createElement('div'); | ||
wrapper.style.position = 'relative'; | ||
wrapper.style.width = '100%'; | ||
wrapper.style.height = '100%'; | ||
parent.appendChild(wrapper); | ||
const { clientWidth: w, clientHeight: h } = wrapper; | ||
const bg = canvas(wrapper, w, h); | ||
const fg = canvas(wrapper, w, h); | ||
return { bg, fg, w, h }; | ||
} | ||
function altpro(parent, data, opts = {}) { | ||
const { | ||
fill = 'chartreuse', | ||
stroke = 'black', | ||
selectedFill = 'orange' | ||
} = opts; | ||
const { | ||
minElevation, | ||
maxElevation, | ||
totalDistance, | ||
items | ||
} = prepare(data); | ||
const extent = { | ||
x: totalDistance, | ||
y: maxElevation - minElevation, | ||
min: minElevation | ||
}; | ||
const { bg, fg, w, h } = createCanvas(parent); | ||
const ctx = bg.getContext('2d'); | ||
ctx.strokeStyle = stroke; | ||
ctx.fillStyle = fill; | ||
const transformMatric = initMatrix({ w, h }, extent); | ||
const invertedMatrix = transformMatric.clone().invert(); | ||
transformMatric.apply(ctx); | ||
drawPath(ctx, items); | ||
const fgCtx = fg.getContext('2d'); | ||
fgCtx.fillStyle = selectedFill; | ||
fgCtx.lineWidth = 3; | ||
transformMatric.apply(fgCtx); | ||
fg.addEventListener('mousemove', onmousemove); | ||
fg.removeEventListener('mouseleve', onmouseleave); | ||
return { | ||
select, | ||
destroy | ||
}; | ||
function destroy() { | ||
fg.removeEventListener('mousemove', onmousemove); | ||
fg.removeEventListener('mouseleve', onmouseleave); | ||
parent.innerHTML = ''; | ||
} | ||
function onmousemove({ clientX, clientY, target }) { | ||
const rect = target.getBoundingClientRect(); | ||
let index = itemIndexFromPoint([ | ||
clientX - rect.left, | ||
clientY - rect.top | ||
]); | ||
select(index); | ||
} | ||
function onmouseleave() { | ||
clear(fgCtx, { w, h }); | ||
} | ||
function itemIndexFromPoint(point) { | ||
const [ distance ] = unproject(point); | ||
return items.findIndex(item => distance < item.distance); | ||
} | ||
function unproject(point) { | ||
return invertedMatrix.project(point); | ||
} | ||
function select(index) { | ||
if (index < 1 || index >= items.length) { | ||
return; | ||
} | ||
clear(fgCtx, { w, h }); | ||
drawSelected(fgCtx, items[index - 1], items[index]); | ||
} | ||
} |
{ | ||
"name": "altpro", | ||
"version": "0.0.0", | ||
"version": "0.0.1", | ||
"description": "Elevation profile widget.", | ||
@@ -19,5 +19,6 @@ "author": { | ||
"devDependencies": { | ||
"jshint": "^2.9.7", | ||
"mocha": "^5.2.0", | ||
"should": "^13.2.3" | ||
"browserify": "~16", | ||
"jshint": "~2", | ||
"mocha": "~5", | ||
"should": "~13" | ||
}, | ||
@@ -31,2 +32,2 @@ "scripts": { | ||
] | ||
} | ||
} |
@@ -8,3 +8,3 @@ [![NPM version][npm-image]][npm-url] | ||
Elevation profile widget. | ||
Elevation profile widget. See demo [here][demo]. | ||
@@ -20,7 +20,40 @@ ## Install | ||
```js | ||
var altpro = require('altpro'); | ||
altpro('Rainbow'); | ||
const data = [ | ||
{ elevation: 10, distance: 0 }, | ||
{ elevation: 15, distance: 10 }, | ||
{ elevation: 25, distance: 10 }, | ||
// etc. | ||
]; | ||
const altpro = require('altpro'); | ||
const container = document.querySelector('.altitude-profile-container'); | ||
altpro(container, data); | ||
``` | ||
## API | ||
### `altpro(parent, data, options)` | ||
Creates new widget inside of `parent`. `parent` element has to exist, be visible and have desired size. | ||
- `data` is an `Array` of items with `elevation` and `distance` properties. All other properties are | ||
ignored, and `data` is not changed by `altpro`. `distance` means - distance from previous items. | ||
The following `options` can be passed: | ||
- `fill` - [fillStyle] for main graph background | ||
- `stroke` - [strokeStyle] for line at the top of the graph | ||
- `selectedFill` - [fillStyle] for the selected item | ||
### `altpro.select(index)` | ||
Selects `index` element of data. | ||
### `altpro.destroy()` | ||
Removes altpro widget from DOM, unbinds all listeners. | ||
## License | ||
@@ -30,2 +63,7 @@ | ||
[fillStyle]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle | ||
[strokeStyle]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle | ||
[demo]: https://pirxpilot.github.io/altpro | ||
[npm-image]: https://img.shields.io/npm/v/altpro.svg | ||
@@ -32,0 +70,0 @@ [npm-url]: https://npmjs.org/package/altpro |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Trivial Package
Supply chain riskPackages less than 10 lines of code are easily copied into your own project and may not warrant the additional supply chain risk of an external dependency.
Found 1 instance in 1 package
8886
487.7%6
20%244
6000%77
97.44%4
33.33%