Public
Edited
Feb 26, 2024
2 forks
16 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
todayRaw = new Date();
Insert cell
Plot.plot({
marks: [
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reverse: true}}),
Plot.ruleY([0])
]
})
Insert cell
today = new Date().toISOString().slice(0, 10);
Insert cell
firstDayOfMonth = new Date(todayRaw.getFullYear(), todayRaw.getMonth(), 1).toISOString().slice(0, 10);
Insert cell
firstDayOfYear = new Date(todayRaw.getFullYear(), 0, 1).toISOString().slice(0, 10);
Insert cell
firstDateWorkout = d3.min(workouts, d=>d.created_at).toISOString().slice(0, 10);
Insert cell
startDate = timeFrame === "ITD" ? firstDateWorkout : timeFrame==="YTD" ? firstDayOfYear : firstDayOfMonth
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
max_diff = d3.max(byInstructor, d => d.avg_difficulty)
Insert cell
min_diff = d3.min(byInstructor, d => d.avg_difficulty)
Insert cell
Insert cell
Insert cell
Insert cell
function sparkbar(max) {
return x => htl.html`<div style="
background: #A0E4D1;
width: ${100 * x / max}%;
float: left;
padding-right: 3px;
box-sizing: border-box;
overflow: visible;
display: flex;
justify-content: start;">${x.toLocaleString("en")}`
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// Template credit: This layout is updated from Martien van Steenbergen @martien/horizontal-inputs

template = (inputs) =>
htl.html`<div class="styled">${Object.values(inputs)}</div>
<style>
div.styled {
text-align: left;
column-count: 2
}
div.styled label {
font-weight: bold;
line-height: 200%;
}
div.styled label:not(div>label):after {
content: ":";
}
</style>`
Insert cell
Insert cell
<style>
@import url('https://fonts.googleapis.com/css?family=Archivo&display=swap');
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css');

@media (max-width: 768px) {
.valueBoxes {
flex-direction: column;
align-items: center;
}

.valueBox {
width: 100%!important;
margin-bottom: 15px;
}


.charts {
flex-direction: column;
align-items: center;
height:auto!important;
}

.col2, .col1 {
width: 100%!important;
}

.chart, .table {
width: 97%!important;
margin-bottom:15px;
}
}
body, form, text {
font-family:Archivo!important;
}

.table th:first-child,
.table td:first-child {
display: none;
}

.table form {
margin:0px;
}

svg {
height:90%!important;
width:100%;
}

.col1, .col2{
width:49.25%;
height:inherit;
}

.col2{
display: flex;
flex-direction: column;
justify-content: space-between;
}

.col2.chart {
flex: 1;
}

td {
vertical-align:middle;
}

.charts {
display:flex;
justify-content:space-between;
height:440px;
}

.svg {
height:inherit!important;
}

.chart {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
height: 205px;
border-radius:5px;
padding-left: 10px;
padding-right:10px;
padding-top:5px;
padding-bottom:5px;
}

.table {
height:inherit;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
border-radius:5px;
padding-left: 10px;
padding-right:10px;
}


.charts span, .table span {
font-size:14px;
font-weight:bold;
}

form > label {
font-weight:bold;
}
h2, h1, p {
max-width:none;
}

.valueBoxes {
margin-top:15px;
display:flex;
width: 100%;
justify-content:space-between;
}
.valueBox {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
background-color: white;
color:#767676;
border-radius:5px;
width: 24%;
height:100px;
}

.valueBoxContent {
padding: 12px;
display:flex;
justify-content: space-between;
}


.value {
font-size:30px;
font-weight:bold;
color:black;
}


.icon {
align-self:center;
font-size:50px;
margin-right:10px;
color:#D3D3D3;
}
</style>
Insert cell
Insert cell
db
select
count(*) as workouts
,sum(ride_duration)/60/60 as hours
,count(distinct created_at) as active_days
,coalesce(round(sum(case when distance<>'NA' then distance::double end),0),0) as distance
from workouts
where ride_id<>'NA'
Insert cell
db
select
month::date as month_of
,coalesce(w.workouts,0) as workouts
,coalesce(w.active_days,0) as active_days
,coalesce(w.hours,0) as hours
from months m
left join(
select
date_trunc('month', created_at) as month_of
,count(*) as workouts
,count(distinct created_at) as active_days
,sum(ride_duration)/60/60 as hours
from workouts
where ride_id<>'NA'
group by 1
order by 1
) w on w.month_of = m.month::date
WHERE m.month::date >= ${filters.startDate}::date and m.month::date<= ${filters.endDate}::date
ORDER BY 1
Insert cell
db
select
fitness_discipline
,count(*) as workouts
,count(distinct created_at) as active_days
,sum(ride_duration)/60/60 as hours
from workouts
where ride_id<>'NA'
group by 1
order by 1
Insert cell
db
select
w.instructor_name
,i.image_url
--,round(avg(avg_difficulty),2) as avg_difficulty
,round(sum(avg_difficulty*ride_duration)/sum(ride_duration),2) as avg_difficulty
,count(*) as workouts
,count(distinct created_at) as active_days
,sum(ride_duration)/60 as minutes
from workouts w
left join instructors i on i.id = w.instructor_id
where ride_id<>'NA' and instructor_name <> 'NA'
group by 1,2
order by 6 desc
Insert cell
Insert cell
db = DuckDBClient.of({
workouts: filteredWorkouts,
instructors: instructors,
months : months
})
Insert cell
ref_instructors.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
peloton-data@14.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
filteredWorkouts = workouts.filter(w => filters.workoutType.includes(w.fitness_discipline) && w.created_at>=filters.startDate && w.created_at<=filters.endDate)
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