Published
Edited
May 3, 2021
2 stars
Insert cell
Insert cell
Insert cell
dashboard = html`

<div style="display: flex;">
<div style="margin-left: 15px; margin-right: -30px; border:1px; text-align:right;">${viewof map_type} </div>
<div style="margin-right: 15px; border:1px; text-align:right;">${viewof select_meta} </div>
</div>

<div style="display: flex;">
<div style="margin-top: 150px; flex-basis:50%; border:1px ">${container} </div>
<div style="margin-top: -25px; flex-basis:1000px">${viewof cgm}</div>
</div>

`
Insert cell
viewof cgm = {

// Clustergrammer-GL uses query selectors to re-find the container, so if we
// might want multiple instances of Clustergrammer-GL on the page, they’ll
// need unique identifiers! (It’d be better if Clustergrammer-GL used
// element.querySelector instead of document.querySelector and didn’t depend
// on these identifiers.)
const containerId = DOM.uid("container");
const container = html`<div style="height: 1025px;" id='${containerId.id}'></div>`;

// Clustergrammer-GL depends on the container having been inserted into the
// document before it’s initialized. We can do this by yielding! However,
// since we’re defining this cell as a view, we don’t want the view’s value
// to resolve until CGM has initialized. Hence we define the view’s value as
// a promise that resolves immediately after. We could simplify this if
// Clustergrammer-GL allowed you to pass in a detached container.
let resolve;
container.value = new Promise(r => resolve = r);
yield container;

// Initialize CGM.
// (We might want to use Observable’s reactive width here?)
const cgm = CGM({
network,
viz_width: 900,
viz_height: 900,
container
});

// Redefine the order parameters as getters and setters, so that we can intercept
// when these change and emit an event. That will allow other cells to listen for
// these changes are react accordingly.
Object.defineProperties(cgm.params.order.new, {
row: reactiveValue(container, "row", cgm.params.order.new.row),
col: reactiveValue(container, "col", cgm.params.order.new.col)
});

// Resolve the view’s value so that downstream cells can run.
resolve(cgm);
d3.select('.row_search_container').remove()
}
Insert cell
network = fetch("https://raw.githubusercontent.com/ismms-himc/clustergrammer2/master/jsons/rc_two_cats.json")
.then(response => response.json())
Insert cell
CGM = require(await FileAttachment("clustergrammer-gl.0.22.0.packd.js").url())
Insert cell
function reactiveValue(container, name, value) {
return {
get() {
return value;
},
set(v) {
value = v;
container.dispatchEvent(new CustomEvent("input", {detail: {name, value}}));
return value;
}
};
}
Insert cell
select_meta_type = typeof data[0][select_meta]
Insert cell
ini_map_type = 'UMAP'
Insert cell
viewof select_meta = Select([null].concat(meta_data_cols), {label: "Metadata", value: ini_cat})
Insert cell
ini_cat = null
Insert cell
num_frames = 45
Insert cell
delay_frames = 30
Insert cell
numbers = Array.from({length: num_frames + 1}, (_, i) => i/num_frames)
Insert cell
// viewof radius = Range([0, 255], {step: 1})
radius = 10
Insert cell
import {Select, Range} from '@observablehq/inputs'
Insert cell
{

const autoHighlight = true
// have deck.gl animate
function apply_map_morph(d){
var inst_x
var inst_y
if (map_type === 'Spatial'){
inst_x = d.x
inst_y = - d.y
} else {
inst_x = umap_x_scale(d['umap-x'])
inst_y = - umap_y_scale(d['umap-y'])
}
return [inst_x, inst_y]
}
var transitions = {
getPosition: {
duration:transitionDuration,
easing: d3.easeCubic
}
}
const scatterLayer = new deck.ScatterplotLayer({
id: 'scatterLayer',
data: data,
getPosition: apply_map_morph,
getFillColor: d => {
var inst_color
if (['undefined'].includes(select_meta_type)){
// show dim scatterplot
inst_color = [50, 50, 50, 100]
} else {

// value or category based metadata
if (select_meta_type === 'number'){
// color by value
////////////////////////////////////////
if (d[select_meta] > 0){
inst_color = [255, 0, 0, opacity_scale(d[select_meta])]
} else {
inst_color = [50, 50, 50, 15]
}
} else {
// color by category
////////////////////////////////////////
var rgb = d3.color(cat_colors[d[select_meta]])
inst_color = [rgb.r, rgb.g, rgb.b, 255 * cat_opacity]
}
}
return inst_color
},
getRadius: radius,
pickable: true,
// autoHighlight: true,
highlightColor: d => [50, 50, 50],
radiusMinPixels: radius_min_pixels,
updateTriggers: {
getFillColor: select_meta,
getPosition: map_type
},
transitions: transitions
})
deckgl.setProps({layers: [scatterLayer]});

}
Insert cell
Insert cell
meta_data_cols = Object.keys(data[0])
.filter(x => non_param_cols.includes(x) === false)
Insert cell
meta_data_cols
Insert cell
meta_max = Math.max.apply(Math, data.map(function(x) { return parseFloat(x[select_meta]) }))
Insert cell
data.map(x => { return parseFloat(x['some']) })
Insert cell
data
Insert cell
opacity_scale = d3.scaleLinear()
.domain([0, meta_max])
.range([0, 250])
Insert cell
opacity_scale(20)
Insert cell
non_param_cols = ['x', 'y', 'umap-x', 'umap-y', 'name']
Insert cell
data = [
{
'name': 'one',
'x': 1,
'y': 1,
'cat':'dog',
'val': 1,
'umap-x':1,
'umap-y':1
},
{
'name': 'two',
'x': 20,
'y': 20,
'val': 20,
'cat':'elephant',
'umap-x':-20,
'umap-y':-20
}
]

