Public
Edited
Apr 5, 2023
Paused
19 forks
1 star
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
viewof crimeBars1 = {
const selectedBar = vl
.selectPoint("selected_bar") // create our selection param
.fields("Offense") // select all crimes of the same type
//.nearest(true) // select nearest point to click
.on("click") // mouse click for selection ('click' is default but specifying for clarity)
//.nearest(true) // select nearest point to the click (helpful because the points are small)
.clear("dblclick")
.bind("legend"); // also bind to the legend; // double click to clear selection

const legend = vl.selectPoint().fields("Offense Category").bind("legend");

const barandlegend = vl.and(selectedBar, legend);

const crimeChart = vl
.markBar()
.width(500)
.data(vanCrime2)
.title("Vancouver Non-Violent Crimes Totals by Offense Type Summer 2016")
.params(selectedBar, legend) // add in the selection param
// .transform(vl.filter(selectedBar) // filter out all but the selected point
.encode(
vl.y().fieldN("Offense").scale({ domain: OffenseTypes }),
vl.x()
.fieldQ("*")
.aggregate("count")
.scale({ domain: [0, 4400] }),
vl.color()
.if(
barandlegend,
vl
.color()
.fieldN("Offense Category")
.scale({ range: OffenseCatColors })
)
.value("lightgrey"),
// .if(selectedBar, vl.fieldN("Offense Category"))
// .value("grey"), // if point selected, use default color, otherwise grey
vl.opacity().if(selectedBar, vl.value(0.99)).value(0.1) // if point selected, use opacity 0.7, otherwise 0.1
);

const text = vl
.markText({ dx: 20, color: "grey" })
.data(vanCrime2)
.transform(vl.filter(selectedBar.empty(false))) // show text for selection but only if a selection exists - is not working
.encode(
vl.y().fieldN("Offense").scale({ domain: OffenseTypes }),
vl.x().fieldQ("*").aggregate("count"),
vl.text().fieldQ("*").aggregate("count")
);

return vl.layer(crimeChart, text).render();
}
Insert cell
Insert cell
viewof crimeTL1 = {
const clickSelector = vl.selectPoint()
.on("click") // select on click
.fields("Offense Category") // select all crimes of the same type
.bind("legend") // also bind to the legend; // double click to clear selection
.toggle(true); // allow for multi-select (on by default) with shift-click

const isClicked = clickSelector.empty(false);

const crimeTimes = vl
.markLine({ strokeWidth: 3 })
.width(600)
.title("Vancouver Non-Violent Crime Trends by Offense Category Summer 2016")
.encode(
vl.x().fieldT("Date"),
vl.y().fieldQ("*").aggregate("count"),
vl.color().fieldN("Offense Category").scale({ range: OffenseCatColors }),
vl.opacity().if(clickSelector, vl.value(0.99)).value(0.2) // if point selected, use opacity 0.99, otherwise 0.1
);

// shared base for new layers but filtered
const base = crimeTimes.transform(vl.filter(isClicked));

const label = { dx: 4, dy: -8, align: "left" };
const black = { stroke: "black", strokeWidth: 0.5 };
const white = { stroke: "white", strokeWidth: 2 };
return vl
.data(vanCrime2)
.layer(
crimeTimes.params(clickSelector),
base
.markText(label, white)
.encode(vl.text().fieldQ("*").aggregate("count")), // backing white text
base
.markText(label, "black")
.encode(vl.text().fieldQ("*").aggregate("count")) // black text label
)
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell
{ //Bar chart offenses x non-violent trends by TYPE
// set the selection interactions to be able to select either an offense type, an offense category, or a time interval
const selectTimeBrush = vl.selectInterval().encodings("x");
// Use an OR operator on which time scrubber to use

const selectType = vl
.selectPoint("selected_pt")
.on("click") // select on click
.fields("Offense") // select all crimes of the same type
.bind("legend") // also bind to the legend
.clear("dblclick") // double click to clear selection
.toggle(true); // allow for multi-select (on by default) with shift-click
//draw the charts

const crimeTrends = vl
.markLine({ strokeWidth: 2 })
.data(vanCrime2)
// .transform(vl.filter(selectType))
//.transform(vl.filter(selectTimeBrushCat))
.params(selectTimeBrush)
.title("Non-Violent Crime Trends by Type Summer 2016")
.encode(
vl.x().fieldT("Date"),
vl.detail().fieldN("Offense"),
vl.y()
.fieldQ("*")
.count()
.scale({ domain: [0, 60] }),
vl.color().fieldN("Offense Category").scale({ range: OffenseCatColors }),
vl.opacity().if(selectType, vl.value(0.99)).value(0.1) // if point selected, use opacity 0.99, otherwise 0.1
);

const crimeBars = vl.markBar()
.data(vanCrime2)
.params(selectType)
.title("Vancouver Non-Violent Crime Total Offenses - Summer 2016")
.transform(vl.filter(selectTimeBrush))
.encode(
vl.y().fieldN("Offense").scale({ domain: OffenseTypes }),
vl.x()
.fieldQ("*")
.aggregate("count")
.scale({ domain: [0, 4400] }),
vl.color()
.fieldN("Offense Category")
.scale({ range: OffenseCatColors })
.legend({ orient: "bottom", offset: -70 }),
vl.opacity().if(selectType, vl.value(0.99)).value(0.1) // if point selected, use opacity 0.7, otherwise 0.1
);
return vl.hconcat(crimeBars, crimeTrends).render();
}
Insert cell
Insert cell
{
//Bar chart offenses x non-violent trends by CATEGORY
// set the selection interactions to be able to select either an offense type, an offense category, or a time interval
const selectTimeBrush = vl.selectInterval().encodings("x");
// Use an OR operator on which time scrubber to use

const selectType = vl
.selectPoint("selected_pt")
.on("click") // select on click
.fields("Offense Category") // select all crimes of the same type
.bind("legend") // also bind to the legend
.clear("dblclick") // double click to clear selection
.toggle(true); // allow for multi-select (on by default) with shift-click

const selectDate = vl
.selectPoint("selected_date")
.on("click") // select on click
.fields("Date") // select all crimes of the same type
.nearest(true)
//.bind("legend") // also bind to the legend
.clear("dblclick") // double click to clear selection
.toggle(true); // allow for multi-select (on by default) with shift-click
//draw the charts

const crimeTrends = vl
.markLine({ strokeWidth: 2 })
.data(vanCrime2)
// .transform(vl.filter(selectType))
//.transform(vl.filter(selectTimeBrushCat))
//.width(600)
.params(selectTimeBrush, selectDate)
.title("Non-Violent Crime Trends by Category Summer 2016")
.encode(
vl.x().fieldT("Date"),
vl.detail().fieldN("Offense Category"),
vl
.y()
.fieldQ("*")
.count()
.scale({ domain: [0, 120] }),
vl.color().fieldN("Offense Category").scale({ range: OffenseCatColors }),
vl.opacity().if(selectType, vl.value(0.99)).value(0.1) // if point selected, use opacity 0.99, otherwise 0.1
);

const crimeBars = vl
.markBar()
//.width(600)
.data(vanCrime2)
.params(selectType)
.title("Vancouver Non-Violent Crime Total Offenses Summer 2016")
.transform(vl.filter(selectTimeBrush), vl.filter(selectDate))
.encode(
vl.y().fieldN("Offense").scale({ domain: OffenseTypes }),
vl.x().fieldQ("*").aggregate("count"),
// .scale({ domain: [0, 4400] }),
vl
.color()
.fieldN("Offense Category")
.scale({ range: OffenseCatColors })
.legend({ orient: "bottom", offset: -70 }),
// .if(selectedBar, vl.fieldN("Offense Category"))
// .value("grey"), // if point selected, use default color, otherwise grey
vl.opacity().if(selectType, vl.value(0.99)).value(0.1) // if point selected, use opacity 0.7, otherwise 0.1
);
// const base = crimeBars.transform(vl.filter(timeSelector));
return vl.hconcat(crimeBars, crimeTrends).render();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mapSelection
Insert cell
viewof CrimePoints = {
let width = 900;
let height = 400;
let container = DOM.element("div", {
style: `width:${width}px;height:${width / 2}px`
});

yield container;

let setCatClr = function(cn) {
let i=0;
for (i=0;i< OffenseCatClr.length; i++){
if (cn === OffenseCatClr[i].Oname){
return OffenseCatClr[i].CClr
}
}
return(null)
}

let map = L.map(container, { maxZoom: 16, minZoom: 12 });
let osmLayer = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
{
attribution:
'&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
opacity:1
}
).addTo(map);

let VanAreasLayer = L.geoJson(vanAreas, {
//instantiates a new geoJson layer using built in geoJson handling
weight: 2, //Attributes of polygons including the weight of boundaries and colors of map.
color: "#432",
fillColor: "#EEE",
fillOpacity: 0,
opacity: 1,
onEachFeature: areaClick
})
.bindPopup(function (Layer) {
//binds a popup when clicking on each polygon to access underlying data
return Layer.feature.properties.NAME;
})
.addTo(map); //Adds the layer to the map.

//IMPORTANT: NOTE THAT HERE WE USE FilteredGeoCrimes, which contains a filtered array of crimes based on the selections in Vega-Lite and Leaflet
let vCrimeLayer2 = L.geoJSON(FilteredGeoCrimes, {
style: function(feature) {
return {
color: setCatClr(feature.properties.Offense)
};
},
pointToLayer: function(feature, latlng) {
return new L.CircleMarker(latlng, {
radius: 3,
fillOpacity: 0.1,
//fillColor: setCatClr(feature.properties.Offense),
fillColor: setCatClr("purple"),
Opacity: 0.1
});
},onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.Offense);
}
});
map.addLayer(vCrimeLayer2);

