Published
Edited
Jun 25, 2021
2 forks
2 stars
Insert cell
Insert cell
chartElement = html`<div style="width: 100%; height: 500px; position: relative;"></div>`
Insert cell
snMapbox = {
const options = {
accessToken: 'pk.eyJ1IjoieWlhbm5pLXZlcnZlcmlzIiwiYSI6ImNrcWF0azdnejBjdm4yd3M3ajBmb2hpeGkifQ.rl7QWaaMtqRYNJ-vMIMoOA', // Change this to your free personal token
style: 'mapbox://styles/mapbox/streets-v11', // This is the default map style
center: [-60, 20],
zoom: 2,
pitch: 0,
bearing: 0,
circleRadius: 8,
circleOpacity: 1,
// Custom tooltip that displays the last 2 dimensions / properties
tooltip: (obj) => `
<div>Gender: ${obj.gender}</div>
<div>Age Bucket: ${obj.AgeBucket}</div>
`,
createLayers: true,
// Add a flying point for entry animation
flyTo: {
center: [-74.50, 40],
zoom: 4,
speed: 0.3,
curve: 1,
easing(t) {
return t;
}
},
// The colors for the dots
palette: [
'#3399CC', // Light Blue
'#CC6666', // Light Red
]
};
return {
// Define the Engine API HyperCube
qae: {
properties: {
qHyperCubeDef: {
qDimensions: [],
qMeasures: [],
qInitialDataFetch: [{ qWidth: 5, qHeight: 2000 }],
qSuppressZero: true,
qSuppressMissing: true,
},
showTitles: true,
title: 'US Data',
subtitle: 'Random gender / age buckets',
footnote: 'Data is random, for this example only.',
},
data: {
targets: [
{
path: '/qHyperCubeDef',
dimensions: {
min: 1,
max: 5,
},
measures: {
min: 0,
max: 0,
},
},
],
},
},
component() {
const element = stardust.useElement();
const layout = stardust.useLayout();
const qData = layout.qHyperCube?.qDataPages[0];
const qMatrix = qData.qMatrix.filter(row => row.some(el => el.qNum !== "NaN"))
const property = layout.qHyperCube?.qDimensionInfo[3]?.qFallbackTitle;
const property2 = layout.qHyperCube?.qDimensionInfo[4]?.qFallbackTitle;
const [instance, setInstance] = stardust.useState();
let GeoJSON, map = null;
let mapData = [];
const propertyChildren = [...new Set(qMatrix.map((array) => array[3].qText))];
const propertyChildrenWithColors = propertyChildren.reduce((r, e, i) => r.push(e, options.palette[i]) && r, []);

// Create the Mapbox features based on our HyperCube data
const buildFeatures = (obj) => {
const featureObj = {
type: 'Feature',
properties: {
count: 1,
userID: obj.id,
[property]: obj[property],
},
geometry: {
type: 'Point',
coordinates: [obj.lng, obj.lat],
},
};
if (options.tooltip !== null) {
featureObj.properties.description = options.tooltip(obj);
}
return featureObj;
}

// Convert our HyperCube data into a GeoJSON for Mapbox
const buildGeoJSON = () => {
const goodGeoJSON = {
type: 'FeatureCollection',
features: [],
};
qMatrix.map((array) => {
if (typeof array[1].qNum !== 'number' || typeof array[2].qNum !== 'number') return false;
const obj = {
id: Number(array[0].qNum),
lat: Number(array[1].qNum),
lng: Number(array[2].qNum),
};

obj[property] = array[3].qText;
obj[property2] = array[4].qText;

const feature = buildFeatures(obj);
goodGeoJSON.features.push(feature);
return obj;
});
return goodGeoJSON;
}
// Create the layer that will hold the dots
const buildLayer = () => {
const match = ['match', ['get', property], ...propertyChildrenWithColors, '#FFF'];
const layer = {
id: 'dots',
type: 'circle',
source: 'hyperCubeData',
paint: {
'circle-stroke-width': 0,
'circle-radius': options.circleRadius,
'circle-color': match,
'circle-opacity': options.circleOpacity,
},
};
return layer;
}

// Create the map
const buildMap = () => {
// Add HyperCube data as GeoJSON
map.addSource('hyperCubeData', {
type: 'geojson',
data: GeoJSON,
});
// Create the layer
const layer = buildLayer();
map.addLayer(layer);
if (options.extraLayers && options.extraLayers.length) {
options.extraLayers.map((_layer) => map.addLayer(_layer));
}
// Create Tooltips and the triggering events
if (options.tooltip !== null) {
const popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false,
className: 'sn-mapbox-tooltip',
});
map.on('mouseenter', 'dots', (e) => {
map.getCanvas().style.cursor = 'pointer';
const coordinates = e.features[0].geometry.coordinates.slice();
const { description } = e.features[0].properties;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
popup
.setLngLat(coordinates)
.setHTML(description)
.addTo(map);
});
map.on('mouseleave', 'dots', () => {
map.getCanvas().style.cursor = '';
popup.remove();
});
}
};
// Update layer data upon HyperCube change
const updateLayers = () => {
const nextChunk = qMatrix.map((array) => {
const obj = {
id: Number(array[0].qNum),
lat: Number(array[1].qNum),
lng: Number(array[2].qNum),
[property]: array[3].qText,
};

return buildFeatures(obj);
});
if (GeoJSON) {
GeoJSON = { ...GeoJSON, features: [...GeoJSON.features, ...nextChunk] };
map.getSource('hyperCubeData').setData(GeoJSON);
} else {
GeoJSON = buildGeoJSON();
buildMap();
}
};
stardust.useEffect(() => {
mapboxgl.accessToken = options.accessToken;
if (!map) {
// Initialize mapbox GL
map = new mapboxgl.Map({
container: element,
...options,
});
// Add layer with data
map.on('load', () => {
updateLayers(qData); // Draw the first set of data, in case we load all
mapData = [...mapData, ...qMatrix];
});
// Add intro animation
if (options.flyTo) {
map.flyTo(options.flyTo);
}
}
}, [layout]);

},
};
}
Insert cell
nebula.render({
element: chartElement,
type: 'sn-mapbox',
fields: [ 'ID', 'lat', 'lon', 'gender', 'AgeBucket'],
});
Insert cell
qlikApp = {
const config = {
host: 'sense-demo.qlik.com',
appId: '4052680c-fd97-4f49-ac83-e026cdd26d65',
};
const url = SenseUtilities.buildUrl(config);
const session = enigma.create({ schema, url });

const qlikApp = await session.open().then((global) => global.openDoc(config.appId));
return qlikApp;
}
Insert cell
enigma = require('enigma.js');
Insert cell
nebula = {
const nebula = await stardust.embed(qlikApp, {
types: [{
name: 'sn-mapbox',
load: () => snMapbox,
}],
});
return nebula;
};
Insert cell
schema = FileAttachment("12.170.2.json").json()
Insert cell
SenseUtilities = require('enigma.js/sense-utilities');
Insert cell
stardust = require('@nebula.js/stardust');
Insert cell
mapboxgl = require('mapbox-gl');
Insert cell
html`<link href="https://api.mapbox.com/mapbox-gl-js/v2.3.0/mapbox-gl.css" rel="stylesheet">`
Insert cell
html`<style>
.mapboxgl-popup-tip {
border-top-color: rgba(0,0,0,0.8) !important;
}
.mapboxgl-popup-content {
color: white;
background: rgba(0,0,0,0.8);
box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
}

</style>`
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more