Published
Edited
Feb 20, 2021
3 forks
1 star
Insert cell
import {vl} from '@vega/vega-lite-api'
Insert cell
d3 = require('d3')
Insert cell
import {printTable} from '@uwdata/data-utilities'
Insert cell
fileContents = FileAttachment("rollercosters_paired_assignment@1.csv")
Insert cell
df1= d3.csvParse(await fileContents.text(), d3.autoType)
Insert cell
printTable(df1.slice(0, 10))
Insert cell
html`
<html>
<body>
<h2>
<span style="color:blue"> Questions?</span>
</h2>
<h4>

How many rollercoaster does each theme park have? </br></br> Based on my preferred levels of ride excitement, ride intensity, and tolerance for nausea, which theme parks and specific rollercoasters should I go? </br></br>When I go with a group of people, which theme parks accomodate the widest range of people's preferences or help the group make collective tradeoffs on rides?

</h4>
</html>
</body>
`
Insert cell
html`
<html>
<body>
<h2>
<span style="color:blue"> Theme Park by Total Number of Rollercoasters and Rating of Ride Intensity</span>
</h2>
<h4> Given my preference for a certain number of rollercoasters and ride intensity, how does the comparison look like by theme park? Which theme park can I visit?
</h4>
</html>
</body>
`
Insert cell
md`
First, we made the assumption that the visitor might be interested to get as much for the ticket price paid and time spent visiting a theme park. At first glance, the horizontal bar graph compares the maxima and minima of the number of total rollercoasters per theme park. Given each theme park is a categorical (nominal) data type, we used the virtual properties of position on common scale and length to make the comparison between theme parks visually discriminating. The secondary task is visitors can get a sense of the distribution of ride intensity per theme park based on their preferences after they see which theme park has the most or least rides, which is represented in varying colors of high to low within each bar.

This bar chart is a colored chart with rectangular bars with lengths proportional to the values that they represent, grouped by descending value and enclosed with clear top heading and intrepretative axis labels. The bar chart is colored in rectangular shapes and filled with varying colors against a white/grey layer of background grid to focus the visitor’s attention on the foreground layer. With spatial region and colored bars in the foreground against neutral-colored grid in the background, we applied principles of similarity, ground, proximity, and closure from Gestalt Principles.
`
Insert cell
viewof stackedBar2 = vl.markBar()
.data(df1)
.encode(
vl.x().count().title("Number of Rollercoasters"), // .stack("normalize").axis({ format: "%" }),
vl.y().fieldO("theme").title("Theme Park").sort("-x"),
vl.color().fieldN("intensity_rating").scale(colorMapping3).legend({orient: 'bottom-right'}),
vl.opacity().value(0.8)
)
.title('Theme Park by Total Number of Rollercoasters and Rating of Ride Intensity')
.render()
Insert cell
html`
<html>
<body>
<h2>
<span style="color:blue"> Theme park by ride ratings: excitement, intensity, and nausea</span>
</h2>
<h4> Given my preference for a certain excitement level, ride intensity, and tolerance for nausea, how does the comparison of excitment level, ride intensity, and nausea rating look like by theme park? Which rollercoasters can I favorite by id to express my intent to visit?
</h4>
</html>
</body>
`
Insert cell
md`
In the second design, we choose to use line graphs to represent the range of the excitement, intensity and nausea for each theme park with colored dots of min and max values. The goal is to give high-level overview of maxima and minima, and range of rating values for excitement, intensity, and nausea ratings that visitors can compare each theme park against, and further narrow their selection to a few theme park of interest. Knowing that each visitor might have different preferences, we aim to provide a holistic coverage of all three ratings, illustrated by different headings and color codes. Moreover, these graphs are grouped by theme park, so the visitor can decide to make a selection based on theme park or by ratings. We choose to use horizontally concatenate them so that users can compare the range of excitement, intensity, and nausea and choose the one which is most able to accomodate for a group of people with diverse interests.

We placed the ratings next to each other horizontally with equal treatment of spatial area because we hypothesize they are of equal relevance to the visitor. However, we placed the nausea rating on the right knowing that tolerance for ride nausea will be a point of dicussion as the visitor starts to read these graphs from the left position to the right.

Moreover, we used red marked thick line of existing ranking of very high/high/medium/low from the dataset to differentiate very high vs the rest for excitement rating, very high vs the rest for intensity rating, and high vs the rest for nausea rating so the visitor understands how values compare relatively across theme parks. We employed Gestalt principles of figure-ground, similarity, proximity, common region, closure, continuity, and focal point.
`
Insert cell
{
//"#90C5C1","#7094B7", "#E77979", "#F59C4F"
const point1 = vl.markPoint({color:"#90C5C1", point: {filled:true, size:50}}).encode(
vl
.x()
.max('excitement')
.axis({title: "Excitement", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme').title("Theme")
.axis({labelFontSize: 12})
);
const point2 = vl.markPoint({color:"#90C5C1", point: {filled:true, size:50}}).encode(
vl
.x()
.min('excitement')
.axis({title: "Excitement", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme')
.axis({labelFontSize: 12}),
);

const lines = vl.markRule().encode(
vl.x().min('excitement'),
vl.x2().max('excitement'),
//vl.x3().q3('excitement'),
vl
.y()
//.sort("x2-x")
.fieldN('theme'),
vl.color({ value: 'Grey' })
);

const point3 = vl.markPoint({color:"#7094B7", point: {filled:true, size:50}}).encode(
vl
.x()
.max('intensity')
.axis({title: "Intensity", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme').title(null)
.axis(null),
//.sort(sortOrder),
);
const point4 = vl.markPoint({color:"#7094B7", point: {filled:true, size:50}}).encode(
vl
.x()
.min('intensity')
.axis({title: "Intensity", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme')
.axis({labelFontSize: 12}),
//.sort(sortOrder),
);
const point5 = vl.markPoint({color:"#E77979", point: {filled:true, size:50}}).encode(
vl
.x()
.max('nausea')
.axis({title: "Nausea", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme').title(null)
.axis(null),
//.sort(sortOrder),
);
const point6 = vl.markPoint({color:"#E77979", point: {filled:true, size:50}}).encode(
vl
.x()
.min('nausea')
.axis({title: "Nausea", labelFontSize: 12, titleFontSize: 13}),
vl
.y()
.fieldN('theme')
.axis({labelFontSize: 12}),
//.sort(sortOrder),
);
const base = vl.markRule({stroke: 'firebrick',strokeWidth: 2, color: '#608BA6'})
.data([{'excitement': 6.6}])
.encode(
vl.x().fieldQ('excitement')
);
const base2 = vl.markRule({stroke: 'firebrick',strokeWidth: 2,color: '#608BA6'})
.data([{'intensity': 7.5}])
.encode(
vl.x().fieldQ('intensity')
);
const base3 = vl.markRule({stroke: 'firebrick',strokeWidth: 2,color: '#608BA6'})
.data([{'nausea': 5.16}])
.encode(
vl.x().fieldQ('nausea')
);
const line1 = vl.markRule().encode(
vl.x().min('intensity'),
vl.x2().max('intensity'),
// vl.x3().q3('intensity'),
vl
.y()
.fieldN('theme'),
vl.color({ value: 'Grey' })
);
const line2 = vl.markRule().encode(
vl.x().min('nausea'),
vl.x2().max('nausea'),
// vl.x3().q3('nausea'),
vl
.y()
.fieldN('theme'),
vl.color({ value: 'Grey' })
);
const nausealevel = vl
.layer(point5,point6,line2,base3)
.data(df1)
.width(350)
.height(550)
.title({
text: 'Range of ride nausea by theme park',
anchor: 'middle',
font: 'Tahoma',
fontSize: 14
})

const intensitylevel = vl
.layer(base2, point3,point4,line1)
.data(df1)
.width(350)
.height(550)
.title({
text: 'Range of ride intensity by theme park',
anchor: 'middle',
font: 'Tahoma',
fontSize: 14
})
const excitementlevel = vl
.layer(base, point1, lines, point2)
.data(df1)
.width(350)
.height(550)
.title({
text: 'Range of ride excitement by theme park',
anchor: 'middle',
font: 'Tahoma',
fontSize: 14
})
return vl.hconcat(excitementlevel, intensitylevel, nausealevel)
.title({
text: 'Comparison of Ride Excitement, Intensity, and Nausea by Theme Park',
anchor: 'middle',
fontSize: 18
})
.render();
}
Insert cell
html`
<html>
<body>
<h2>
<span style="color:blue"> Theme park by ride ratings: excitement, intensity, and nausea</span>
</h2>
<h4> Given my preference for a certain excitement level, ride intensity, and tolerance for nausea, how does the comparison of excitment level, ride intensity, and nausea rating look like by theme park? Which rollercoasters can I favorite by id to express my intent to visit?
</h4>
</html>
</body>
`
Insert cell
md`
In the third design, we choose to use scatter charts to map individual rollercoaster's excitement, intensity, and nausea level to complement earlier graphs yet with ability to make a selection not only on theme park level but also on a specific rollercoaster ride. At a glance, the visitor sees dots of rides in four quandrants using red marked thick line separated by existing ranking of very high vs the rest for excitement rating and very high vs the rest for intensity rating. And then, each ride is color coded by different nausea level. Although the visitor can read the maxima and minima values, and range of excitement and intensity values, it is a secondary task compared to the primary focus of nausea rating. We hypothesize people frequent theme parks in large groups of people and are likely to make a selection to ride only a handful of few rollercoasters out of all rollercoasters on-site; thus, they would like to focus on satisfying varying levels of tolerance for ride nausea and make tradeoffs collectively for which coasters to ride.


We choose the quantitative data types, as compared to the nominal data type, which provides more precise read of each rollercoaster's attributes. For each point, the x and y-axis measured the excitement and intensity correspondingly. To measure nausea, we use the color, green which represents high nausea, blue is the medium, and red is low. In addition, the points on the right of markRule on the x-axis represent those very high excitement rides. Similarly, the points above the markRule on the y axis represent those very high-intensity rides. By viewing the graph, users can choose the quadrant that they are interested in. If they find the specific rollercoaster that they are interested in, they can hover on the point which will provide them more information on the theme park, the coaster id, the type of the coaster, the maximum speed, the ride time, and ride length. This provides more information for viewers and for people who are interested to take on the ride, it helps them make better decisions. From the graph, we can also see the general trend with high excitement level corresponding to the high-intensity level of the rides.

We employed Gestalt principles of figure-ground, similarity, proximity, common region, closure, continuity, and focal point.
`
Insert cell
{
const colors = {
domain: ["High","Medium","Low"],
range: ["OrangeRed",'#F2BB13',"LightSeaGreen"]
};
const point = vl.markPoint({strokeWidth: 3, opacity: 1,point: {size: 100}})
.data(df1)
.encode(
vl.y().fieldQ("intensity").title('Intensity').axis({labelAngle: 0 }),
vl.x().fieldQ("excitement").title('Excitment'),
vl.color().fieldN("nausea_rating").title('Nausea Level').scale(colorMapping5).legend({orient: 'top-left'}),
vl.tooltip(["theme", "coaster_id","rollercoaster_type", "max_speed", "ride_time","ride_length"])
)
const base = vl.markRule({stroke: 'firebrick',strokeWidth: 2, color: '#608BA6'})
.data([{'excitement': 6.6}])
.encode(
vl.x().fieldQ('excitement')
);
const base2 = vl.markRule({stroke: 'firebrick',strokeWidth: 2,color: '#608BA6'})
.data([{'intensity': 7.5}])
.encode(
vl.y().fieldQ('intensity')
);

return vl.layer(point,base,base2)
.title('View map of theme park by ride ratings: excitement, intensity, and nausea')
.width(600)
.height(600)
.render();
}

Insert cell
colorMapping = ({
domain: ["Very High","High","Medium","Low"],
range: ["#3D6E0",'#28ACEA',"#09EBEE","#C9FEFE"]})
Insert cell
colorMapping2 = ({
domain: ["Low","Medium","High","Very High"],
range: ["005581","#72CDF4","#FFD200","#FDA300",]})
Insert cell
colorMapping3 = ({
domain: ["Very High", "High", "Medium", "Low"],
range: ["#90C5C1","#7094B7", "#E77979", "#F59C4F"]})
Insert cell
colorMapping4 = ({
range: ["#90C5C1","#7094B7", "#E77979", "#F59C4F"]})
Insert cell
colorMapping5 = ({
domain: ["High","Medium","Low"],
range: ["#90C5C1","#7094B7", "#E77979"]})
Insert cell
//Color:: "#90C5C1","#7094B7", "#E77979", "#F59C4F"
Insert cell
orderlegend = ({
domain: ["Very High", "High", "Medium", "Low"],
range: ["#90C5C1","#7094B7", "#E77979", "#F59C4F"]})
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