1
0
Fork 0

Add transmitters chart

Signed-off-by: Pierros Papadeas <pierros@papadeas.gr>
spacecruft
Pierros Papadeas 2020-10-03 15:07:27 +03:00
parent 8e52ab8e8d
commit 2835df9553
6 changed files with 841 additions and 54 deletions

View File

@ -572,3 +572,111 @@ a.skip-main:focus, a.skip-main:active {
font-size: .7rem;
margin-bottom: .2rem;
}
.transmitters-chart {
position: relative;
font-size: 14px;
--color-text: #4d4e59;
--color-axis: #dee2e6;
--color-transmitter: #7c82b9;
--color-transceiver: #5cb85c;
--color-transponder: #fff3cd;
}
.transmitters-chart .color-transmitter {
color: var(--color-transmitter);
}
.transmitters-chart .color-transceiver {
color: var(--color-transceiver);
}
.transmitters-chart .color-transponder {
color: var(--color-transponder);
}
.transmitters-chart .chart-tooltip {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
background-color: rgba(255, 255, 255, 0.9);
padding: 0.5rem 0.75rem;
border-radius: 0.25rem;
border: 1px solid var(--color-text);
color: var(--color-text);
opacity: 0;
transition: opacity 0.2s ease;
}
.transmitters-chart .chart-tooltip.color-transmitter {
border-color: var(--color-transmitter);
}
.transmitters-chart .chart-tooltip.color-transceiver {
border-color: var(--color-transceiver);
}
.transmitters-chart .chart-tooltip.color-transponder {
border-color: var(--color-transponder);
}
.transmitters-chart .chart-tooltip.show {
opacity: 1;
}
.transmitters-chart .chart-tooltip td:first-child {
padding-right: 0.5rem;
}
.transmitters-chart .chart-tooltip td:last-child {
text-align: right;
}
.transmitters-chart .chart-legend {
display: flex;
margin-left: 20px;
}
.transmitters-chart .legend-item {
margin-right: 1.5rem;
display: flex;
align-items: center;
}
.transmitters-chart .legend-item:last-child {
margin-right: 0;
}
.transmitters-chart .legend-swatch {
width: 14px;
height: 14px;
background-color: currentColor;
margin-right: 0.5rem;
}
.transmitters-chart .legend-value {
color: var(--color-text);
}
.transmitters-chart .axis {
font-family: inherit;
color: var(--color-text);
}
.transmitters-chart .axis__unit {
font-size: 14px;
}
.transmitters-chart .axis path,
.transmitters-chart .axis line {
stroke: var(--color-axis);
}
.transmitters-chart .bar {
fill: currentColor;
}
.transmitters-chart .bar:hover {
stroke: var(--color-text);
}

View File

