From 669532c24a06ef7206d405f67919ba84662a8b08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 01:42:08 +0000 Subject: [PATCH 01/22] Initial plan From 9efb3d7d129dfd744acce61ceba6e0294597f754 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 01:52:39 +0000 Subject: [PATCH 02/22] Add month view events calendar example Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 548 ++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 packages/web-deploy/json/month-calendar.idoc.json 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..07001500 --- /dev/null +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -0,0 +1,548 @@ +{ + "$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: 1200px; 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-bottom: 30px; align-items: center; }", + ".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-table { width: 100%; border-collapse: collapse; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }", + ".calendar-table th { background: #3498db; color: white; padding: 12px; text-align: center; font-weight: 600; border: 1px solid #2980b9; }", + ".calendar-table td { border: 1px solid #ddd; padding: 8px; vertical-align: top; height: 100px; min-height: 100px; background: white; }", + ".calendar-table td.other-month { background: #f8f9fa; opacity: 0.6; }", + ".calendar-table td.weekend { background: #f0f8ff; }", + ".day-number { font-weight: bold; color: #2c3e50; margin-bottom: 6px; font-size: 1.1em; }", + ".other-month .day-number { color: #95a5a6; }", + ".events-list { display: flex; flex-direction: column; gap: 3px; }", + ".event-item { background: #3498db; color: white; padding: 4px 6px; border-radius: 3px; font-size: 0.8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; transition: all 0.2s; }", + ".event-item:hover { transform: translateX(2px); opacity: 0.9; }", + ".event-time { font-weight: 600; margin-right: 4px; }", + ".event-work { background: #e74c3c; }", + ".event-personal { background: #9b59b6; }", + ".event-meeting { background: #f39c12; }", + ".event-holiday { background: #1abc9c; }", + ".legend { display: flex; justify-content: center; gap: 25px; margin-top: 20px; 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: 80px; padding: 4px; font-size: 0.9em; } .event-item { font-size: 0.7em; padding: 3px 4px; } }" + ] + }, + "dataLoaders": [ + { + "dataSourceName": "eventsData", + "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 Appointment", "category": "personal"}, + {"date": "2025-01-13", "time": "09:00", "title": "Team Standup", "category": "meeting"}, + {"date": "2025-01-13", "time": "11:00", "title": "Design Review", "category": "work"}, + {"date": "2025-01-15", "time": "13:00", "title": "Lunch with 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": "Team 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 Session", "category": "personal"}, + {"date": "2025-01-27", "time": "09:00", "title": "Team 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 with Manager", "category": "meeting"}, + {"date": "2025-01-31", "time": "15:00", "title": "Month End Review", "category": "work"}, + {"date": "2025-02-03", "time": "09:00", "title": "Team Standup", "category": "meeting"}, + {"date": "2025-02-05", "time": "10:00", "title": "Quarterly Planning", "category": "meeting"}, + {"date": "2025-02-07", "time": "14:00", "title": "Training Session", "category": "work"}, + {"date": "2025-02-10", "time": "09:00", "title": "Team 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": "Team Standup", "category": "meeting"}, + {"date": "2025-02-19", "time": "10:30", "title": "Architecture Review", "category": "work"}, + {"date": "2025-02-21", "time": "15:00", "title": "Doctor Appointment", "category": "personal"}, + {"date": "2025-02-24", "time": "09:00", "title": "Team 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": "eventsWithMeta", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": ["eventsData"], + "dataFrameTransformations": [ + { + "type": "formula", + "as": "dateStr", + "expr": "datum.date" + }, + { + "type": "formula", + "as": "eventInfo", + "expr": "{time: datum.time, title: datum.title, category: datum.category}" + } + ] + } + }, + { + "variableId": "eventsByDateAgg", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": ["eventsWithMeta"], + "dataFrameTransformations": [ + { + "type": "aggregate", + "groupby": ["dateStr"], + "ops": ["values"], + "fields": ["eventInfo"], + "as": ["events"] + } + ] + } + }, + { + "variableId": "calendarData", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": ["eventsByDateAgg"], + "dataFrameTransformations": [ + { + "type": "formula", + "as": "firstDay", + "expr": "datetime(selectedYear, selectedMonth - 1, 1)" + }, + { + "type": "formula", + "as": "startWeekday", + "expr": "day(datum.firstDay)" + }, + { + "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": "nextMonth", + "expr": "selectedMonth === 12 ? 1 : selectedMonth + 1" + }, + { + "type": "formula", + "as": "nextMonthYear", + "expr": "selectedMonth === 12 ? 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": "sequence", + "start": 0, + "stop": 42, + "as": "cellIdx" + }, + { + "type": "formula", + "as": "day", + "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonthDays - datum.startWeekday + datum.cellIdx + 1 : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.cellIdx - datum.startWeekday - datum.daysInMonth + 1 : datum.cellIdx - datum.startWeekday + 1" + }, + { + "type": "formula", + "as": "month", + "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonth : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.nextMonth : selectedMonth" + }, + { + "type": "formula", + "as": "year", + "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonthYear : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.nextMonthYear : selectedYear" + }, + { + "type": "formula", + "as": "isCurrentMonth", + "expr": "datum.month === selectedMonth" + }, + { + "type": "formula", + "as": "isWeekend", + "expr": "datum.cellIdx % 7 === 0 || datum.cellIdx % 7 === 6" + }, + { + "type": "formula", + "as": "dateStr", + "expr": "datum.year + '-' + (datum.month < 10 ? '0' + datum.month : datum.month) + '-' + (datum.day < 10 ? '0' + datum.day : datum.day)" + }, + { + "type": "lookup", + "from": "eventsByDateAgg", + "key": "dateStr", + "fields": ["dateStr"], + "values": ["events"], + "as": ["eventsArray"] + }, + { + "type": "formula", + "as": "events", + "expr": "datum.eventsArray || []" + } + ] + } + } + ], + "groups": [ + { + "groupId": "main", + "elements": [ + "# 📅 Month View Events Calendar", + "Track your events, meetings, and important dates throughout the month", + "", + { + "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"] + }, + "## {{monthName}} {{selectedYear}}", + { + "type": "treebark", + "variableId": "calendarData", + "template": { + "table": { + "class": "calendar-table", + "$children": [ + { + "thead": { + "$children": [ + { + "tr": { + "$children": [ + {"th": "Sunday"}, + {"th": "Monday"}, + {"th": "Tuesday"}, + {"th": "Wednesday"}, + {"th": "Thursday"}, + {"th": "Friday"}, + {"th": "Saturday"} + ] + } + } + ] + } + }, + { + "tbody": { + "$children": [ + { + "tr": { + "$bind": ".[0:7]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[7:14]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[14:21]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[21:28]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[28:35]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "tr": { + "$bind": ".[35:42]", + "$children": [ + { + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "$children": [ + { + "div": { + "class": "day-number", + "$children": ["{{day}}"] + } + }, + { + "div": { + "class": "events-list", + "$bind": "events", + "$children": [ + { + "div": { + "class": "event-item event-{{category}}", + "$children": [ + { + "span": { + "class": "event-time", + "$children": ["{{time}}"] + } + }, + "{{title}}" + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } + }, + "### Event Legend", + "🟠 **Meeting** • 🔴 **Work** • 🟣 **Personal** • 🟢 **Holiday**" + ] + } + ] +} From 8672c188d52d583e24b90572a26c704f4e216e6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:00:28 +0000 Subject: [PATCH 03/22] Implement proper data shaping with Vega transforms for calendar grid Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 1106 ++++++++++++----- 1 file changed, 817 insertions(+), 289 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 07001500..bf419369 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -4,72 +4,234 @@ "style": { "css": [ "body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #f5f7fa; }", - ".container { max-width: 1200px; margin: 0 auto; }", + ".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-bottom: 30px; align-items: center; }", + ".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-table { width: 100%; border-collapse: collapse; background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }", - ".calendar-table th { background: #3498db; color: white; padding: 12px; text-align: center; font-weight: 600; border: 1px solid #2980b9; }", - ".calendar-table td { border: 1px solid #ddd; padding: 8px; vertical-align: top; height: 100px; min-height: 100px; background: white; }", - ".calendar-table td.other-month { background: #f8f9fa; opacity: 0.6; }", - ".calendar-table td.weekend { background: #f0f8ff; }", - ".day-number { font-weight: bold; color: #2c3e50; margin-bottom: 6px; font-size: 1.1em; }", - ".other-month .day-number { color: #95a5a6; }", - ".events-list { display: flex; flex-direction: column; gap: 3px; }", - ".event-item { background: #3498db; color: white; padding: 4px 6px; border-radius: 3px; font-size: 0.8em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; transition: all 0.2s; }", - ".event-item:hover { transform: translateX(2px); opacity: 0.9; }", + ".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; }", - ".event-work { background: #e74c3c; }", - ".event-personal { background: #9b59b6; }", - ".event-meeting { background: #f39c12; }", - ".event-holiday { background: #1abc9c; }", - ".legend { display: flex; justify-content: center; gap: 25px; margin-top: 20px; flex-wrap: wrap; }", + ".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: 80px; padding: 4px; font-size: 0.9em; } .event-item { font-size: 0.7em; padding: 3px 4px; } }" + "@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": "eventsData", + "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 Appointment", "category": "personal"}, - {"date": "2025-01-13", "time": "09:00", "title": "Team Standup", "category": "meeting"}, - {"date": "2025-01-13", "time": "11:00", "title": "Design Review", "category": "work"}, - {"date": "2025-01-15", "time": "13:00", "title": "Lunch with 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": "Team 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 Session", "category": "personal"}, - {"date": "2025-01-27", "time": "09:00", "title": "Team 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 with Manager", "category": "meeting"}, - {"date": "2025-01-31", "time": "15:00", "title": "Month End Review", "category": "work"}, - {"date": "2025-02-03", "time": "09:00", "title": "Team Standup", "category": "meeting"}, - {"date": "2025-02-05", "time": "10:00", "title": "Quarterly Planning", "category": "meeting"}, - {"date": "2025-02-07", "time": "14:00", "title": "Training Session", "category": "work"}, - {"date": "2025-02-10", "time": "09:00", "title": "Team 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": "Team Standup", "category": "meeting"}, - {"date": "2025-02-19", "time": "10:30", "title": "Architecture Review", "category": "work"}, - {"date": "2025-02-21", "time": "15:00", "title": "Doctor Appointment", "category": "personal"}, - {"date": "2025-02-24", "time": "09:00", "title": "Team 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"} + { + "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" + } ] } ], @@ -93,61 +255,146 @@ } }, { - "variableId": "eventsWithMeta", + "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": ["eventsData"], + "dataSourceNames": [ + "eventsRaw" + ], "dataFrameTransformations": [ { "type": "formula", - "as": "dateStr", - "expr": "datum.date" + "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": "sequence", + "start": 1, + "stop": { + "signal": "datum.daysInMonth + 1" + }, + "as": "dayOfMonth" + }, + { + "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": "eventInfo", - "expr": "{time: datum.time, title: datum.title, category: datum.category}" + "as": "week", + "expr": "floor((datum.dayOfMonth + datum.firstDayWeekday - 1) / 7)" } ] } }, { - "variableId": "eventsByDateAgg", + "variableId": "daysWithEvents", "type": "object", "isArray": true, "initialValue": [], "calculation": { - "dataSourceNames": ["eventsWithMeta"], + "dataSourceNames": [ + "allDaysOfMonth", + "eventsGrouped" + ], "dataFrameTransformations": [ { - "type": "aggregate", - "groupby": ["dateStr"], - "ops": ["values"], - "fields": ["eventInfo"], - "as": ["events"] + "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": "calendarData", + "variableId": "paddedDays", "type": "object", "isArray": true, "initialValue": [], "calculation": { - "dataSourceNames": ["eventsByDateAgg"], + "dataSourceNames": [ + "daysWithEvents" + ], "dataFrameTransformations": [ { "type": "formula", - "as": "firstDay", - "expr": "datetime(selectedYear, selectedMonth - 1, 1)" - }, - { - "type": "formula", - "as": "startWeekday", - "expr": "day(datum.firstDay)" + "as": "firstDayWeekday", + "expr": "day(datetime(selectedYear, selectedMonth - 1, 1))" }, { "type": "formula", @@ -164,16 +411,6 @@ "as": "prevMonthYear", "expr": "selectedMonth === 1 ? selectedYear - 1 : selectedYear" }, - { - "type": "formula", - "as": "nextMonth", - "expr": "selectedMonth === 12 ? 1 : selectedMonth + 1" - }, - { - "type": "formula", - "as": "nextMonthYear", - "expr": "selectedMonth === 12 ? selectedYear + 1 : selectedYear" - }, { "type": "formula", "as": "prevMonthDays", @@ -187,46 +424,82 @@ }, { "type": "formula", - "as": "day", - "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonthDays - datum.startWeekday + datum.cellIdx + 1 : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.cellIdx - datum.startWeekday - datum.daysInMonth + 1 : datum.cellIdx - datum.startWeekday + 1" + "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": "month", - "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonth : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.nextMonth : selectedMonth" + "as": "isWeekend", + "expr": "datum.cellIdx % 7 === 0 || datum.cellIdx % 7 === 6" }, { "type": "formula", - "as": "year", - "expr": "datum.cellIdx < datum.startWeekday ? datum.prevMonthYear : datum.cellIdx >= datum.startWeekday + datum.daysInMonth ? datum.nextMonthYear : selectedYear" + "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": "isCurrentMonth", - "expr": "datum.month === selectedMonth" + "as": "event1", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 0 ? datum.cellEventTimes[0] + ' ' + datum.cellEventTitles[0] : null" }, { "type": "formula", - "as": "isWeekend", - "expr": "datum.cellIdx % 7 === 0 || datum.cellIdx % 7 === 6" + "as": "event1Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 0 ? datum.cellEventCats[0] : null" }, { "type": "formula", - "as": "dateStr", - "expr": "datum.year + '-' + (datum.month < 10 ? '0' + datum.month : datum.month) + '-' + (datum.day < 10 ? '0' + datum.day : datum.day)" + "as": "event2", + "expr": "datum.cellEventTimes && datum.cellEventTimes.length > 1 ? datum.cellEventTimes[1] + ' ' + datum.cellEventTitles[1] : null" }, { - "type": "lookup", - "from": "eventsByDateAgg", - "key": "dateStr", - "fields": ["dateStr"], - "values": ["events"], - "as": ["eventsArray"] + "type": "formula", + "as": "event2Cat", + "expr": "datum.cellEventCats && datum.cellEventCats.length > 1 ? datum.cellEventCats[1] : null" }, { "type": "formula", - "as": "events", - "expr": "datum.eventsArray || []" + "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" } ] } @@ -237,292 +510,545 @@ "groupId": "main", "elements": [ "# 📅 Month View Events Calendar", - "Track your events, meetings, and important dates throughout the month", - "", + "Track your events, meetings, and important dates", { "type": "dropdown", "variableId": "selectedMonth", "label": "Month:", - "options": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"] + "options": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12" + ] }, { "type": "dropdown", "variableId": "selectedYear", "label": "Year:", - "options": ["2024", "2025", "2026"] + "options": [ + "2024", + "2025", + "2026" + ] }, "## {{monthName}} {{selectedYear}}", { "type": "treebark", - "variableId": "calendarData", + "variableId": "paddedDays", "template": { - "table": { - "class": "calendar-table", + "div": { + "class": "calendar-wrapper", "$children": [ { - "thead": { + "table": { + "class": "calendar-table", "$children": [ { - "tr": { + "thead": { "$children": [ - {"th": "Sunday"}, - {"th": "Monday"}, - {"th": "Tuesday"}, - {"th": "Wednesday"}, - {"th": "Thursday"}, - {"th": "Friday"}, - {"th": "Saturday"} + { + "tr": { + "$children": [ + { + "th": "Sun" + }, + { + "th": "Mon" + }, + { + "th": "Tue" + }, + { + "th": "Wed" + }, + { + "th": "Thu" + }, + { + "th": "Fri" + }, + { + "th": "Sat" + } + ] + } + } ] } - } - ] - } - }, - { - "tbody": { - "$children": [ + }, { - "tr": { - "$bind": ".[0:7]", + "tbody": { "$children": [ { - "td": { - "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", + "tr": { + "$bind": ".[0:7]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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' : ''}}", + "tr": { + "$bind": ".[7:14]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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' : ''}}", + "tr": { + "$bind": ".[14:21]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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' : ''}}", + "tr": { + "$bind": ".[21:28]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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' : ''}}", + "tr": { + "$bind": ".[28:35]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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' : ''}}", + "tr": { + "$bind": ".[35:42]", "$children": [ { - "div": { - "class": "day-number", - "$children": ["{{day}}"] - } - }, - { - "div": { - "class": "events-list", - "$bind": "events", + "td": { + "class": "{{isCurrentMonth ? '' : 'other-month'}} {{isWeekend ? 'weekend' : ''}}", "$children": [ { - "div": { - "class": "event-item event-{{category}}", + "span": { + "class": "day-number", "$children": [ - { - "span": { - "class": "event-time", - "$children": ["{{time}}"] - } - }, - "{{title}}" + "{{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}}" + ] + } + } + } } ] } @@ -541,7 +1067,9 @@ } }, "### Event Legend", - "🟠 **Meeting** • 🔴 **Work** • 🟣 **Personal** • 🟢 **Holiday**" + "🟠 **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." ] } ] From f8cde6dca555107036de7195042b56e37ac20d1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:14:48 +0000 Subject: [PATCH 04/22] Add tabulators to visualize data transformation pipeline Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 132 +++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index bf419369..3530914e 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -507,10 +507,12 @@ ], "groups": [ { - "groupId": "main", + "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", @@ -539,8 +541,132 @@ "2025", "2026" ] - }, - "## {{monthName}} {{selectedYear}}", + } + ] + }, + { + "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", + "variableId": "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", + "variableId": "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", + "variableId": "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": "step5", + "elements": [ + "## Step 5: Padded Calendar Grid (42 cells)", + "Final transform adds padding days from previous/next months to create a complete 6-week grid (42 cells = 6 weeks × 7 days). This is the data that feeds the calendar visualization below.", + { + "type": "tabulator", + "variableId": "paddedDays", + "tabulatorOptions": { + "layout": "fitColumns", + "maxHeight": "400px", + "pagination": "local", + "paginationSize": 21, + "columns": [ + {"title": "Cell", "field": "cellIdx", "width": 60}, + {"title": "Day", "field": "cellDay", "width": 60}, + {"title": "Current Month?", "field": "isCurrentMonth", "width": 100, "formatter": "tickCross"}, + {"title": "Weekend?", "field": "isWeekend", "width": 90, "formatter": "tickCross"}, + {"title": "Event 1", "field": "event1"}, + {"title": "Event 2", "field": "event2"}, + {"title": "Event 3", "field": "event3"} + ] + } + } + ] + }, + { + "groupId": "calendar", + "elements": [ + "## {{monthName}} {{selectedYear}} - Final Calendar View", { "type": "treebark", "variableId": "paddedDays", From 6372f51830754aeb5be0c618fe88325de21a170f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:19:18 +0000 Subject: [PATCH 05/22] Fix tabulator elements to use dataSourceName instead of variableId Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 3530914e..9c4de4b2 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -574,7 +574,7 @@ "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", - "variableId": "eventsGrouped", + "dataSourceName": "eventsGrouped", "tabulatorOptions": { "layout": "fitColumns", "maxHeight": "300px", @@ -597,7 +597,7 @@ "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", - "variableId": "allDaysOfMonth", + "dataSourceName": "allDaysOfMonth", "tabulatorOptions": { "layout": "fitColumns", "maxHeight": "300px", @@ -620,7 +620,7 @@ "Using `lookup` transform to join events arrays to each day. Days without events have empty arrays.", { "type": "tabulator", - "variableId": "daysWithEvents", + "dataSourceName": "daysWithEvents", "tabulatorOptions": { "layout": "fitColumns", "maxHeight": "300px", @@ -644,7 +644,7 @@ "Final transform adds padding days from previous/next months to create a complete 6-week grid (42 cells = 6 weeks × 7 days). This is the data that feeds the calendar visualization below.", { "type": "tabulator", - "variableId": "paddedDays", + "dataSourceName": "paddedDays", "tabulatorOptions": { "layout": "fitColumns", "maxHeight": "400px", From 8ba090db8d6b126f57805bbb9c26ca22194e177e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:29:15 +0000 Subject: [PATCH 06/22] Fix allDaysOfMonth variable to properly generate calendar days Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 9c4de4b2..36598526 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -298,6 +298,11 @@ "eventsRaw" ], "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": ["count"], + "as": ["count"] + }, { "type": "formula", "as": "daysInMonth", @@ -306,9 +311,7 @@ { "type": "sequence", "start": 1, - "stop": { - "signal": "datum.daysInMonth + 1" - }, + "stop": {"signal": "datum.daysInMonth + 1"}, "as": "dayOfMonth" }, { From 51ea85ca6a30f8cfee3e7f4838fa74758407bbf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:40:24 +0000 Subject: [PATCH 07/22] Simplify allDaysOfMonth to use fixed sequence with filter Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 36598526..b3550086 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -303,16 +303,20 @@ "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": "sequence", - "start": 1, - "stop": {"signal": "datum.daysInMonth + 1"}, - "as": "dayOfMonth" + "type": "filter", + "expr": "datum.dayOfMonth <= datum.daysInMonth" }, { "type": "formula", From c48a80f873ac4802e382b4edb542585260f39567 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:46:33 +0000 Subject: [PATCH 08/22] Add calendarWeeks variable to show grid organized by week with 7 columns Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index b3550086..481665f0 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -510,6 +510,46 @@ } ] } + }, + { + "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": "cellInfo", + "expr": "datum.cellDay + (datum.event1 ? ' [' + datum.event1 + ']' : '')" + }, + { + "type": "pivot", + "groupby": ["weekNum"], + "field": "dayName", + "value": "cellInfo", + "op": "values" + } + ] + } } ], "groups": [ @@ -647,24 +687,23 @@ { "groupId": "step5", "elements": [ - "## Step 5: Padded Calendar Grid (42 cells)", - "Final transform adds padding days from previous/next months to create a complete 6-week grid (42 cells = 6 weeks × 7 days). This is the data that feeds the calendar visualization below.", + "## 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": "paddedDays", + "dataSourceName": "calendarWeeks", "tabulatorOptions": { "layout": "fitColumns", "maxHeight": "400px", - "pagination": "local", - "paginationSize": 21, "columns": [ - {"title": "Cell", "field": "cellIdx", "width": 60}, - {"title": "Day", "field": "cellDay", "width": 60}, - {"title": "Current Month?", "field": "isCurrentMonth", "width": 100, "formatter": "tickCross"}, - {"title": "Weekend?", "field": "isWeekend", "width": 90, "formatter": "tickCross"}, - {"title": "Event 1", "field": "event1"}, - {"title": "Event 2", "field": "event2"}, - {"title": "Event 3", "field": "event3"} + {"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"} ] } } From ac42d22753f8182161d4645f9c4602108107356e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:52:03 +0000 Subject: [PATCH 09/22] Fix pivot to use max instead of values to avoid array duplication Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 481665f0..595267d2 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -546,7 +546,7 @@ "groupby": ["weekNum"], "field": "dayName", "value": "cellInfo", - "op": "values" + "op": "max" } ] } From 794b1709ed0de8994de25b97bf7492afca8d4868 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 02:56:01 +0000 Subject: [PATCH 10/22] Change pivot op to min for string compatibility Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 595267d2..46a0d278 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -546,7 +546,7 @@ "groupby": ["weekNum"], "field": "dayName", "value": "cellInfo", - "op": "max" + "op": "min" } ] } From d93ab4a1cbbb952e71eef5512c1fe6bec8e821b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:01:25 +0000 Subject: [PATCH 11/22] Simplify pivot to use cellDay directly without op parameter Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 46a0d278..8b9460ab 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -536,17 +536,11 @@ "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": "cellInfo", - "expr": "datum.cellDay + (datum.event1 ? ' [' + datum.event1 + ']' : '')" - }, { "type": "pivot", "groupby": ["weekNum"], "field": "dayName", - "value": "cellInfo", - "op": "min" + "value": "cellDay" } ] } From 5723fc87916d333c3c1437d09b0d352412aa86d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:05:46 +0000 Subject: [PATCH 12/22] Add op:max to pivot transform for calendarWeeks Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 8b9460ab..3928e093 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -540,7 +540,8 @@ "type": "pivot", "groupby": ["weekNum"], "field": "dayName", - "value": "cellDay" + "value": "cellDay", + "op": "max" } ] } From 5b1fcbe58b8b2ced2b7548aa8e2233d30ab94d84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 03:26:59 +0000 Subject: [PATCH 13/22] Convert cellDay to string before pivot to fix blank cells in step 5 Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 3928e093..3b10990a 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -536,12 +536,16 @@ "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": "cellDay", - "op": "max" + "value": "cellDayStr" } ] } From a6476bd7bbd576f392de39d4c3f12fe35e1e60a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:09:41 +0000 Subject: [PATCH 14/22] Add tabulator for paddedDays to visualize 42-cell grid Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 3b10990a..b75bd592 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -683,6 +683,32 @@ } ] }, + { + "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": [ From dd983f988b5b0632d68f5d8db8a25ae83cab05b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:14:57 +0000 Subject: [PATCH 15/22] Fix paddedDays to generate 42-cell grid from scratch instead of from daysWithEvents Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index b75bd592..151cfab4 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -395,9 +395,20 @@ "initialValue": [], "calculation": { "dataSourceNames": [ - "daysWithEvents" + "eventsRaw" ], "dataFrameTransformations": [ + { + "type": "aggregate", + "ops": ["count"], + "as": ["count"] + }, + { + "type": "sequence", + "start": 0, + "stop": 42, + "as": "cellIdx" + }, { "type": "formula", "as": "firstDayWeekday", @@ -423,12 +434,6 @@ "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": "sequence", - "start": 0, - "stop": 42, - "as": "cellIdx" - }, { "type": "formula", "as": "cellDay", From d82030a25019c848d6c727d85178a2413d64f2f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 04:26:57 +0000 Subject: [PATCH 16/22] Fix paddedDays to use daysWithEvents as data source Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- packages/web-deploy/json/month-calendar.idoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 151cfab4..c3dcc52e 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -395,7 +395,7 @@ "initialValue": [], "calculation": { "dataSourceNames": [ - "eventsRaw" + "daysWithEvents" ], "dataFrameTransformations": [ { From 1f9ef921e0df2c21b5f59c150a56b2b351ffbe27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 11:44:49 +0000 Subject: [PATCH 17/22] Update treebark template to use calendarWeeks pivoted data Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-backup.idoc.json | 1278 +++++++++++++++++ .../web-deploy/json/month-calendar.idoc.json | 684 +++------ 2 files changed, 1465 insertions(+), 497 deletions(-) create mode 100644 packages/web-deploy/json/month-calendar-backup.idoc.json 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.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index c3dcc52e..d7ee8a98 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -300,8 +300,12 @@ "dataFrameTransformations": [ { "type": "aggregate", - "ops": ["count"], - "as": ["count"] + "ops": [ + "count" + ], + "as": [ + "count" + ] }, { "type": "sequence", @@ -400,8 +404,12 @@ "dataFrameTransformations": [ { "type": "aggregate", - "ops": ["count"], - "as": ["count"] + "ops": [ + "count" + ], + "as": [ + "count" + ] }, { "type": "sequence", @@ -548,7 +556,9 @@ }, { "type": "pivot", - "groupby": ["weekNum"], + "groupby": [ + "weekNum" + ], "field": "dayName", "value": "cellDayStr" } @@ -560,7 +570,7 @@ { "groupId": "header", "elements": [ - "# 📅 Month View Events Calendar", + "# \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.", @@ -609,10 +619,25 @@ "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} + { + "title": "Date", + "field": "date", + "width": 120 + }, + { + "title": "Time", + "field": "time", + "width": 100 + }, + { + "title": "Title", + "field": "title" + }, + { + "title": "Category", + "field": "category", + "width": 100 + } ] } } @@ -632,10 +657,26 @@ "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"} + { + "title": "Date", + "field": "date", + "width": 120 + }, + { + "title": "Times", + "field": "times", + "formatter": "textarea" + }, + { + "title": "Titles", + "field": "titles", + "formatter": "textarea" + }, + { + "title": "Categories", + "field": "categories", + "formatter": "textarea" + } ] } } @@ -655,10 +696,26 @@ "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} + { + "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 + } ] } } @@ -678,11 +735,31 @@ "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"} + { + "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" + } ] } } @@ -692,7 +769,7 @@ "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.", + "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", @@ -702,13 +779,41 @@ "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"} + { + "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" + } ] } } @@ -726,14 +831,39 @@ "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"} + { + "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" + } ] } } @@ -745,7 +875,7 @@ "## {{monthName}} {{selectedYear}} - Final Calendar View", { "type": "treebark", - "variableId": "paddedDays", + "variableId": "calendarWeeks", "template": { "div": { "class": "calendar-wrapper", @@ -792,468 +922,28 @@ "$children": [ { "tr": { - "$bind": ".[0:7]", + "$bind": ".", "$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": "{{Sun}}" + }, { - "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": "{{Mon}}" + }, { - "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": "{{Tue}}" + }, { - "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": "{{Wed}}" + }, { - "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": "{{Thu}}" + }, + { + "td": "{{Fri}}" + }, { - "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}}" - ] - } - } - } - } - ] - } + "td": "{{Sat}}" } ] } @@ -1269,10 +959,10 @@ } }, "### Event Legend", - "🟠 **Meeting** • 🔴 **Work** • 🟣 **Personal** • 🟢 **Holiday**", + "\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 From 47da0f36798436ce3677f3a28ab2afecf9db141d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:06:11 +0000 Subject: [PATCH 18/22] Fix treebark template to properly render 7 cells per row Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 117 +++++++++++++----- 1 file changed, 89 insertions(+), 28 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index d7ee8a98..f2256521 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -919,36 +919,97 @@ }, { "tbody": { - "$children": [ - { - "tr": { - "$bind": ".", - "$children": [ - { - "td": "{{Sun}}" - }, - { - "td": "{{Mon}}" - }, - { - "td": "{{Tue}}" - }, - { - "td": "{{Wed}}" - }, - { - "td": "{{Thu}}" - }, - { - "td": "{{Fri}}" - }, - { - "td": "{{Sat}}" + "$children": { + "tr": { + "$bind": ".", + "$children": [ + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Sun}}" + } + } + ] } - ] - } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Mon}}" + } + } + ] + } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Tue}}" + } + } + ] + } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Wed}}" + } + } + ] + } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Thu}}" + } + } + ] + } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Fri}}" + } + } + ] + } + }, + { + "td": { + "$children": [ + { + "span": { + "class": "day-number", + "$text": "{{Sat}}" + } + } + ] + } + } + ] } - ] + } } } ] From 8ce9c5648a5a32d71d96139cf57af82dbcfc9322 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:48:16 +0000 Subject: [PATCH 19/22] Fix calendar rendering with simplified treebark template pattern Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 89 +++---------------- 1 file changed, 13 insertions(+), 76 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index f2256521..41fbce2c 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -547,7 +547,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'" + "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", @@ -919,97 +919,34 @@ }, { "tbody": { - "$children": { - "tr": { - "$bind": ".", - "$children": [ + "$bind": ".", + "$children": [ + { + "tr": [ { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Sun}}" - } - } - ] - } + "td": "{{sun}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Mon}}" - } - } - ] - } + "td": "{{mon}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Tue}}" - } - } - ] - } + "td": "{{tue}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Wed}}" - } - } - ] - } + "td": "{{wed}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Thu}}" - } - } - ] - } + "td": "{{thu}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Fri}}" - } - } - ] - } + "td": "{{fri}}" }, { - "td": { - "$children": [ - { - "span": { - "class": "day-number", - "$text": "{{Sat}}" - } - } - ] - } + "td": "{{sat}}" } ] } - } + ] } } ] From b4fb648107dceaa6559447379277b7751989d1a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 12:58:45 +0000 Subject: [PATCH 20/22] Remove CSS and tabulator options for easier debugging Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 243 +----------------- 1 file changed, 6 insertions(+), 237 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 41fbce2c..007fa9cd 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -1,39 +1,6 @@ { "$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", @@ -612,34 +579,7 @@ "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 - } - ] - } + "dataSourceName": "eventsRaw" } ] }, @@ -650,35 +590,7 @@ "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" - } - ] - } + "dataSourceName": "eventsGrouped" } ] }, @@ -689,35 +601,7 @@ "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 - } - ] - } + "dataSourceName": "allDaysOfMonth" } ] }, @@ -728,40 +612,7 @@ "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" - } - ] - } + "dataSourceName": "daysWithEvents" } ] }, @@ -772,50 +623,7 @@ "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", - "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" - } - ] - } + "dataSourceName": "paddedDays" } ] }, @@ -826,46 +634,7 @@ "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" - } - ] - } + "dataSourceName": "calendarWeeks" } ] }, From bde9e5e46ca424a7a1014c4c087d0fc07dacea57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 14:54:04 +0000 Subject: [PATCH 21/22] Extend allDaysOfMonth to include full week padding before and after month Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 007fa9cd..788b893c 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -276,34 +276,45 @@ }, { "type": "sequence", - "start": 1, - "stop": 32, - "as": "dayOfMonth" + "start": -7, + "stop": 39, + "as": "dayOffset" }, { "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" + "as": "date", + "expr": "datetime(selectedYear, selectedMonth - 1, datum.dayOffset)" }, { - "type": "filter", - "expr": "datum.dayOfMonth <= datum.daysInMonth" + "type": "formula", + "as": "dayOfMonth", + "expr": "date(datum.date)" }, { "type": "formula", - "as": "date", - "expr": "datetime(selectedYear, selectedMonth - 1, datum.dayOfMonth)" + "as": "month", + "expr": "month(datum.date) + 1" + }, + { + "type": "formula", + "as": "year", + "expr": "year(datum.date)" }, { "type": "formula", "as": "dateKey", - "expr": "selectedYear + '-' + (selectedMonth < 10 ? '0' + selectedMonth : selectedMonth) + '-' + (datum.dayOfMonth < 10 ? '0' + datum.dayOfMonth : datum.dayOfMonth)" + "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", @@ -312,7 +323,12 @@ { "type": "formula", "as": "week", - "expr": "floor((datum.dayOfMonth + datum.firstDayWeekday - 1) / 7)" + "expr": "floor((datum.dayOffset + datum.firstDayWeekday + 7) / 7)" + }, + { + "type": "formula", + "as": "isCurrentMonth", + "expr": "datum.month === selectedMonth" } ] } From 280a54f6a3ddcfc786e5c0f7edff1c28c5f5effd Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Mon, 20 Oct 2025 01:32:03 -0700 Subject: [PATCH 22/22] added working calendar core display --- .../json/month-calendar-core.idoc.json | 282 ++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 packages/web-deploy/json/month-calendar-core.idoc.json 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