//we also register a mouse event, so that when the map is clicked in an empty area, we set nHood to null. More on that below
map.on("click", () => (mutable nHood = null));

map.fitBounds(VanAreasLayer.getBounds());
}
Insert cell
<div style="
background: #fff;
display: grid;
// height: ${screen.height / screen.width * 100}vw;
grid-template-areas:
'a'
'b';
grid-gap: 10px;
">
<div name="a" style="grid-area:a;border:solid 1px #ccc;text-align: center">${viewof threeCViews}</div>
<div name="b" style="grid-area:b;border:solid 1px #ccc;text-align: center">${viewof CrimePoints}</div>
</div>
Insert cell
Insert cell
Insert cell
Insert cell
otype
Insert cell
bSelection
Insert cell
vanCrimeFiltered
Insert cell
FilteredGeoCrimes
Insert cell
Insert cell
Insert cell
Insert cell
{return "Neighbourhood selected: " + (nHood == null ? 'none' : nHood)}
Insert cell
mapSelection
Insert cell
vl.markCircle({size: 30})
.data(crimeDensityTotal)
.transform(
vl.filter({'and': [{'field': 'NEIGHBOURHOOD', 'oneOf': mapSelection}]})
)
.encode(
vl.x().fieldN('Offense').count().axis('Number of crimes').scale({type: 'log'}),
vl.y().fieldQ('sqm').axis({title: 'Square meters/person'}).scale({'domain': [0, 500]}),
vl.column().fieldN('Offense Type'),
vl.color().fieldN('Offense Category')
)
.width(300)
.title(selectedNeighbourhoods)
.render()

