Published
Edited
Feb 23, 2020
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`## Load libraries, look at data`
Insert cell
md`Load in libraries`
Insert cell
import {vl} from '@vega/vega-lite-api'
Insert cell
d3 = require('d3')
Insert cell
import {printTable} from '@uwdata/data-utilities'
Insert cell
Insert cell
stats = d3.csvParse(await FileAttachment("cityfixitdata@2.csv").text(), d3.autoType)
Insert cell
md`Take a look at the first five records, using a nice formatter`
Insert cell
printTable(stats.slice(0,5))
Insert cell
md`Look at the end of the table with negative indexing`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//answer to Q1
vl.markLine()
.data(stats)
.encode(
vl.x().fieldO("Year"),
vl.y().fieldQ("NumRequests"),
vl.color().fieldN("ProblemType"))
.render()
Insert cell
Insert cell
// answer to q2
vl.markLine({strokeWidth: 4, opacity: 0.5, interpolate: 'monotone', point:{size:100}})
.data(stats)
.encode(
vl.x().fieldO("Year"),
vl.y().fieldQ("NumRequests"),
vl.color().fieldN("ProblemType"),
vl.size(100))
.height(600)
.width(300)
.render()
Insert cell
Insert cell
vl.markLine({strokeWidth: 3, opacity: 0.5, interpolate: 'monotone', point:{size:100}})
.data(stats)
.encode(
vl.x().fieldO("Year").axis({labelAngle:0}),
vl.y().fieldQ("NumRequests").title("Number of Requests"),
vl.color().fieldN("ProblemType"),
vl.shape().fieldN("ProblemType"))
.height(600)
.width(300)
.render()
Insert cell
Insert cell
repairsInOrder = [ 'IllegalDumping','BuildingMaintenance', 'Electrical', 'StreetSweeping', 'Graffiti',
'RoadRepair', 'Drainage', 'StreetLights','VegetationControl', 'Survey']
Insert cell
colorsInOrder = ['darkred', 'green', 'dodgerblue', 'orange', 'purple', 'gold', 'tomato', 'forestgreen', 'hotpink', 'grey']
Insert cell
shapesInOrder = ['circle', 'diamond', 'square' , 'triangle', 'triangle-up','circle', 'diamond', 'square' ,'triangle', 'triangle-up']
Insert cell
colorMapping = ({domain: repairsInOrder, range: colorsInOrder})

Insert cell
shapeMapping = ({domain: repairsInOrder, range: shapesInOrder})
Insert cell
vl
.markLine({strokeWidth: 4,opacity: 0.4, interpolate: 'monotone', point: { size: 100 }})
.data(stats)
.encode(
vl.x().fieldO('Year').axis({labelAngle: 0}),
vl.y().fieldQ('NumRequests').title('Number of Requests'),
vl.color().fieldN('ProblemType').scale(colorMapping),
vl.shape().fieldN('ProblemType').scale(shapeMapping)

)
.width(300)
.height(600)
.render()
Insert cell
Insert cell
vl
.markLine({strokeWidth: 4, opacity: 0.4, interpolate: 'monotone', point: { size: 100 }})
.data(stats)
.encode(
vl.x().fieldO('Year').axis({labelAngle: 0}),
vl.y().fieldQ('NumRequests').title('Number of Requests'),
vl.shape().fieldN('ProblemType').scale(shapeMapping).legend({orient: 'top-left'}),
vl.color().fieldN('ProblemType').scale(colorMapping).legend({orient: 'top-left'})
)
.width(300)
.height(600)
.title({text: 'By Year', fontSize: 16, font: "Tahoma" })
.resolve({legend: {orient: 'independent'}}) // line courtesy Ryan Liu
.render()
Insert cell
Insert cell
Insert cell
vl.markBar()
.encode(
vl.y().fieldO('Year'),
vl.x().sum('NumRequests'),
vl.color().fieldQ('Year').scale({scheme: 'greys'})
)
.height(40)
// can also be .facet({row: vl.fieldN('ProblemType')})
.facet({row: vl.row('ProblemType') })
.data(stats)
.render()
Insert cell
Insert cell
vl.markBar()
.encode(
vl.y().fieldO('Year').axis({title: null}),
vl.x().fieldQ('NumRequests').axis({labelAngle: 0, title: null}),
vl.count(),
vl.color().fieldQ('Year').legend(null).scale({scheme: 'greys'})
)
.height(40)
.facet({row: vl.row('ProblemType').header({labelAngle: 0, labelAlign: "left", title: null, labelFontStyle: "bold", labelFontSize: 12, labelFont: 'Tahoma'}).sort(repairsInOrder)})
.data(stats)
.title({text: 'By Problem Type', anchor: 'center', offset: 5, fontSize: 16, font: "Tahoma"})
.render()
Insert cell
Insert cell
{
const bars =
vl.markBar()
.encode(
vl.y().fieldO('Year').axis({title: null}),
vl.x().fieldQ('NumRequests').axis({labelAngle: 0, title: null}),
vl.count(),
vl.color().fieldQ('Year').legend(null).scale({scheme: 'greys'})
)
.height(40);
const barText =
vl.markText({"dx": 15})
.encode(
vl.x().fieldQ('NumRequests'),
vl.y().fieldO('Year'),
vl.text().fieldQ('NumRequests')
);
return vl.layer(bars, barText)
.height(500)
.facet({row: vl.row('ProblemType')
.header({labelAngle: 0, labelAlign: "left", title: null,
labelFontStyle: "bold", labelFontSize: 12, labelFont: 'Tahoma'})
.sort(repairsInOrder)})
.title({text: 'By Problem Type', anchor: 'center', offset: 5, fontSize: 16, font: "Tahoma"})
.data(stats)
.render();
}
Insert cell
md`## Put it all together

Now put the two charts side-by-side. I suggest that you output the code from each chart into a variable and then combine those in a new chart. (Note that the combined legend doesn't look right in the line chart; there is probably a way to fix this, but I haven't figured it out. I also haven't found a way to make a colored background for the title of the chart.)
`
Insert cell
{
const lines = vl
.markLine({strokeWidth: 4, opacity: 0.4, interpolate: 'monotone', point: { size: 100 }})
.data(stats)
.encode(
vl.x().fieldO('Year').axis({labelAngle: 0}),
vl.y().fieldQ('NumRequests').title('Number of Requests'),
vl.shape().fieldN('ProblemType').scale(shapeMapping).legend({orient: 'top-left'}),
vl.color().fieldN('ProblemType').scale(colorMapping).legend({orient: 'top-left'})
)
.width(300)
.height(600)
.title({text: 'By Year', fontSize: 16, font: "Tahoma" })
.resolve({legend: {orient: 'independent'}}) // line courtesy Ryan Liu

const bars =
vl.markBar()
.encode(
vl.y().fieldO('Year').axis({title: null}),
vl.x().fieldQ('NumRequests').axis({labelAngle: 0, title: null}),
vl.count(),
vl.color().fieldQ('Year').legend(null).scale({scheme: 'greys'})
)
.height(40);
const barText =
vl.markText({"dx": 15})
.encode(
vl.x().fieldQ('NumRequests'),
vl.y().fieldO('Year'),
vl.text().fieldQ('NumRequests')
);
// the fix with resolve() on scale color by student Ryan Liu
const groupedBars = vl.layer(bars, barText)
.height(500)
.facet({row: vl.row('ProblemType')
.header({labelAngle: 0, labelAlign: "left", title: null,
labelFontStyle: "bold", labelFontSize: 12, labelFont: 'Tahoma'})
.sort(repairsInOrder)})
.data(stats)
.title({text: 'By Problem Type', anchor: 'center', offset: 5, fontSize: 16, font: "Tahoma"})
.resolve({scale: {color: 'independent'}}) // this line courtesy Ryan Liu
return vl.hconcat(lines, groupedBars)
.title({
text: 'City of Springfield Service Request Statistics (2009-2012)',
dx: 150, dy: -10,fontSize: 20, font: "Tahoma"})
.render()
}
Insert cell


