diff --git a/packages/web-deploy/json/month-calendar-backup.idoc.json b/packages/web-deploy/json/month-calendar-backup.idoc.json new file mode 100644 index 00000000..c3dcc52e --- /dev/null +++ b/packages/web-deploy/json/month-calendar-backup.idoc.json @@ -0,0 +1,1278 @@ +{ + "$schema": "../../../docs/schema/idoc_v1.json", + "title": "Month View Events Calendar", + "style": { + "css": [ + "body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #f5f7fa; }", + ".container { max-width: 1400px; margin: 0 auto; }", + "h1 { text-align: center; color: #2c3e50; margin-bottom: 10px; }", + ".subtitle { text-align: center; color: #7f8c8d; margin-bottom: 30px; }", + ".controls { display: flex; justify-content: center; gap: 20px; margin: 20px 0; align-items: center; flex-wrap: wrap; }", + ".controls label { font-weight: 600; margin-right: 8px; }", + ".controls select { padding: 8px 15px; font-size: 1em; border: 2px solid #3498db; border-radius: 6px; background: white; cursor: pointer; }", + ".month-title { text-align: center; color: #2c3e50; font-size: 2em; margin: 20px 0; font-weight: 600; }", + ".calendar-wrapper { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }", + ".calendar-table { width: 100%; border-collapse: collapse; table-layout: fixed; }", + ".calendar-table th { background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); color: white; padding: 15px 8px; text-align: center; font-weight: 600; font-size: 1em; border: 1px solid #2980b9; }", + ".calendar-table td { border: 1px solid #ddd; padding: 8px 6px; vertical-align: top; min-height: 110px; height: 110px; background: white; position: relative; }", + ".calendar-table td.other-month { background: #f8f9fa; }", + ".calendar-table td.other-month .day-number { color: #bbb; }", + ".calendar-table td.weekend { background: #fffaf0; }", + ".calendar-table td.other-month.weekend { background: #f5f5f5; }", + ".day-number { font-weight: 700; color: #2c3e50; margin-bottom: 6px; font-size: 1.1em; display: block; }", + ".event-badge { background: #3498db; color: white; padding: 3px 6px; margin: 2px 0; border-radius: 3px; font-size: 0.75em; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; transition: all 0.2s; }", + ".event-badge:hover { transform: translateX(2px); opacity: 0.9; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }", + ".event-badge.work { background: #e74c3c; }", + ".event-badge.personal { background: #9b59b6; }", + ".event-badge.meeting { background: #f39c12; }", + ".event-badge.holiday { background: #1abc9c; font-weight: 600; }", + ".event-time { font-weight: 600; margin-right: 4px; }", + ".legend { text-align: center; margin-top: 25px; padding: 15px; background: #f8f9fa; border-radius: 8px; }", + ".legend-items { display: flex; justify-content: center; gap: 25px; margin-top: 10px; flex-wrap: wrap; }", + ".legend-item { display: flex; align-items: center; gap: 8px; font-size: 0.95em; }", + ".legend-box { width: 18px; height: 18px; border-radius: 3px; }", + "@media (max-width: 768px) { .calendar-table td { height: 90px; min-height: 90px; padding: 5px 4px; font-size: 0.9em; } .event-badge { font-size: 0.7em; padding: 2px 4px; } .day-number { font-size: 1em; } }" + ] + }, + "dataLoaders": [ + { + "dataSourceName": "eventsRaw", + "type": "inline", + "format": "json", + "content": [ + { + "date": "2025-01-01", + "time": "All Day", + "title": "New Year's Day", + "category": "holiday" + }, + { + "date": "2025-01-06", + "time": "09:00", + "title": "Team Standup", + "category": "meeting" + }, + { + "date": "2025-01-06", + "time": "14:00", + "title": "Project Review", + "category": "work" + }, + { + "date": "2025-01-08", + "time": "10:30", + "title": "Client Meeting", + "category": "meeting" + }, + { + "date": "2025-01-10", + "time": "15:00", + "title": "Dentist", + "category": "personal" + }, + { + "date": "2025-01-13", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-13", + "time": "11:00", + "title": "Design Review", + "category": "work" + }, + { + "date": "2025-01-15", + "time": "13:00", + "title": "Lunch w/ Sarah", + "category": "personal" + }, + { + "date": "2025-01-17", + "time": "16:00", + "title": "Code Review", + "category": "work" + }, + { + "date": "2025-01-20", + "time": "All Day", + "title": "MLK Jr. Day", + "category": "holiday" + }, + { + "date": "2025-01-20", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-22", + "time": "10:00", + "title": "Sprint Planning", + "category": "meeting" + }, + { + "date": "2025-01-22", + "time": "14:00", + "title": "Feature Demo", + "category": "work" + }, + { + "date": "2025-01-24", + "time": "18:00", + "title": "Gym", + "category": "personal" + }, + { + "date": "2025-01-27", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-27", + "time": "13:00", + "title": "Budget Review", + "category": "work" + }, + { + "date": "2025-01-29", + "time": "11:00", + "title": "1-on-1", + "category": "meeting" + }, + { + "date": "2025-01-31", + "time": "15:00", + "title": "Month End", + "category": "work" + }, + { + "date": "2025-02-03", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-05", + "time": "10:00", + "title": "Q1 Planning", + "category": "meeting" + }, + { + "date": "2025-02-07", + "time": "14:00", + "title": "Training", + "category": "work" + }, + { + "date": "2025-02-10", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-12", + "time": "12:00", + "title": "Team Lunch", + "category": "personal" + }, + { + "date": "2025-02-14", + "time": "All Day", + "title": "Valentine's Day", + "category": "holiday" + }, + { + "date": "2025-02-14", + "time": "19:00", + "title": "Dinner Date", + "category": "personal" + }, + { + "date": "2025-02-17", + "time": "All Day", + "title": "Presidents' Day", + "category": "holiday" + }, + { + "date": "2025-02-17", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-19", + "time": "10:30", + "title": "Arch Review", + "category": "work" + }, + { + "date": "2025-02-21", + "time": "15:00", + "title": "Doctor Appt", + "category": "personal" + }, + { + "date": "2025-02-24", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-26", + "time": "11:00", + "title": "Product Launch", + "category": "work" + }, + { + "date": "2025-02-28", + "time": "14:00", + "title": "Sprint Retro", + "category": "meeting" + } + ] + } + ], + "variables": [ + { + "variableId": "selectedYear", + "type": "number", + "initialValue": 2025 + }, + { + "variableId": "selectedMonth", + "type": "number", + "initialValue": 1 + }, + { + "variableId": "monthName", + "type": "string", + "initialValue": "January", + "calculation": { + "vegaExpression": "selectedMonth === 1 ? 'January' : selectedMonth === 2 ? 'February' : selectedMonth === 3 ? 'March' : selectedMonth === 4 ? 'April' : selectedMonth === 5 ? 'May' : selectedMonth === 6 ? 'June' : selectedMonth === 7 ? 'July' : selectedMonth === 8 ? 'August' : selectedMonth === 9 ? 'September' : selectedMonth === 10 ? 'October' : selectedMonth === 11 ? 'November' : 'December'" + } + }, + { + "variableId": "eventsGrouped", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "eventsRaw" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "groupby": [ + "date" + ], + "ops": [ + "values", + "values", + "values" + ], + "fields": [ + "time", + "title", + "category" + ], + "as": [ + "times", + "titles", + "categories" + ] + } + ] + } + }, + { + "variableId": "allDaysOfMonth", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "eventsRaw" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": ["count"], + "as": ["count"] + }, + { + "type": "sequence", + "start": 1, + "stop": 32, + "as": "dayOfMonth" + }, + { + "type": "formula", + "as": "daysInMonth", + "expr": "selectedMonth === 2 ? (selectedYear % 4 === 0 && (selectedYear % 100 !== 0 || selectedYear % 400 === 0) ? 29 : 28) : (selectedMonth === 4 || selectedMonth === 6 || selectedMonth === 9 || selectedMonth === 11) ? 30 : 31" + }, + { + "type": "filter", + "expr": "datum.dayOfMonth <= datum.daysInMonth" + }, + { + "type": "formula", + "as": "date", + "expr": "datetime(selectedYear, selectedMonth - 1, datum.dayOfMonth)" + }, + { + "type": "formula", + "as": "dateKey", + "expr": "selectedYear + '-' + (selectedMonth < 10 ? '0' + selectedMonth : selectedMonth) + '-' + (datum.dayOfMonth < 10 ? '0' + datum.dayOfMonth : datum.dayOfMonth)" + }, + { + "type": "formula", + "as": "weekday", + "expr": "day(datum.date)" + }, + { + "type": "formula", + "as": "firstDayWeekday", + "expr": "day(datetime(selectedYear, selectedMonth - 1, 1))" + }, + { + "type": "formula", + "as": "week", + "expr": "floor((datum.dayOfMonth + datum.firstDayWeekday - 1) / 7)" + } + ] + } + }, + { + "variableId": "daysWithEvents", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "allDaysOfMonth", + "eventsGrouped" + ], + "dataFrameTransformations": [ + { + "type": "lookup", + "from": "eventsGrouped", + "key": "date", + "fields": [ + "dateKey" + ], + "values": [ + "times", + "titles", + "categories" + ], + "as": [ + "eventTimes", + "eventTitles", + "eventCategories" + ] + }, + { + "type": "formula", + "as": "events", + "expr": "datum.eventTimes == null ? [] : datum.eventTimes" + }, + { + "type": "formula", + "as": "eventCount", + "expr": "datum.events.length" + } + ] + } + }, + { + "variableId": "paddedDays", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "daysWithEvents" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": ["count"], + "as": ["count"] + }, + { + "type": "sequence", + "start": 0, + "stop": 42, + "as": "cellIdx" + }, + { + "type": "formula", + "as": "firstDayWeekday", + "expr": "day(datetime(selectedYear, selectedMonth - 1, 1))" + }, + { + "type": "formula", + "as": "daysInMonth", + "expr": "selectedMonth === 2 ? (selectedYear % 4 === 0 && (selectedYear % 100 !== 0 || selectedYear % 400 === 0) ? 29 : 28) : (selectedMonth === 4 || selectedMonth === 6 || selectedMonth === 9 || selectedMonth === 11) ? 30 : 31" + }, + { + "type": "formula", + "as": "prevMonth", + "expr": "selectedMonth === 1 ? 12 : selectedMonth - 1" + }, + { + "type": "formula", + "as": "prevMonthYear", + "expr": "selectedMonth === 1 ? selectedYear - 1 : selectedYear" + }, + { + "type": "formula", + "as": "prevMonthDays", + "expr": "datum.prevMonth === 2 ? (datum.prevMonthYear % 4 === 0 && (datum.prevMonthYear % 100 !== 0 || datum.prevMonthYear % 400 === 0) ? 29 : 28) : (datum.prevMonth === 4 || datum.prevMonth === 6 || datum.prevMonth === 9 || datum.prevMonth === 11) ? 30 : 31" + }, + { + "type": "formula", + "as": "cellDay", + "expr": "datum.cellIdx < datum.firstDayWeekday ? datum.prevMonthDays - datum.firstDayWeekday + datum.cellIdx + 1 : datum.cellIdx >= datum.firstDayWeekday + datum.daysInMonth ? datum.cellIdx - datum.firstDayWeekday - datum.daysInMonth + 1 : datum.cellIdx - datum.firstDayWeekday + 1" + }, + { + "type": "formula", + "as": "isCurrentMonth", + "expr": "datum.cellIdx >= datum.firstDayWeekday && datum.cellIdx < datum.firstDayWeekday + datum.daysInMonth" + }, + { + "type": "formula", + "as": "isWeekend", + "expr": "datum.cellIdx % 7 === 0 || datum.cellIdx % 7 === 6" + }, + { + "type": "formula", + "as": "lookupKey", + "expr": "datum.isCurrentMonth ? (selectedYear + '-' + (selectedMonth < 10 ? '0' + selectedMonth : selectedMonth) + '-' + (datum.cellDay < 10 ? '0' + datum.cellDay : datum.cellDay)) : null" + }, + { + "type": "lookup", + "from": "daysWithEvents", + "key": "dateKey", + "fields": [ + "lookupKey" + ], + "values": [ + "eventTimes", + "eventTitles", + "eventCategories", + "eventCount" + ], + "as": [ + "cellEventTimes", + "cellEventTitles", + "cellEventCats", + "cellEventCount" + ] + }, + { + "type": "formula", + "as": "event1", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 0 ? datum.cellEventTimes[0] + ' ' + datum.cellEventTitles[0] : null" + }, + { + "type": "formula", + "as": "event1Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 0 ? datum.cellEventCats[0] : null" + }, + { + "type": "formula", + "as": "event2", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 1 ? datum.cellEventTimes[1] + ' ' + datum.cellEventTitles[1] : null" + }, + { + "type": "formula", + "as": "event2Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 1 ? datum.cellEventCats[1] : null" + }, + { + "type": "formula", + "as": "event3", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 2 ? datum.cellEventTimes[2] + ' ' + datum.cellEventTitles[2] : null" + }, + { + "type": "formula", + "as": "event3Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 2 ? datum.cellEventCats[2] : null" + }, + { + "type": "formula", + "as": "moreEvents", + "expr": "datum.cellEventCount && datum.cellEventCount > 3 ? '+' + (datum.cellEventCount - 3) + ' more' : null" + }, + { + "type": "filter", + "expr": "datum.cellIdx < 42" + } + ] + } + }, + { + "variableId": "calendarWeeks", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "paddedDays" + ], + "dataFrameTransformations": [ + { + "type": "formula", + "as": "weekNum", + "expr": "floor(datum.cellIdx / 7)" + }, + { + "type": "formula", + "as": "dayOfWeek", + "expr": "datum.cellIdx % 7" + }, + { + "type": "formula", + "as": "dayName", + "expr": "datum.dayOfWeek === 0 ? 'Sun' : datum.dayOfWeek === 1 ? 'Mon' : datum.dayOfWeek === 2 ? 'Tue' : datum.dayOfWeek === 3 ? 'Wed' : datum.dayOfWeek === 4 ? 'Thu' : datum.dayOfWeek === 5 ? 'Fri' : 'Sat'" + }, + { + "type": "formula", + "as": "cellDayStr", + "expr": "'' + datum.cellDay" + }, + { + "type": "pivot", + "groupby": ["weekNum"], + "field": "dayName", + "value": "cellDayStr" + } + ] + } + } + ], + "groups": [ + { + "groupId": "header", + "elements": [ + "# 📅 Month View Events Calendar", + "Track your events, meetings, and important dates", + "", + "This example demonstrates how to shape data for a calendar view using Vega transforms. Scroll down to see each step of the data transformation pipeline.", + { + "type": "dropdown", + "variableId": "selectedMonth", + "label": "Month:", + "options": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] + }, + { + "type": "dropdown", + "variableId": "selectedYear", + "label": "Year:", + "options": [ + "2024", + "2025", + "2026" + ] + } + ] + }, + { + "groupId": "step1", + "elements": [ + "## Step 1: Raw Events Data", + "This is the source data - a flat list of events with dates, times, titles, and categories.", + { + "type": "tabulator", + "dataSourceName": "eventsRaw", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "300px", + "pagination": "local", + "paginationSize": 10, + "columns": [ + {"title": "Date", "field": "date", "width": 120}, + {"title": "Time", "field": "time", "width": 100}, + {"title": "Title", "field": "title"}, + {"title": "Category", "field": "category", "width": 100} + ] + } + } + ] + }, + { + "groupId": "step2", + "elements": [ + "## Step 2: Events Grouped by Date", + "Using `aggregate` transform to group multiple events per day into arrays. Each row now represents one date with arrays of times, titles, and categories.", + { + "type": "tabulator", + "dataSourceName": "eventsGrouped", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "300px", + "pagination": "local", + "paginationSize": 10, + "columns": [ + {"title": "Date", "field": "date", "width": 120}, + {"title": "Times", "field": "times", "formatter": "textarea"}, + {"title": "Titles", "field": "titles", "formatter": "textarea"}, + {"title": "Categories", "field": "categories", "formatter": "textarea"} + ] + } + } + ] + }, + { + "groupId": "step3", + "elements": [ + "## Step 3: All Days of Month", + "Using `sequence` transform to generate all days in the selected month (not just days with events). Computed weekday and week for calendar positioning.", + { + "type": "tabulator", + "dataSourceName": "allDaysOfMonth", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "300px", + "pagination": "local", + "paginationSize": 15, + "columns": [ + {"title": "Day", "field": "dayOfMonth", "width": 80}, + {"title": "Date Key", "field": "dateKey", "width": 120}, + {"title": "Weekday", "field": "weekday", "width": 100}, + {"title": "Week", "field": "week", "width": 80} + ] + } + } + ] + }, + { + "groupId": "step4", + "elements": [ + "## Step 4: Days with Events Joined", + "Using `lookup` transform to join events arrays to each day. Days without events have empty arrays.", + { + "type": "tabulator", + "dataSourceName": "daysWithEvents", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "300px", + "pagination": "local", + "paginationSize": 15, + "columns": [ + {"title": "Day", "field": "dayOfMonth", "width": 80}, + {"title": "Date", "field": "dateKey", "width": 120}, + {"title": "Event Count", "field": "eventCount", "width": 100}, + {"title": "Event Times", "field": "eventTimes", "formatter": "textarea"}, + {"title": "Event Titles", "field": "eventTitles", "formatter": "textarea"} + ] + } + } + ] + }, + { + "groupId": "step4b", + "elements": [ + "## Step 4b: Padded 42-Cell Grid", + "Expanded to 42 cells (6 weeks × 7 days) including previous/next month days for padding. Each cell has its day number, events, and metadata.", + { + "type": "tabulator", + "dataSourceName": "paddedDays", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "350px", + "pagination": "local", + "paginationSize": 15, + "columns": [ + {"title": "Cell #", "field": "cellIdx", "width": 70}, + {"title": "Day", "field": "cellDay", "width": 60}, + {"title": "Current Month?", "field": "isCurrentMonth", "width": 120, "formatter": "tickCross"}, + {"title": "Weekend?", "field": "isWeekend", "width": 100, "formatter": "tickCross"}, + {"title": "Event Count", "field": "cellEventCount", "width": 100}, + {"title": "Event 1", "field": "event1"}, + {"title": "Event 2", "field": "event2"} + ] + } + } + ] + }, + { + "groupId": "step5", + "elements": [ + "## Step 5: Calendar Grid by Week", + "Final step groups the 42 cells into 6 weeks with 7 columns (Sun-Sat). Each row represents one week of the calendar.", + { + "type": "tabulator", + "dataSourceName": "calendarWeeks", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "400px", + "columns": [ + {"title": "Week", "field": "weekNum", "width": 60}, + {"title": "Sun", "field": "Sun"}, + {"title": "Mon", "field": "Mon"}, + {"title": "Tue", "field": "Tue"}, + {"title": "Wed", "field": "Wed"}, + {"title": "Thu", "field": "Thu"}, + {"title": "Fri", "field": "Fri"}, + {"title": "Sat", "field": "Sat"} + ] + } + } + ] + }, + { + "groupId": "calendar", + "elements": [ + "## {{monthName}} {{selectedYear}} - Final Calendar View", + { + "type": "treebark", + "variableId": "paddedDays", + "template": { + "div": { + "class": "calendar-wrapper", + "$children": [ + { + "table": { + "class": "calendar-table", + "$children": [ + { + "thead": { + "$children": [ + { + "tr": { + "$children": [ + { + "th": "Sun" + }, + { + "th": "Mon" + }, + { + "th": "Tue" + }, + { + "th": "Wed" + }, + { + "th": "Thu" + }, + { + "th": "Fri" + }, + { + "th": "Sat" + } + ] + } + } + ] + } + }, + { + "tbody": { + "$children": [ + { + "tr": { + "$bind": ".[0:7]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[7:14]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[14:21]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[21:28]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[28:35]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[35:42]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "span": { + "class": "day-number", + "$children": [ + "{{cellDay}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "span": { + "class": "event-badge {{event1Cat}}", + "$children": [ + "{{event1}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event2", + "$then": { + "span": { + "class": "event-badge {{event2Cat}}", + "$children": [ + "{{event2}}" + ] + } + } + } + }, + { + "$if": { + "$check": "event3", + "$then": { + "span": { + "class": "event-badge {{event3Cat}}", + "$children": [ + "{{event3}}" + ] + } + } + } + }, + { + "$if": { + "$check": "moreEvents", + "$then": { + "span": { + "class": "event-badge", + "style": { + "background": "#95a5a6", + "font-size": "0.7em" + }, + "$children": [ + "{{moreEvents}}" + ] + } + } + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + }, + "### Event Legend", + "🟠 **Meeting** • 🔴 **Work** • 🟣 **Personal** • 🟢 **Holiday**", + "", + "**Data Shaping:** This calendar uses Vega transforms to create a complete calendar grid (42 cells) and join events onto it. Each cell can display up to 3 events, with an indicator for additional events." + ] + } + ] +} diff --git a/packages/web-deploy/json/month-calendar-core.idoc.json b/packages/web-deploy/json/month-calendar-core.idoc.json new file mode 100644 index 00000000..2deab9b2 --- /dev/null +++ b/packages/web-deploy/json/month-calendar-core.idoc.json @@ -0,0 +1,282 @@ +{ + "$schema": "../../../docs/schema/idoc_v1.json", + "title": "Month View Calendar", + "variables": [ + { + "variableId": "selectedYear", + "type": "number", + "initialValue": 2025 + }, + { + "variableId": "selectedMonth", + "type": "number", + "initialValue": 2 + }, + { + "variableId": "calendarMonthList", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [], + "dataFrameTransformations": [ + { + "type": "sequence", + "start": -7, + "stop": 38, + "as": "dayOffset" + }, + { + "type": "formula", + "as": "selectedDate", + "expr": "datetime(selectedYear, selectedMonth - 1)" + }, + { + "type": "formula", + "as": "date", + "expr": "timeOffset('day', datum.selectedDate, datum.dayOffset)" + }, + { + "type": "formula", + "expr": "day(datum.selectedDate)", + "as": "firstWeekdayOffset" + }, + { + "type": "formula", + "as": "dayOfMonth", + "expr": "date(datum.date)" + }, + { + "type": "formula", + "as": "year", + "expr": "year(datum.date)" + }, + { + "type": "formula", + "as": "weekday", + "expr": "day(datum.date)" + }, + { + "type": "formula", + "as": "isSunday", + "expr": "datum.weekday === 0 ? 1 : 0" + }, + { + "type": "window", + "ops": [ + "sum" + ], + "fields": [ + "isSunday" + ], + "as": [ + "sundayCount" + ], + "frame": [ + null, + 0 + ] + }, + { + "type": "formula", + "as": "inCurrentMonth", + "expr": "month(datum.date) === selectedMonth - 1" + }, + { + "type": "formula", + "as": "precedingWeek", + "expr": "datum.weekday - datum.firstWeekdayOffset > datum.dayOffset" + }, + { + "type": "formula", + "as": "nextMonth", + "expr": "datum.dayOfMonth < 32 && datum.dayOffset > 0 && !datum.inCurrentMonth" + }, + { + "type": "formula", + "expr": "datum.nextMonth && datum.weekday === 0", + "as": "succeedingSunday" + }, + { + "type": "window", + "ops": [ + "sum" + ], + "fields": [ + "succeedingSunday" + ], + "as": [ + "succeedingWeek" + ], + "frame": [ + null, + 0 + ] + }, + { + "type": "filter", + "expr": "!datum.precedingWeek" + }, + { + "type": "filter", + "expr": "!datum.succeedingWeek" + } + ] + } + }, + { + "variableId": "calendarMonthByWeek", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "calendarMonthList" + ], + "dataFrameTransformations": [ + { + "type": "nest", + "keys": [ + "sundayCount", + "weekday" + ], + "generate": true + } + ] + } + } + ], + "groups": [ + { + "groupId": "header", + "elements": [ + "# \ud83d\udcc5 Month Calendar", + "", + "This example demonstrates how to shape data for a calendar view using Vega transforms. Scroll down to see each step of the data transformation pipeline.", + { + "type": "dropdown", + "variableId": "selectedMonth", + "label": "Month:", + "options": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] + }, + { + "type": "dropdown", + "variableId": "selectedYear", + "label": "Year:", + "options": [ + "2024", + "2025", + "2026" + ] + } + ] + }, + { + "groupId": "step1", + "elements": [ + "## Step 1: All Days of Calendar Surrounding the Selected Month", + "Using `sequence` transform to generate all days in the selected month (7 days before and 7 after, then filtered to show Sunday to Saturday).", + { + "type": "tabulator", + "dataSourceName": "calendarMonthList", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px" + } + } + ] + }, + { + "groupId": "calendar", + "elements": [ + "## Step 2: Calendar View (Treebark / HTML table)", + { + "type": "treebark", + "variableId": "calendarMonthByWeek", + "template": { + "div": [ + { + "table": [ + { + "thead": [ + { + "tr": [ + { + "th": "Sun" + }, + { + "th": "Mon" + }, + { + "th": "Tue" + }, + { + "th": "Wed" + }, + { + "th": "Thu" + }, + { + "th": "Fri" + }, + { + "th": "Sat" + } + ] + } + ] + }, + { + "tbody": { + "$bind": "root.children", + "$children": [ + { + "tr": { + "$bind": "children", + "$children": [ + { + "td": { + "$bind": "data.values", + "$children": [ + { + "$if": { + "$check": "inCurrentMonth", + "$then": { + "div": [ + "{{dayOfMonth}}" + ] + } + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json new file mode 100644 index 00000000..788b893c --- /dev/null +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -0,0 +1,751 @@ +{ + "$schema": "../../../docs/schema/idoc_v1.json", + "title": "Month View Events Calendar", + "dataLoaders": [ + { + "dataSourceName": "eventsRaw", + "type": "inline", + "format": "json", + "content": [ + { + "date": "2025-01-01", + "time": "All Day", + "title": "New Year's Day", + "category": "holiday" + }, + { + "date": "2025-01-06", + "time": "09:00", + "title": "Team Standup", + "category": "meeting" + }, + { + "date": "2025-01-06", + "time": "14:00", + "title": "Project Review", + "category": "work" + }, + { + "date": "2025-01-08", + "time": "10:30", + "title": "Client Meeting", + "category": "meeting" + }, + { + "date": "2025-01-10", + "time": "15:00", + "title": "Dentist", + "category": "personal" + }, + { + "date": "2025-01-13", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-13", + "time": "11:00", + "title": "Design Review", + "category": "work" + }, + { + "date": "2025-01-15", + "time": "13:00", + "title": "Lunch w/ Sarah", + "category": "personal" + }, + { + "date": "2025-01-17", + "time": "16:00", + "title": "Code Review", + "category": "work" + }, + { + "date": "2025-01-20", + "time": "All Day", + "title": "MLK Jr. Day", + "category": "holiday" + }, + { + "date": "2025-01-20", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-22", + "time": "10:00", + "title": "Sprint Planning", + "category": "meeting" + }, + { + "date": "2025-01-22", + "time": "14:00", + "title": "Feature Demo", + "category": "work" + }, + { + "date": "2025-01-24", + "time": "18:00", + "title": "Gym", + "category": "personal" + }, + { + "date": "2025-01-27", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-01-27", + "time": "13:00", + "title": "Budget Review", + "category": "work" + }, + { + "date": "2025-01-29", + "time": "11:00", + "title": "1-on-1", + "category": "meeting" + }, + { + "date": "2025-01-31", + "time": "15:00", + "title": "Month End", + "category": "work" + }, + { + "date": "2025-02-03", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-05", + "time": "10:00", + "title": "Q1 Planning", + "category": "meeting" + }, + { + "date": "2025-02-07", + "time": "14:00", + "title": "Training", + "category": "work" + }, + { + "date": "2025-02-10", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-12", + "time": "12:00", + "title": "Team Lunch", + "category": "personal" + }, + { + "date": "2025-02-14", + "time": "All Day", + "title": "Valentine's Day", + "category": "holiday" + }, + { + "date": "2025-02-14", + "time": "19:00", + "title": "Dinner Date", + "category": "personal" + }, + { + "date": "2025-02-17", + "time": "All Day", + "title": "Presidents' Day", + "category": "holiday" + }, + { + "date": "2025-02-17", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-19", + "time": "10:30", + "title": "Arch Review", + "category": "work" + }, + { + "date": "2025-02-21", + "time": "15:00", + "title": "Doctor Appt", + "category": "personal" + }, + { + "date": "2025-02-24", + "time": "09:00", + "title": "Standup", + "category": "meeting" + }, + { + "date": "2025-02-26", + "time": "11:00", + "title": "Product Launch", + "category": "work" + }, + { + "date": "2025-02-28", + "time": "14:00", + "title": "Sprint Retro", + "category": "meeting" + } + ] + } + ], + "variables": [ + { + "variableId": "selectedYear", + "type": "number", + "initialValue": 2025 + }, + { + "variableId": "selectedMonth", + "type": "number", + "initialValue": 1 + }, + { + "variableId": "monthName", + "type": "string", + "initialValue": "January", + "calculation": { + "vegaExpression": "selectedMonth === 1 ? 'January' : selectedMonth === 2 ? 'February' : selectedMonth === 3 ? 'March' : selectedMonth === 4 ? 'April' : selectedMonth === 5 ? 'May' : selectedMonth === 6 ? 'June' : selectedMonth === 7 ? 'July' : selectedMonth === 8 ? 'August' : selectedMonth === 9 ? 'September' : selectedMonth === 10 ? 'October' : selectedMonth === 11 ? 'November' : 'December'" + } + }, + { + "variableId": "eventsGrouped", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "eventsRaw" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "groupby": [ + "date" + ], + "ops": [ + "values", + "values", + "values" + ], + "fields": [ + "time", + "title", + "category" + ], + "as": [ + "times", + "titles", + "categories" + ] + } + ] + } + }, + { + "variableId": "allDaysOfMonth", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "eventsRaw" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": [ + "count" + ], + "as": [ + "count" + ] + }, + { + "type": "sequence", + "start": -7, + "stop": 39, + "as": "dayOffset" + }, + { + "type": "formula", + "as": "date", + "expr": "datetime(selectedYear, selectedMonth - 1, datum.dayOffset)" + }, + { + "type": "formula", + "as": "dayOfMonth", + "expr": "date(datum.date)" + }, + { + "type": "formula", + "as": "month", + "expr": "month(datum.date) + 1" + }, + { + "type": "formula", + "as": "year", + "expr": "year(datum.date)" + }, + { + "type": "formula", + "as": "dateKey", + "expr": "datum.year + '-' + (datum.month < 10 ? '0' + datum.month : datum.month) + '-' + (datum.dayOfMonth < 10 ? '0' + datum.dayOfMonth : datum.dayOfMonth)" + }, + { + "type": "formula", + "as": "weekday", + "expr": "day(datum.date)" + }, + { + "type": "formula", + "as": "dayName", + "expr": "datum.weekday === 0 ? 'sun' : datum.weekday === 1 ? 'mon' : datum.weekday === 2 ? 'tue' : datum.weekday === 3 ? 'wed' : datum.weekday === 4 ? 'thu' : datum.weekday === 5 ? 'fri' : 'sat'" + }, + { + "type": "formula", + "as": "firstDayWeekday", + "expr": "day(datetime(selectedYear, selectedMonth - 1, 1))" + }, + { + "type": "formula", + "as": "week", + "expr": "floor((datum.dayOffset + datum.firstDayWeekday + 7) / 7)" + }, + { + "type": "formula", + "as": "isCurrentMonth", + "expr": "datum.month === selectedMonth" + } + ] + } + }, + { + "variableId": "daysWithEvents", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "allDaysOfMonth", + "eventsGrouped" + ], + "dataFrameTransformations": [ + { + "type": "lookup", + "from": "eventsGrouped", + "key": "date", + "fields": [ + "dateKey" + ], + "values": [ + "times", + "titles", + "categories" + ], + "as": [ + "eventTimes", + "eventTitles", + "eventCategories" + ] + }, + { + "type": "formula", + "as": "events", + "expr": "datum.eventTimes == null ? [] : datum.eventTimes" + }, + { + "type": "formula", + "as": "eventCount", + "expr": "datum.events.length" + } + ] + } + }, + { + "variableId": "paddedDays", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "daysWithEvents" + ], + "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": [ + "count" + ], + "as": [ + "count" + ] + }, + { + "type": "sequence", + "start": 0, + "stop": 42, + "as": "cellIdx" + }, + { + "type": "formula", + "as": "firstDayWeekday", + "expr": "day(datetime(selectedYear, selectedMonth - 1, 1))" + }, + { + "type": "formula", + "as": "daysInMonth", + "expr": "selectedMonth === 2 ? (selectedYear % 4 === 0 && (selectedYear % 100 !== 0 || selectedYear % 400 === 0) ? 29 : 28) : (selectedMonth === 4 || selectedMonth === 6 || selectedMonth === 9 || selectedMonth === 11) ? 30 : 31" + }, + { + "type": "formula", + "as": "prevMonth", + "expr": "selectedMonth === 1 ? 12 : selectedMonth - 1" + }, + { + "type": "formula", + "as": "prevMonthYear", + "expr": "selectedMonth === 1 ? selectedYear - 1 : selectedYear" + }, + { + "type": "formula", + "as": "prevMonthDays", + "expr": "datum.prevMonth === 2 ? (datum.prevMonthYear % 4 === 0 && (datum.prevMonthYear % 100 !== 0 || datum.prevMonthYear % 400 === 0) ? 29 : 28) : (datum.prevMonth === 4 || datum.prevMonth === 6 || datum.prevMonth === 9 || datum.prevMonth === 11) ? 30 : 31" + }, + { + "type": "formula", + "as": "cellDay", + "expr": "datum.cellIdx < datum.firstDayWeekday ? datum.prevMonthDays - datum.firstDayWeekday + datum.cellIdx + 1 : datum.cellIdx >= datum.firstDayWeekday + datum.daysInMonth ? datum.cellIdx - datum.firstDayWeekday - datum.daysInMonth + 1 : datum.cellIdx - datum.firstDayWeekday + 1" + }, + { + "type": "formula", + "as": "isCurrentMonth", + "expr": "datum.cellIdx >= datum.firstDayWeekday && datum.cellIdx < datum.firstDayWeekday + datum.daysInMonth" + }, + { + "type": "formula", + "as": "isWeekend", + "expr": "datum.cellIdx % 7 === 0 || datum.cellIdx % 7 === 6" + }, + { + "type": "formula", + "as": "lookupKey", + "expr": "datum.isCurrentMonth ? (selectedYear + '-' + (selectedMonth < 10 ? '0' + selectedMonth : selectedMonth) + '-' + (datum.cellDay < 10 ? '0' + datum.cellDay : datum.cellDay)) : null" + }, + { + "type": "lookup", + "from": "daysWithEvents", + "key": "dateKey", + "fields": [ + "lookupKey" + ], + "values": [ + "eventTimes", + "eventTitles", + "eventCategories", + "eventCount" + ], + "as": [ + "cellEventTimes", + "cellEventTitles", + "cellEventCats", + "cellEventCount" + ] + }, + { + "type": "formula", + "as": "event1", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 0 ? datum.cellEventTimes[0] + ' ' + datum.cellEventTitles[0] : null" + }, + { + "type": "formula", + "as": "event1Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 0 ? datum.cellEventCats[0] : null" + }, + { + "type": "formula", + "as": "event2", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 1 ? datum.cellEventTimes[1] + ' ' + datum.cellEventTitles[1] : null" + }, + { + "type": "formula", + "as": "event2Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 1 ? datum.cellEventCats[1] : null" + }, + { + "type": "formula", + "as": "event3", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 2 ? datum.cellEventTimes[2] + ' ' + datum.cellEventTitles[2] : null" + }, + { + "type": "formula", + "as": "event3Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 2 ? datum.cellEventCats[2] : null" + }, + { + "type": "formula", + "as": "moreEvents", + "expr": "datum.cellEventCount && datum.cellEventCount > 3 ? '+' + (datum.cellEventCount - 3) + ' more' : null" + }, + { + "type": "filter", + "expr": "datum.cellIdx < 42" + } + ] + } + }, + { + "variableId": "calendarWeeks", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "paddedDays" + ], + "dataFrameTransformations": [ + { + "type": "formula", + "as": "weekNum", + "expr": "floor(datum.cellIdx / 7)" + }, + { + "type": "formula", + "as": "dayOfWeek", + "expr": "datum.cellIdx % 7" + }, + { + "type": "formula", + "as": "dayName", + "expr": "datum.dayOfWeek === 0 ? 'sun' : datum.dayOfWeek === 1 ? 'mon' : datum.dayOfWeek === 2 ? 'tue' : datum.dayOfWeek === 3 ? 'wed' : datum.dayOfWeek === 4 ? 'thu' : datum.dayOfWeek === 5 ? 'fri' : 'sat'" + }, + { + "type": "formula", + "as": "cellDayStr", + "expr": "'' + datum.cellDay" + }, + { + "type": "pivot", + "groupby": [ + "weekNum" + ], + "field": "dayName", + "value": "cellDayStr" + } + ] + } + } + ], + "groups": [ + { + "groupId": "header", + "elements": [ + "# \ud83d\udcc5 Month View Events Calendar", + "Track your events, meetings, and important dates", + "", + "This example demonstrates how to shape data for a calendar view using Vega transforms. Scroll down to see each step of the data transformation pipeline.", + { + "type": "dropdown", + "variableId": "selectedMonth", + "label": "Month:", + "options": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] + }, + { + "type": "dropdown", + "variableId": "selectedYear", + "label": "Year:", + "options": [ + "2024", + "2025", + "2026" + ] + } + ] + }, + { + "groupId": "step1", + "elements": [ + "## Step 1: Raw Events Data", + "This is the source data - a flat list of events with dates, times, titles, and categories.", + { + "type": "tabulator", + "dataSourceName": "eventsRaw" + } + ] + }, + { + "groupId": "step2", + "elements": [ + "## Step 2: Events Grouped by Date", + "Using `aggregate` transform to group multiple events per day into arrays. Each row now represents one date with arrays of times, titles, and categories.", + { + "type": "tabulator", + "dataSourceName": "eventsGrouped" + } + ] + }, + { + "groupId": "step3", + "elements": [ + "## Step 3: All Days of Month", + "Using `sequence` transform to generate all days in the selected month (not just days with events). Computed weekday and week for calendar positioning.", + { + "type": "tabulator", + "dataSourceName": "allDaysOfMonth" + } + ] + }, + { + "groupId": "step4", + "elements": [ + "## Step 4: Days with Events Joined", + "Using `lookup` transform to join events arrays to each day. Days without events have empty arrays.", + { + "type": "tabulator", + "dataSourceName": "daysWithEvents" + } + ] + }, + { + "groupId": "step4b", + "elements": [ + "## Step 4b: Padded 42-Cell Grid", + "Expanded to 42 cells (6 weeks \u00d7 7 days) including previous/next month days for padding. Each cell has its day number, events, and metadata.", + { + "type": "tabulator", + "dataSourceName": "paddedDays" + } + ] + }, + { + "groupId": "step5", + "elements": [ + "## Step 5: Calendar Grid by Week", + "Final step groups the 42 cells into 6 weeks with 7 columns (Sun-Sat). Each row represents one week of the calendar.", + { + "type": "tabulator", + "dataSourceName": "calendarWeeks" + } + ] + }, + { + "groupId": "calendar", + "elements": [ + "## {{monthName}} {{selectedYear}} - Final Calendar View", + { + "type": "treebark", + "variableId": "calendarWeeks", + "template": { + "div": { + "class": "calendar-wrapper", + "$children": [ + { + "table": { + "class": "calendar-table", + "$children": [ + { + "thead": { + "$children": [ + { + "tr": { + "$children": [ + { + "th": "Sun" + }, + { + "th": "Mon" + }, + { + "th": "Tue" + }, + { + "th": "Wed" + }, + { + "th": "Thu" + }, + { + "th": "Fri" + }, + { + "th": "Sat" + } + ] + } + } + ] + } + }, + { + "tbody": { + "$bind": ".", + "$children": [ + { + "tr": [ + { + "td": "{{sun}}" + }, + { + "td": "{{mon}}" + }, + { + "td": "{{tue}}" + }, + { + "td": "{{wed}}" + }, + { + "td": "{{thu}}" + }, + { + "td": "{{fri}}" + }, + { + "td": "{{sat}}" + } + ] + } + ] + } + } + ] + } + } + ] + } + } + }, + "### Event Legend", + "\ud83d\udfe0 **Meeting** \u2022 \ud83d\udd34 **Work** \u2022 \ud83d\udfe3 **Personal** \u2022 \ud83d\udfe2 **Holiday**", + "", + "**Data Shaping:** This calendar uses Vega transforms to create a complete calendar grid (42 cells) and join events onto it. Each cell can display up to 3 events, with an indicator for additional events." + ] + } + ] +} \ No newline at end of file