@ -0,0 +1,322 @@
/* global d3 */
/* eslint-disable no-unused-vars */
function renderTransmittersChart({ el, data }) {
d3.formatDefaultLocale({
decimal: '.',
thousands: '',
grouping: [3],
});
data = processData(data);
const container = d3.select(el).classed('transmitters-chart', true);
const tooltip = renderTooltip(container);
renderLegend(container, data.types);
const { zoom } = renderChart(container, data, tooltip);
function processData(data) {
let sorted = data
.map((d) => {
const width = (d.baud || 9600) / 1e6;
const x0 = d.downlink_low / 1e6 - width / 2;
const x1 = d.downlink_low / 1e6 + width / 2;
return {
x0,
x1,
width,
type: d.type.toLowerCase(),
data: d,
};
})
.sort((a, b) => d3.ascending(a.x0, b.x0));
const xMin = d3.min(sorted, (d) => d.x0);
const xMax = d3.max(sorted, (d) => d.x1);
const rows = [];
const types = ['Transmitter', 'Transceiver', 'Transponder'];
types.forEach((type) => {
let typeSorted = sorted.filter((d) => d.data.type === type);
while (typeSorted.length > 0) {
const picked = [];
const left = [];
let x = xMin - 1;
typeSorted.forEach((d, i) => {
if (d.x0 > x) {
picked.push(i);
x = d.x1;
} else {
left.push(i);
}
});
const row = picked.map((i) => typeSorted[i]);
row.type = type.toLowerCase();
rows.push(row);
typeSorted = left.map((i) => typeSorted[i]);
}
});
return {
rows,
types,
xMin,
xMax,
};
}
function renderTooltip(container) {
const tooltip = container.append('div').attr('class', 'chart-tooltip');
let tooltipBox,
containerBox;
function show(content, colorClass) {
tooltip.attr('class', `chart-tooltip ${colorClass}`).html(content);
tooltipBox = tooltip.node().getBoundingClientRect();
containerBox = container.node().getBoundingClientRect();
tooltip.classed('show', true);
}
function hide() {
tooltip.classed('show', false);
}
function move(event) {
let [x, y] = d3.pointer(event, container.node());
x = x - tooltipBox.width / 2;
if (x < 0) {x = 0;}
if (x + tooltipBox.width > containerBox.width)
{x = containerBox.width - tooltipBox.width;}
y = y - tooltipBox.height - 8;
if (y < 0) {
y = y + tooltipBox.height + 8 * 2;
}
tooltip.style('transform', `translate(${x}px,${y}px)`);
}
return {
show,
hide,
move,
};
}
function renderLegend(container, items) {
const legend = container.append('div').attr('class', 'chart-legend');
const legendItem = legend
.selectAll('.legend-item')
.data(items)
.join('div')
.attr('class', 'legend-item');
legendItem
.append('div')
.attr('class', (d) => `legend-swatch color-${d.toLowerCase()}`);
legendItem
.append('div')
.attr('class', 'legend-value')
.text((d) => d);
}
function renderChart(container, data, tooltip) {
const dimension = {
rowHeight: 20,
rowPadding: 4,
margin: {
top: 40,
right: 20,
bottom: 20,
left: 20,
},
totalWidth: null,
get width() {
return this.totalWidth - this.margin.left - this.margin.right;
},
get height() {
return this.rowHeight * data.rows.length;
},
get totalHeight() {
return this.height + this.margin.top + this.margin.bottom;
},
minBarWidth: 2,
};
const zoom = d3.zoom().on('zoom', zoomed);
const x = d3.scaleLinear().domain([data.xMin, data.xMax]);
let xz = x.copy();
const y = d3
.scaleBand()
.domain(d3.range(data.rows.length))
.range([0, dimension.height])
.paddingInner(dimension.rowPadding / dimension.rowHeight)
.paddingOuter(dimension.rowPadding / dimension.rowHeight / 2);
const xAxisTop = (g, x) =>
g.call(
d3
.axisTop(x)
.ticks(dimension.width / 80)
.tickPadding(6)
.tickSizeInner(-dimension.height)
.tickSizeOuter(0)
);
const xAxisBottom = (g, x) =>
g.call(
d3
.axisBottom(x)
.ticks(dimension.width / 80)
.tickPadding(6)
.tickSize(0)
);
const svg = container
.append('svg')
.attr('class', 'chart-svg')
.call(zoom)
.on('click', () => {});
const clipRect = svg
.append('defs')
.append('clipPath')
.attr('id', 'transmitters-chart-bars-clip')
.append('rect');
const g = svg
.append('g')
.attr(
'transform',
`translate(${dimension.margin.left},${dimension.margin.top})`
);
const axisUnit = g
.append('text')
.attr('class', 'axis axis__unit')
.attr('y', -20)
.attr('text-anchor', 'end')
.text('Frequency (MHz)');
const gAxisTop = g.append('g').attr('class', 'axis axis--top');
const gAxisBottom = g
.append('g')
.attr('class', 'axis axis--bottom')
.attr('transform', `translate(0,${dimension.height})`);
const gRows = g
.append('g')
.attr('class', 'bar-rows')
.attr('clip-path', 'url(#transmitters-chart-bars-clip)');
const gRow = gRows
.selectAll('.bar-row')
.data(data.rows)
.join('g')
.attr('class', 'bar-row')
.attr('transform', (_, i) => `translate(0,${y(i)})`);
const bar = gRow
.selectAll('.bar')
.data((d) => d)
.join('rect')
.attr('class', (d) => `bar color-${d.type}`)
.attr('height', y.bandwidth())
.on('mouseenter', entered)
.on('mouseleave', left)
.on('mousemove', moved)
.on('click', clicked);
resize();
window.addEventListener('resize', resize);
function entered(_, d) {
d3.select(this).raise();
tooltip.show(tooltipContent(d.data), `color-${d.type}`);
}
function left() {
tooltip.hide();
}
function moved(event) {
tooltip.move(event);
}
function clicked(_, d) {
window.open('/satellite/' + d.data.norad_cat_id + '#transmitters', 'transmitter');
}
function zoomed({ transform }) {
const range = x.range().map(transform.invertX, transform);
const domain = range.map(x.invert, x);
xz.domain(domain);
render();
}
function resize() {
dimension.totalWidth = container.node().clientWidth;
svg.attr('viewBox', [0, 0, dimension.totalWidth, dimension.totalHeight]);
clipRect.attr('width', dimension.width).attr('height', dimension.height);
axisUnit.attr('x', dimension.width);
x.range([0, dimension.width - dimension.minBarWidth]);
xz.range([0, dimension.width - dimension.minBarWidth]);
render();
}
function render() {
gAxisTop.call(xAxisTop, xz);
gAxisBottom.call(xAxisBottom, xz);
bar
.attr('x', (d) => xz(d.x0))
.attr('width', (d) =>
Math.max(dimension.minBarWidth, xz(d.x1) - xz(d.x0))
);
}
function tooltipContent(d) {
return `
<table>
<tbody>
<tr>
<td>NORAD ID</td>
<td>${d.norad_cat_id}</td>
</tr>
<tr>
<td>Type</td>
<td>${d.type}</td>
</tr>
<tr>
<td>Description</td>
<td>${d.description}</td>
</tr>
<tr>
<td>Downlink</td>
<td>${d3.format(',')(d.downlink_low / 1e6) + 'MHz'}</td>
</tr>
<tr>
<td>Mode</td>
<td>${d.mode}</td>
</tr>
<tr>
<td>Baud</td>
<td>${d.baud ? d3.format(',')(d.baud) : ''}</td>
</tr>
<tr>
<td>Service</td>
<td>${d.service}</td>
</tr>
<tr>
<td>Status</td>
<td>${d.status}</td>
</tr>
</tbody>
</table>
`;
}
return {
zoom: (minFreq, maxFreq) => {
svg
.transition()
.call(
zoom.transform,
d3.zoomIdentity
.scale(dimension.width / (x(maxFreq) - x(minFreq)))
.translate(-x(minFreq), 0)
);
},
};
}
return {
zoom,
};
}

