Public
Edited
Dec 5, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const selection = vl.selectSingle(); // setup selection type
return vl.markCircle()
.data(cars)
.select(selection) // apply selection type to circle mark
.encode(
vl.x().fieldQ('Horsepower'),
vl.y().fieldQ('Miles_per_Gallon'),
vl.color().if(selection, vl.fieldO('Cylinders')).value('grey'), // selection indicator
vl.opacity().if(selection, vl.value(0.8)).value(0.1) // selection indicator
)
.render()
}
Insert cell
Insert cell
function plot(selection) { // basic scatterplot setup to respond to selection
return vl.markCircle()
.data(cars)
.select(selection)
.encode(
vl.x().fieldQ('Horsepower'),
vl.y().fieldQ('Miles_per_Gallon'),
vl.color().if(selection, vl.fieldO('Cylinders')).value('grey'),
vl.opacity().if(selection, vl.value(0.8)).value(0.1)
)
.width(240)
.height(180);
}
Insert cell
Insert cell
vl.hconcat(
plot(vl.selectSingle()).title('Single (Click)'),
plot(vl.selectMulti()).title('Multi (Shift-Click)'),
plot(vl.selectInterval()).title('Interval (Drag)')
).render()
Insert cell
Insert cell
vl.hconcat(
plot(vl.selectSingle().on('mouseover')).title('Single (Mouseover)'),
plot(vl.selectMulti().on('mouseover')).title('Multi (Shift-Mouseover)')
).render()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
genres = uniqueValid(movies, d => d.Major_Genre)
Insert cell
Insert cell
mpaa = ['G', 'PG', 'PG-13', 'R', 'NC-17', 'Not Rated']
Insert cell
Insert cell
{
const selectGenre = vl.selectSingle("Select") // name the selection 'Select'
.fields('Major_Genre') // limit selection to the Major_Genre field
.init({Major_Genre: genres[0]}) // use first genre entry as initial value
.bind(vl.menu(genres)); // bind to a menu of unique genre values
// scatter plot, modify opacity based on genre selection
return vl.markCircle()
.data(movies)
.select(selectGenre)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(selectGenre, vl.value(0.75)).value(0.05)
)
.render();
}
Insert cell
Insert cell
Insert cell
{
// single-value selection over [Major_Genre, MPAA_Rating] pairs
// use specific hard-wired values as the initial selected values
const selection = vl.selectSingle('Select')
.fields('Major_Genre', 'MPAA_Rating')
.init({Major_Genre: 'Drama', MPAA_Rating: 'R'})
.bind({Major_Genre: vl.menu(genres), MPAA_Rating: vl.radio(mpaa)}); // dropdown menu and radio buttons
// scatter plot, modify opacity based on selection
return vl.markCircle()
.data(movies)
.select(selection)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(selection, vl.value(0.75)).value(0.05)
)
.render();
}
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval()
.encodings('x'); // limit selection to x-axis (year) values
// dynamic query histogram
const years = vl.markBar({width: 4})
.data(movies)
.select(brush)
.encode(
vl.x().year('Release_Date').title('Films by Release Year'),
vl.y().count().title(null)
)
.width(600)
.height(50);
// ratings scatter plot
const ratings = vl.markCircle()
.data(movies)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating'),
vl.tooltip().fieldN('Title'),
vl.opacity().if(brush, vl.value(0.75)).value(0.05) // no selection on scatter plot, but is conditional
)
.width(600)
.height(400);

return vl.vconcat(years, ratings).spacing(5).render();
}
Insert cell
Insert cell
Insert cell
Insert cell
vl.markCircle()
.data(movies)
.select(
vl.selectInterval().bind('scales') // bind interval selection to scale domains
)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating')
.axis({minExtent: 30}), // add min extent to stabilize axis title placement
vl.tooltip(['Title', 'Release_Date', 'IMDB_Rating', 'Rotten_Tomatoes_Rating'])
)
.width(600)
.height(450)
.render()
Insert cell
Insert cell
vl.markCircle()
.data(movies)
.select(
vl.selectInterval().bind('scales').encodings('x') // bind to x-axis scale only
)
.encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating').axis({minExtent: 30}),
vl.tooltip(['Title', 'Release_Date', 'IMDB_Rating', 'Rotten_Tomatoes_Rating'])
)
.width(600)
.height(450)
.render()
Insert cell
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval().encodings('x'); // only x-axis selection
const x = vl.x().fieldT('date').title(null);
const base = vl.markArea() // same base area chart
.encode(x, vl.y().fieldQ('price'))
.width(700);
return vl.data(sp500)
.vconcat(
base.encode(x.scale({domain: brush})), // update x encoding so domain mapped to selection
base.select(brush).height(60) // mapped to brush, short height
)
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const hover = vl.selectSingle()
.on('mouseover') // select on mouseover
.nearest(true) // select nearest point to mouse cursor
.empty('none'); // empty selection should match nothing
const click = vl.selectMulti()
.empty('none'); // empty selection matches no points
// scatter plot encodings shared by all marks
const plot = vl.markCircle().encode(
vl.x().fieldQ('Rotten_Tomatoes_Rating'),
vl.y().fieldQ('IMDB_Rating')
);
// shared base for new layers
const base = plot.transform(
vl.filter(vl.or(hover, click)) // filter to points in either selection
);
// mark properties for new layers
const halo = {size: 100, stroke: 'firebrick', strokeWidth: 1};
const label = {dx: 4, dy: -8, align: 'right'};
const white = {stroke: 'white', strokeWidth: 2};

// layer scatter plot points, halo annotations, and title labels
return vl.data(movies)
.layer(
plot.select(hover, click),
base.markPoint(halo),
base.markText(label, white).encode(vl.text().fieldN('Title')),
base.markText(label).encode(vl.text().fieldN('Title'))
)
.width(600)
.height(450)
.render();
}
Insert cell
Insert cell
Insert cell
{
const brush = vl.selectInterval()
.resolve('global'); // resolve all selections to a single global instance
return vl.markCircle()
.data(cars)
.select(brush)
.encode(
vl.x().fieldQ(vl.repeat('column')),
vl.y().fieldQ(vl.repeat('row')),
vl.color().if(brush, vl.fieldO('Cylinders')).value('grey'),
vl.opacity().if(brush, vl.value(0.8)).value(0.1)
)
.width(140)
.height(140)
.repeat({
column: ['Acceleration', 'Horsepower', 'Miles_per_Gallon'],
row: ['Acceleration', 'Horsepower', 'Miles_per_Gallon']
})
.render();
}
Insert cell
Insert cell
Insert cell
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