Insert cell
Insert cell
{
const lineGraphs =
vl
.markLine({strokeWidth: 4, opacity: 0.4, interpolate: 'monotone', point: { size: 100 }})
.data(stats)
.encode(
vl.x().fieldO('Year').axis({labelAngle: 0}),
vl.y().fieldQ('NumRequests').title('Number of Requests'),
vl.shape().fieldN('ProblemType').scale(shapeMapping).legend({orient: 'top-left'}),
vl.color().fieldN('ProblemType').scale(colorMapping).legend({orient: 'top-left'})
)
.width(300)
.height(600)
.title({text: 'By Year', fontSize: 16, font: "Tahoma" })
.resolve({legend: {orient: 'independent'}});
// The trick here is to use the x and the x2 fields. We encode the NumSeeClickFix values in the first part of the bars, and the NumRequests fill in the rest.`
const bars1 = vl.markBar()
.encode(
vl.y().fieldO('Year').axis({labelAngle: 0, gridOpacity: 0.0}),
vl.x().fieldQ('NumSeeClickFix'),
vl.color({"value": "forestgreen"})
)
.height(40);
const bars2 = vl.markBar()
.encode(
vl.y().fieldO('Year').axis({labelAngle: 0, gridOpacity: 0.0, title: null}),
vl.x().fieldQ('NumSeeClickFix'),
vl.x2().field('NumRequests'),
vl.color().fieldQ('Year').legend(null).scale({ scheme: 'greys' })
)
.height(40);
const barText = vl.markText({"dx": 15})
.encode(
vl.x().fieldQ('NumRequests').stack(null),
vl.y().fieldO('Year'),
vl.text().fieldQ('NumRequests')
);

const groupedBars = vl.layer(bars1, bars2, barText)
.height(500)
// both of these approaches work
// .facet({row: vl.fieldN('ProblemType')})
// .config({header: {labelAngle: 0, labelAlign: "left", title: null}})
.facet({row: vl.row('ProblemType').header({labelAngle: 0, labelAlign: "left", title: null, labelFontStyle: "bold", labelFontSize: 12}).sort(repairsInOrder)})
.title({text: 'By Problem Type', anchor: 'center', offset: 5, fontSize: 16, font: "Tahoma"})
.resolve({scale: {color: 'independent'}})
.data(stats)
.transform(vl.calculate('datum.NumRequests * (datum.ReportedBySeeClickFix*.01)').as('NumSeeClickFix'));

return vl
.hconcat(lineGraphs, groupedBars)
.title({
text: 'City of Springfield Service Request Statistics (2009-2012)',
dx: 150, dy: -10,fontSize: 20, font: "Tahoma"})
.render()
}
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