View File

@ -1,3 +1,5 @@
/* global renderTransmittersChart */
function ppb_to_freq(freq, drift) {
var freq_obs = freq + ((freq * drift) / Math.pow(10,9));
return Math.round(freq_obs);
@ -68,4 +70,53 @@ $(document).ready(function() {
order: [ 1, 'asc' ],
pageLength: 25
} );
// Handle deep linking of tabbed panes
let url = location.href.replace(/\/$/, '');
history.replaceState(null, null, url);
if (location.hash) {
const hash = url.split('#');
$('#tabs a[href="#' + hash[1] + '"]').tab('show');
url = location.href.replace(/\/#/, '#');
history.replaceState(null, null, url);
setTimeout(() => {
$(window).scrollTop(0);
}, 400);
}
$('a[data-toggle="tab"]').on('click', function () {
let newUrl;
const hash = $(this).attr('href');
if (hash == '#list') {
newUrl = url.split('#')[0];
} else {
newUrl = url.split('#')[0] + hash;
}
history.replaceState(null, null, newUrl);
});
fetch('/api/transmitters/?format=json')
.then((response) => response.json())
.then((json) => {
const transmittersChart = renderTransmittersChart({
el: document.querySelector('#transmitters-chart-container'),
data: json,
});
transmittersChart.zoom(
434.5,
438.5
);
document.querySelector('#zoom-all').addEventListener('click', () => {
transmittersChart.zoom(0,30000);
});
document.querySelector('#zoom-vhf').addEventListener('click', () => {
transmittersChart.zoom(143,147);
});
document.querySelector('#zoom-uhf').addEventListener('click', () => {
transmittersChart.zoom(434.5,438.5);
});
});
} );

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block title %} - Transmitter List{% endblock %}
{% block title %} - Transmitters{% endblock %}
{% block css %}
<link rel="stylesheet" href="{% static 'lib/admin-lte/plugins/datatables-bs4/css/dataTables.bootstrap4.min.css' %}">
@ -12,65 +12,102 @@
<span class="h4 mb-0 mr-3 text-truncate d-none d-md-block">Transmitters</span>
{% endblock %}
{% block top-menu-right %}
<ul class="navbar-nav nav nav-pills" data-widget="treeview" role="menu" data-accordion="false" id="tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="list-tab" data-toggle="tab" href="#list" role="tab" aria-controls="list"
aria-selected="true" aria-label="List"><i class="nav-icon fas fa-th-list"></i>
<p class="d-none d-lg-inline-block text-sm">List</p>
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="spectrum-tab" data-toggle="tab" href="#spectrum" role="tab" aria-controls="spectrum"
aria-selected="false" aria-label="Spectrum"><i class="nav-icon fas fa-barcode"></i>
<p class="d-none d-lg-inline-block text-sm">Spectrum</p>
</a>
</li>
</ul>
{% endblock %}
{% block top %}
<span class="h4 mb-0">Transmitters</span>
{% endblock %}
{% block content %}
<div class="p-3">
<table id="transmitters" class="table table-sm display responsive table-striped" width="100%">
<thead>
<tr>
<th data-priority="1"></th>
<th data-visible="false" data-priority="20">UUID</th>
<th data-sortable="true" data-priority="1">Satellite</th>
<th data-sortable="true" data-priority="4">Type</th>
<th data-sortable="true" data-priority="1">Description</th>
<th data-sortable="true" data-priority="2">Downlink</th>
<th data-sortable="true" data-visible="false" data-priority="10">Downlink Drift</th>
<th data-sortable="true" data-visible="false" data-priority="10">Uplink</th>
<th data-sortable="true" data-visible="false" data-priority="10">Uplink Drift</th>
<th data-sortable="true" data-visible="false" data-priority="10">Inverted</th>
<th data-sortable="true" data-priority="3">Mode</th>
<th data-sortable="true" data-priority="4">Baud</th>
<th data-sortable="true" data-priority="10">Service</th>
<th data-sortable="true" data-priority="10">Status</th>
</tr>
</thead>
<tbody>
{% for trans in transmitters %}
<tr>
<td class='details-control'>
<i class="fa fa-plus-square" aria-hidden="true"></i>
</td>
<div class="row h-100 pl-2 pr-3">
<!-- The following div is managed by the tab menus -->
<div class="col-12 tab-content mx-2 pt-3" id="myTabContent">
<td>{{ trans.uuid }}</td>
<td>
{% if trans.satellite.norad_cat_id %}
<a href="{% url 'satellite' norad=trans.satellite.norad_cat_id %}">
{{ trans.satellite }}
</a>
{% else %}
{{ trans.satellite }}
{% endif %}
</td>
<td>{{ trans.type }}</td>
<td>{{ trans.description }}</td>
<td class="frequency">{{ trans.downlink_low }}</td>
<td>{{ trans.downlink_drift }}</td>
<td class="frequency">{{ trans.uplink }}</td>
<td>{{ trans.uplink_drift }}</td>
<td>{{ trans.invert }}</td>
<td>{{ trans.downlink_mode }}</td>
<td>{{ trans.baud }}</td>
<td>{{ trans.service }}</td>
<td>{{ trans.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- List panel -->
<div class="tab-pane fade show active mx-1" id="list" role="tabpanel" aria-labelledby="list-tab">
<table id="transmitters" class="table table-sm display responsive table-striped" width="100%">
<thead>
<tr>
<th data-priority="1"></th>
<th data-visible="false" data-priority="20">UUID</th>
<th data-sortable="true" data-priority="1">Satellite</th>
<th data-sortable="true" data-priority="4">Type</th>
<th data-sortable="true" data-priority="1">Description</th>
<th data-sortable="true" data-priority="2">Downlink</th>
<th data-sortable="true" data-visible="false" data-priority="10">Downlink Drift</th>
<th data-sortable="true" data-visible="false" data-priority="10">Uplink</th>
<th data-sortable="true" data-visible="false" data-priority="10">Uplink Drift</th>
<th data-sortable="true" data-visible="false" data-priority="10">Inverted</th>
<th data-sortable="true" data-priority="3">Mode</th>
<th data-sortable="true" data-priority="4">Baud</th>
<th data-sortable="true" data-priority="10">Service</th>
<th data-sortable="true" data-priority="10">Status</th>
</tr>
</thead>
<tbody>
{% for trans in transmitters %}
<tr>
<td class='details-control'>
<i class="fa fa-plus-square" aria-hidden="true"></i>
</td>
<td>{{ trans.uuid }}</td>
<td>
{% if trans.satellite.norad_cat_id %}
<a href="{% url 'satellite' norad=trans.satellite.norad_cat_id %}">
{{ trans.satellite }}
</a>
{% else %}
{{ trans.satellite }}
{% endif %}
</td>
<td>{{ trans.type }}</td>
<td>{{ trans.description }}</td>
<td class="frequency">{{ trans.downlink_low }}</td>
<td>{{ trans.downlink_drift }}</td>
<td class="frequency">{{ trans.uplink }}</td>
<td>{{ trans.uplink_drift }}</td>
<td>{{ trans.invert }}</td>
<td>{{ trans.downlink_mode }}</td>
<td>{{ trans.baud }}</td>
<td>{{ trans.service }}</td>
<td>{{ trans.status }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Spectrum panel -->
<div class="tab-pane fade show active mx-1" id="spectrum" role="tabpanel" aria-labelledby="spectrum-tab">
<div id="transmitters-chart-container"></div>
<div class="p-3">
<button type="button" class="btn btn-primary" id="zoom-all">
<i class="fas fa-search"></i> All</button>
<button type="button" class="btn btn-primary" id="zoom-vhf">
<i class="fas fa-search"></i> VHF Amateur</button>
<button type="button" class="btn btn-primary" id="zoom-uhf">
<i class="fas fa-search"></i> UHF Amateur</button>
</div>
</div>
{% endblock %}
{% block javascript %}
@ -80,6 +117,8 @@
<script src="{% static 'lib/admin-lte/plugins/datatables-buttons/js/buttons.colVis.js' %}"></script>
<script src="{% static 'lib/admin-lte/plugins/datatables-bs4/js/dataTables.bootstrap4.min.js' %}"></script>
<script src="{% static 'lib/admin-lte/plugins/datatables-buttons/js/buttons.bootstrap4.min.js' %}"></script>
<script src="{% static 'lib/d3/dist/d3.min.js' %}"></script>
<script src="{% static 'js/datatables-natural.js' %}"></script>
<script src="{% static 'js/transmitters-chart.js' %}"></script>
<script src="{% static 'js/transmitters.js' %}"></script>
{% endblock %}

268
package-lock.json generated
View File

@ -1542,6 +1542,11 @@
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"dev": true
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@ -1670,6 +1675,263 @@
"type": "^1.0.1"
}
},
"d3": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/d3/-/d3-6.2.0.tgz",
"integrity": "sha512-aH+kx55J8vRBh4K4k9GN4EbNO3QnZsXy4XBfrnr4fL2gQuszUAPQU3fV2oObO2iSpreRH/bG/wfvO+hDu2+e9w==",
"requires": {
"d3-array": "2",
"d3-axis": "2",
"d3-brush": "2",
"d3-chord": "2",
"d3-color": "2",
"d3-contour": "2",
"d3-delaunay": "5",
"d3-dispatch": "2",
"d3-drag": "2",
"d3-dsv": "2",
"d3-ease": "2",
"d3-fetch": "2",
"d3-force": "2",
"d3-format": "2",
"d3-geo": "2",
"d3-hierarchy": "2",
"d3-interpolate": "2",
"d3-path": "2",
"d3-polygon": "2",
"d3-quadtree": "2",
"d3-random": "2",
"d3-scale": "3",
"d3-scale-chromatic": "2",
"d3-selection": "2",
"d3-shape": "2",
"d3-time": "2",
"d3-time-format": "3",
"d3-timer": "2",
"d3-transition": "2",
"d3-zoom": "2"
}
},
"d3-array": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.8.0.tgz",
"integrity": "sha512-6V272gsOeg7+9pTW1jSYOR1QE37g95I3my1hBmY+vOUNHRrk9yt4OTz/gK7PMkVAVDrYYq4mq3grTiZ8iJdNIw=="
},
"d3-axis": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.0.0.tgz",
"integrity": "sha512-9nzB0uePtb+u9+dWir+HTuEAKJOEUYJoEwbJPsZ1B4K3iZUgzJcSENQ05Nj7S4CIfbZZ8/jQGoUzGKFznBhiiQ=="
},
"d3-brush": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-2.1.0.tgz",
"integrity": "sha512-cHLLAFatBATyIKqZOkk/mDHUbzne2B3ZwxkzMHvFTCZCmLaXDpZRihQSn8UNXTkGD/3lb/W2sQz0etAftmHMJQ==",
"requires": {
"d3-dispatch": "1 - 2",
"d3-drag": "2",
"d3-interpolate": "1 - 2",
"d3-selection": "2",
"d3-transition": "2"
}
},
"d3-chord": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz",
"integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==",
"requires": {
"d3-path": "1 - 2"
}
},
"d3-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-2.0.0.tgz",
"integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ=="
},
"d3-contour": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz",
"integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==",
"requires": {
"d3-array": "2"
}
},
"d3-delaunay": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
"requires": {
"delaunator": "4"
}
},
"d3-dispatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-2.0.0.tgz",
"integrity": "sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA=="
},
"d3-drag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-2.0.0.tgz",
"integrity": "sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==",
"requires": {
"d3-dispatch": "1 - 2",
"d3-selection": "2"
}
},
"d3-dsv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz",
"integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==",
"requires": {
"commander": "2",
"iconv-lite": "0.4",
"rw": "1"
}
},
"d3-ease": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-2.0.0.tgz",
"integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ=="
},
"d3-fetch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz",
"integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==",
"requires": {
"d3-dsv": "1 - 2"
}
},
"d3-force": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz",
"integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==",
"requires": {
"d3-dispatch": "1 - 2",
"d3-quadtree": "1 - 2",
"d3-timer": "1 - 2"
}
},
"d3-format": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-2.0.0.tgz",
"integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA=="
},
"d3-geo": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.1.tgz",
"integrity": "sha512-M6yzGbFRfxzNrVhxDJXzJqSLQ90q1cCyb3EWFZ1LF4eWOBYxFypw7I/NFVBNXKNqxv1bqLathhYvdJ6DC+th3A==",
"requires": {
"d3-array": ">=2.5"
}
},
"d3-hierarchy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz",
"integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw=="
},
"d3-interpolate": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-2.0.1.tgz",
"integrity": "sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==",
"requires": {
"d3-color": "1 - 2"
}
},
"d3-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz",
"integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA=="
},
"d3-polygon": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz",
"integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ=="
},
"d3-quadtree": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz",
"integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw=="
},
"d3-random": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz",
"integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw=="
},
"d3-scale": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.3.tgz",
"integrity": "sha512-8E37oWEmEzj57bHcnjPVOBS3n4jqakOeuv1EDdQSiSrYnMCBdMd3nc4HtKk7uia8DUHcY/CGuJ42xxgtEYrX0g==",
"requires": {
"d3-array": "^2.3.0",
"d3-format": "1 - 2",
"d3-interpolate": "1.2.0 - 2",
"d3-time": "1 - 2",
"d3-time-format": "2 - 3"
}
},
"d3-scale-chromatic": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz",
"integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==",
"requires": {
"d3-color": "1 - 2",
"d3-interpolate": "1 - 2"
}
},
"d3-selection": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-2.0.0.tgz",
"integrity": "sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA=="
},
"d3-shape": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.0.0.tgz",
"integrity": "sha512-djpGlA779ua+rImicYyyjnOjeubyhql1Jyn1HK0bTyawuH76UQRWXd+pftr67H6Fa8hSwetkgb/0id3agKWykw==",
"requires": {
"d3-path": "1 - 2"
}
},
"d3-time": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-2.0.0.tgz",
"integrity": "sha512-2mvhstTFcMvwStWd9Tj3e6CEqtOivtD8AUiHT8ido/xmzrI9ijrUUihZ6nHuf/vsScRBonagOdj0Vv+SEL5G3Q=="
},
"d3-time-format": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
"integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
"requires": {
"d3-time": "1 - 2"
}
},
"d3-timer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-2.0.0.tgz",
"integrity": "sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA=="
},
"d3-transition": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-2.0.0.tgz",
"integrity": "sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==",
"requires": {
"d3-color": "1 - 2",
"d3-dispatch": "1 - 2",
"d3-ease": "1 - 2",
"d3-interpolate": "1 - 2",
"d3-timer": "1 - 2"
}
},
"d3-zoom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz",
"integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==",
"requires": {
"d3-dispatch": "1 - 2",
"d3-drag": "2",
"d3-interpolate": "1 - 2",
"d3-selection": "2",
"d3-transition": "2"
}
},
"dash-ast": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz",
@ -2039,6 +2301,11 @@
}
}
},
"delaunator": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="
},
"detect-file": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
@ -4086,7 +4353,6 @@
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}

View File

@ -17,6 +17,7 @@
},
"dependencies": {
"admin-lte": "^3.0",
"d3": "^6.2.0",
"flot": "^4.2.1",
"gpredict.js": "github:kerel-fs/gpredict.js",
"mapbox-gl": "^1.11.0"