Insert cell
Insert cell
Insert cell
Insert cell
// clicked = Generators.observe((notify) => {
// const clicked = (event, { datum }) => notify(datum);
// threeCViews.addEventListener("click", clicked);
// return () => threeCViews.removeEventListener("click", clicked);
// })
Insert cell
Insert cell
// Here we add our signal listener
//selected_pt is the name of the VL selectPoint selection we defined in threeCViews

threeCViews.addSignalListener("selected_pt", function (name, value) {
if (value == null) mutable otype = null;
else mutable otype = value.Offense;
})
Insert cell
Insert cell
Insert cell
Insert cell
//This is some more backend stuff where we use a generator to observe changes in the event listener

mutable ObsDateSelection = Generators.observe(notify => { //creates generator that observes changes within the event listener.
const signaled = (name, value) => notify(value); //two arguments for keyvalue pair. Notifies when updated
threeCViews.addSignalListener("tbrush", signaled); //Adds signal listener to our selection
notify(threeCViews.signal("tbrush")); //notifies when signal exists
return () => threeCViews.removeSignalListener("timeBrush", signaled); // removes signal listener to clear
})
Insert cell
Insert cell
Insert cell
Insert cell
mutable nHoods = []
Insert cell
// mapSelection = {
// if(nHood == null ){
// return Array.from(new Set(vanCrime2.map(d => d.NEIGHBOURHOOD))) //Set is a new datastructure in ES6 -> only unique values
// } else {
// return [nHood]
// }
// }
Insert cell
vHoods = Array.from(new Set(vanCrime2.map((d) => d.NEIGHBOURHOOD)))
Insert cell
mapSelection = {
if (nHood == null) {
return vHoods; //Set is a new datastructure in ES6 -> only unique values
} else {
return [nHood];
}
}
Insert cell
mapSelections = {
if (nHood == null) {
mutable nHoods.length = 0; //remove all selections to date. reset selection array
return vHoods;
} else {
if (nHoods.indexOf(nHood) == -1)
//value is not in nHoods
mutable nHoods.push(nHood);
return nHoods;
} // add the selected neighbourhood
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
timeParse = d3.timeParse("%m/%d/%y")
Insert cell
dateFormat = d3.timeFormat("%m/%d/%y")
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
Insert cell
Insert cell
heatLayer = L, require('leaflet.heat').catch(() => L.heatLayer)
Insert cell
embed = require("vega-embed@6")
Insert cell
mutable selectedMap = ["warmgreys"]
Insert cell
Insert cell
vanCrime = FileAttachment("vanCrime.json").json()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
vanAreas = FileAttachment("VanAreas.json").json()
Insert cell
Insert cell
Insert cell
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