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

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