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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more