Insert cell
cat_colors = ({
'dog': 'blue',
'elephant': 'red'
})
Insert cell
d3.color(cat_colors['cat'])
Insert cell
transitionDuration = 3000
Insert cell
x_min = Math.min.apply(Math, data.map(function(o) { return parseFloat(o['x']) }))
Insert cell
x_max = Math.max.apply(Math, data.map(function(o) { return parseFloat(o['x']) }))
Insert cell
y_min = Math.min.apply(Math, data.map(function(o) { return parseFloat(o['y']) }))
Insert cell
y_max = Math.max.apply(Math, data.map(function(o) { return parseFloat(o['y']) }))
Insert cell
umap_x_min = Math.min.apply(Math, data.map(function(o) { return parseFloat(o['umap-x']) }))
Insert cell
umap_x_max = Math.max.apply(Math, data.map(function(o) { return parseFloat(o['umap-x']) }))
Insert cell
umap_y_min = Math.min.apply(Math, data.map(function(o) { return parseFloat(o['umap-y']) }))
Insert cell
umap_y_max = Math.max.apply(Math, data.map(function(o) { return parseFloat(o['umap-y']) }))
Insert cell
umap_x_scale = d3.scaleLinear()
.domain([umap_x_min, umap_x_max])
.range([x_min, x_max]);
Insert cell
umap_y_scale = d3.scaleLinear()
.domain([umap_y_min, umap_y_max])
.range([y_min, y_max]);
Insert cell
import {slider} from '@jashkenas/inputs'
Insert cell
unselected_opacity_scale = 0.25
Insert cell
meta_size_scale = 1.25
Insert cell
default_opacity = 0.5
Insert cell
radius_min_pixels = 1
Insert cell
height = 750
Insert cell
width = 'auto'
Insert cell
cat_opacity = 0.9
Insert cell
initial_view_state = ({
target: [ini_x, -ini_y, 0],
zoom: zoom,
minZoom:minZoom
})
Insert cell
ini_x = d3.mean(data, d => +d.x) // 0
Insert cell
ini_y = d3.mean(data, d => +d.x) // 0
Insert cell
zoom = 1
Insert cell
minZoom = zoom - 1
Insert cell
data[0]
Insert cell
tooltip = meta_data_cols
Insert cell
tooltip
Insert cell
tooltip_string = tooltip.map(x => x + ':' + String(data[0][x]) ).join('\n')
Insert cell
data[0]['name']
Insert cell
deckgl = {
// // reference for below https://observablehq.com/@tomvantilburg/deckgl-mapbox-and-3d-tiles
// // This is an Observable hack: clear previously generated content
container.innerHTML = '';
const view = new deck.OrthographicView({id: 'ortho'})
return new deck.DeckGL({
container,
views:[view],
initialViewState:initial_view_state,
controller: true,
getTooltip: ({object}) => {
return object &&
`name: ${object.name}\npos: ${object.x}, ${object['y']}\n` +
tooltip.map(x => x + ': ' + String(object[x]) ).join('\n')
},
});
}
Insert cell
deck = require.alias({
// optional dependencies
h3: {}
})('deck.gl@latest/dist.min.js')
Insert cell
d3 = require("d3@5")
Insert cell
data[0].hasOwnProperty('name')
Insert cell
Insert cell
viewof map_type = {
const element = html`
<div style="display: inline-block; user-select: none;"></div>`;

d3.select(element)
.selectAll('div')
.data(['Spatial', 'UMAP'])
.join('span')
.style('min-width', '200px')
.style('max-width', '200px')
.style('margin-right', '10px')
.text(d => d)
.style('font-weight', '550px')
.style("font-family", "sans-serif")
.style("font-size", "16")
.style("text-anchor", "end")
.style('font-weight', 'bold')
.style('color', d => d === 'UMAP' ? 'blue': '#808080')
.on('click', function(d){
d3.select(element).selectAll('span')
.style('color', '#808080')
d3.select(this)
.style('color', 'blue')
element.value = d.replace(', ', '')
element.dispatchEvent(new CustomEvent("input"));
})

element.value = ini_map_type
element.dispatchEvent(new CustomEvent("input"))

return element
}
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