From 423f6c8413026a83a15432c4cac7b0f956fb0970 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:32:52 +0000 Subject: [PATCH 01/12] Initial plan From 813b9cd3df6cc746d3d8eae685a468d0b77e2ba5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:42:42 +0000 Subject: [PATCH 02/12] Add collect/sort transform to order weekday columns and enableDownload to step 5 table Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- docs/schema/idoc_v1.d.ts | 1 + packages/markdown/src/plugins/tabulator.ts | 15 ++++++++++++++- packages/web-deploy/json/month-calendar.idoc.json | 10 +++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts index b80ead27..26e82a76 100644 --- a/docs/schema/idoc_v1.d.ts +++ b/docs/schema/idoc_v1.d.ts @@ -248,6 +248,7 @@ interface TabulatorElementProps extends OptionalVariableControl { /** Name of the data source to use for incoming data (output data is available via the variableId of this table element) */ dataSourceName: string; editable?: boolean; + enableDownload?: boolean; /** Tabulator options (must be JSON stringify-able, so no callbacks allowed) */ tabulatorOptions?: object; } diff --git a/packages/markdown/src/plugins/tabulator.ts b/packages/markdown/src/plugins/tabulator.ts index fbfd9796..b61c5210 100644 --- a/packages/markdown/src/plugins/tabulator.ts +++ b/packages/markdown/src/plugins/tabulator.ts @@ -89,7 +89,7 @@ export const tabulatorPlugin: Plugin = { // Build all buttons in one HTML string let buttonsHtml = ''; - if (spec.editable || selectableRows) { + if (spec.editable || selectableRows || spec.enableDownload) { buttonsHtml = '
'; if (spec.editable) { @@ -101,6 +101,10 @@ export const tabulatorPlugin: Plugin = { buttonsHtml += ''; } + if (spec.enableDownload) { + buttonsHtml += ''; + } + buttonsHtml += '
'; } @@ -275,6 +279,15 @@ export const tabulatorPlugin: Plugin = { } } + if (spec.enableDownload) { + const downloadBtn = container.querySelector('.tabulator-download-csv') as HTMLButtonElement; + if (downloadBtn) { + downloadBtn.onclick = () => { + table.download('csv', `${spec.dataSourceName}.csv`); + }; + } + } + return { ...tabulatorInstance, initialSignals, diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 788b893c..5a414f45 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -537,6 +537,13 @@ "as": "cellDayStr", "expr": "'' + datum.cellDay" }, + { + "type": "collect", + "sort": { + "field": ["weekNum", "dayOfWeek"], + "order": ["ascending", "ascending"] + } + }, { "type": "pivot", "groupby": [ @@ -650,7 +657,8 @@ "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" + "dataSourceName": "calendarWeeks", + "enableDownload": true } ] }, From 205db580c61567b866aa244f98d4810548fd06ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:53:29 +0000 Subject: [PATCH 03/12] Use numeric column names (0-6) for weekdays and add currentMonthDays aggregation Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 5a414f45..4153365c 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -328,7 +328,7 @@ { "type": "formula", "as": "isCurrentMonth", - "expr": "datum.month === selectedMonth" + "expr": "datum.month === selectedMonth ? 1 : 0" } ] } @@ -527,11 +527,6 @@ "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", @@ -546,11 +541,17 @@ }, { "type": "pivot", - "groupby": [ - "weekNum" - ], - "field": "dayName", - "value": "cellDayStr" + "groupby": ["weekNum", "isCurrentMonth"], + "field": "dayOfWeek", + "value": "cellDayStr", + "op": "max" + }, + { + "type": "aggregate", + "groupby": ["weekNum"], + "ops": ["max", "max", "max", "max", "max", "max", "max", "sum"], + "fields": ["0", "1", "2", "3", "4", "5", "6", "isCurrentMonth"], + "as": ["0", "1", "2", "3", "4", "5", "6", "currentMonthDays"] } ] } From ed72b9d6d5c5f2079aec97ab659c3c29669f26d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:09:48 +0000 Subject: [PATCH 04/12] Remove aggregate from step3, delete steps 4 and 4b, update treebark to show numeric columns, order by week Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../web-deploy/json/month-calendar.idoc.json | 248 ++---------------- 1 file changed, 20 insertions(+), 228 deletions(-) diff --git a/packages/web-deploy/json/month-calendar.idoc.json b/packages/web-deploy/json/month-calendar.idoc.json index 4153365c..3a79f08e 100644 --- a/packages/web-deploy/json/month-calendar.idoc.json +++ b/packages/web-deploy/json/month-calendar.idoc.json @@ -265,15 +265,6 @@ "eventsRaw" ], "dataFrameTransformations": [ - { - "type": "aggregate", - "ops": [ - "count" - ], - "as": [ - "count" - ] - }, { "type": "sequence", "start": -7, @@ -333,180 +324,6 @@ ] } }, - { - "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", @@ -514,44 +331,41 @@ "initialValue": [], "calculation": { "dataSourceNames": [ - "paddedDays" + "allDaysOfMonth" ], "dataFrameTransformations": [ - { - "type": "formula", - "as": "weekNum", - "expr": "floor(datum.cellIdx / 7)" - }, - { - "type": "formula", - "as": "dayOfWeek", - "expr": "datum.cellIdx % 7" - }, { "type": "formula", "as": "cellDayStr", - "expr": "'' + datum.cellDay" + "expr": "'' + datum.dayOfMonth" }, { "type": "collect", "sort": { - "field": ["weekNum", "dayOfWeek"], + "field": ["week", "weekday"], "order": ["ascending", "ascending"] } }, { "type": "pivot", - "groupby": ["weekNum", "isCurrentMonth"], - "field": "dayOfWeek", + "groupby": ["week", "isCurrentMonth"], + "field": "weekday", "value": "cellDayStr", "op": "max" }, { "type": "aggregate", - "groupby": ["weekNum"], + "groupby": ["week"], "ops": ["max", "max", "max", "max", "max", "max", "max", "sum"], "fields": ["0", "1", "2", "3", "4", "5", "6", "isCurrentMonth"], "as": ["0", "1", "2", "3", "4", "5", "6", "currentMonthDays"] + }, + { + "type": "collect", + "sort": { + "field": ["week"], + "order": ["ascending"] + } } ] } @@ -629,28 +443,6 @@ } ] }, - { - "groupId": "step4", - "elements": [ - "## Step 4: Days with Events Joined", - "Using `lookup` transform to join events arrays to each day. Days without events have empty arrays.", - { - "type": "tabulator", - "dataSourceName": "daysWithEvents" - } - ] - }, - { - "groupId": "step4b", - "elements": [ - "## Step 4b: Padded 42-Cell Grid", - "Expanded to 42 cells (6 weeks \u00d7 7 days) including previous/next month days for padding. Each cell has its day number, events, and metadata.", - { - "type": "tabulator", - "dataSourceName": "paddedDays" - } - ] - }, { "groupId": "step5", "elements": [ @@ -718,25 +510,25 @@ { "tr": [ { - "td": "{{sun}}" + "td": "{{0}}" }, { - "td": "{{mon}}" + "td": "{{1}}" }, { - "td": "{{tue}}" + "td": "{{2}}" }, { - "td": "{{wed}}" + "td": "{{3}}" }, { - "td": "{{thu}}" + "td": "{{4}}" }, { - "td": "{{fri}}" + "td": "{{5}}" }, { - "td": "{{sat}}" + "td": "{{6}}" } ] } From 82587df95556662ce10b22cb5dbfc96c8d46b069 Mon Sep 17 00:00:00 2001 From: Dan Marshall Date: Mon, 20 Oct 2025 01:32:03 -0700 Subject: [PATCH 05/12] 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 From bad02958a6f5061fd5a07472ba9b07e986f7c8bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 08:59:57 +0000 Subject: [PATCH 06/12] Create month-calendar-with-events.idoc.json based on core with events lookup Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 625 ++++++++++++++++++ 1 file changed, 625 insertions(+) create mode 100644 packages/web-deploy/json/month-calendar-with-events.idoc.json diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json new file mode 100644 index 00000000..caab760b --- /dev/null +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -0,0 +1,625 @@ +{ + "$schema": "../../../docs/schema/idoc_v1.json", + "title": "Month View Events Calendar", + "variables": [ + { + "variableId": "selectedYear", + "type": "number", + "initialValue": 2025 + }, + { + "variableId": "selectedMonth", + "type": "number", + "initialValue": 1 + }, + { + "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": "month", + "expr": "month(datum.date) + 1" + }, + { + "type": "formula", + "as": "dateKey", + "expr": "datum.year + '-' + (datum.month < 10 ? '0' + datum.month : datum.month) + '-' + (datum.dayOfMonth < 10 ? '0' + datum.dayOfMonth : datum.dayOfMonth)" + }, + { + "type": "formula", + "as": "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": "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": "calendarWithEvents", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "calendarMonthList", + "eventsGrouped" + ], + "dataFrameTransformations": [ + { + "type": "lookup", + "from": "eventsGrouped", + "key": "date", + "fields": [ + "dateKey" + ], + "values": [ + "times", + "titles", + "categories" + ], + "as": [ + "eventTimes", + "eventTitles", + "eventCategories" + ] + }, + { + "type": "formula", + "as": "eventCount", + "expr": "datum.eventTimes ? datum.eventTimes.length : 0" + }, + { + "type": "formula", + "as": "event1", + "expr": "datum.eventTimes && datum.eventTimes.length > 0 ? datum.eventTimes[0] + ' ' + datum.eventTitles[0] : null" + } + ] + } + }, + { + "variableId": "calendarMonthByWeek", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "calendarWithEvents" + ], + "dataFrameTransformations": [ + { + "type": "nest", + "keys": [ + "sundayCount", + "weekday" + ], + "generate": true + } + ] + } + } + ], + "groups": [ + { + "groupId": "header", + "elements": [ + "# 📅 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": "step2", + "elements": [ + "## Step 2: Events Grouped by Date", + "Using `aggregate` transform to group multiple events per day into arrays.", + { + "type": "tabulator", + "dataSourceName": "eventsGrouped", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px" + } + } + ] + }, + { + "groupId": "step3", + "elements": [ + "## Step 3: Calendar with Events Joined", + "Using `lookup` transform to join events arrays to each calendar day.", + { + "type": "tabulator", + "dataSourceName": "calendarWithEvents", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px" + } + } + ] + }, + { + "groupId": "calendar", + "elements": [ + "## Step 4: 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": [ + { + "div": { + "$attr": { + "style": "font-weight: bold" + }, + "$children": [ + "{{dayOfMonth}}" + ] + } + }, + { + "$if": { + "$check": "event1", + "$then": { + "div": { + "$attr": { + "style": "font-size: 0.8em; margin-top: 2px" + }, + "$children": [ + "{{event1}}" + ] + } + } + } + } + ] + } + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + ] + } + } + ] + } + ], + "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" + } + ] + } + ] +} From f6eadee94f58b25e7daf790ed63cc5ad4ae42eba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 09:51:18 +0000 Subject: [PATCH 07/12] Fix event display in calendar - show event count and first event title Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index caab760b..55a85383 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -378,7 +378,7 @@ { "div": { "$attr": { - "style": "font-weight: bold" + "style": "font-weight: bold; margin-bottom: 4px" }, "$children": [ "{{dayOfMonth}}" @@ -387,14 +387,29 @@ }, { "$if": { - "$check": "event1", + "$check": "eventCount", "$then": { "div": { "$attr": { - "style": "font-size: 0.8em; margin-top: 2px" + "style": "font-size: 0.75em; color: #666" }, "$children": [ - "{{event1}}" + "{{eventCount}} event(s)" + ] + } + } + } + }, + { + "$if": { + "$check": "eventTitles", + "$then": { + "div": { + "$attr": { + "style": "font-size: 0.7em; margin-top: 2px; color: #333" + }, + "$children": [ + "{{eventTitles.0}}" ] } } From 0c3768cdc2841eeedd883d1d76c419a84e68e7b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:52:29 +0000 Subject: [PATCH 08/12] Add raw events data tabulator display Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index 55a85383..37ce811f 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -270,10 +270,26 @@ } ] }, + { + "groupId": "rawEvents", + "elements": [ + "## Raw Events Data", + "This is the source data - a list of events with dates, times, titles, and categories.", + { + "type": "tabulator", + "dataSourceName": "eventsRaw", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px" + } + } + ] + }, { "groupId": "step1", "elements": [ - "## Step 1: All Days of Calendar Surrounding the Selected Month", + "## Step 2: 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", @@ -289,7 +305,7 @@ { "groupId": "step2", "elements": [ - "## Step 2: Events Grouped by Date", + "## Step 3: Events Grouped by Date", "Using `aggregate` transform to group multiple events per day into arrays.", { "type": "tabulator", @@ -305,7 +321,7 @@ { "groupId": "step3", "elements": [ - "## Step 3: Calendar with Events Joined", + "## Step 4: Calendar with Events Joined", "Using `lookup` transform to join events arrays to each calendar day.", { "type": "tabulator", @@ -321,7 +337,7 @@ { "groupId": "calendar", "elements": [ - "## Step 4: Calendar View (Treebark / HTML table)", + "## Step 5: Calendar View (Treebark / HTML table)", { "type": "treebark", "variableId": "calendarMonthByWeek", From 048dea49f666a569d28f8a4b4d3b45164ca8f953 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:01:13 +0000 Subject: [PATCH 09/12] Replace events with diverse set across all days of the week Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 244 +++++++++++++----- 1 file changed, 182 insertions(+), 62 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index 37ce811f..599108c1 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -459,59 +459,107 @@ "format": "json", "content": [ { - "date": "2025-01-01", - "time": "All Day", - "title": "New Year's Day", - "category": "holiday" - }, - { - "date": "2025-01-06", + "date": "2025-01-02", "time": "09:00", - "title": "Team Standup", + "title": "Morning Standup", "category": "meeting" }, { - "date": "2025-01-06", + "date": "2025-01-02", "time": "14:00", - "title": "Project Review", + "title": "Code Review", + "category": "work" + }, + { + "date": "2025-01-03", + "time": "10:00", + "title": "Client Call", + "category": "meeting" + }, + { + "date": "2025-01-04", + "time": "All Day", + "title": "Team Offsite", + "category": "work" + }, + { + "date": "2025-01-05", + "time": "11:00", + "title": "Dentist Appointment", + "category": "personal" + }, + { + "date": "2025-01-07", + "time": "13:00", + "title": "Design Review", "category": "work" }, { "date": "2025-01-08", - "time": "10:30", - "title": "Client Meeting", + "time": "09:00", + "title": "Sprint Planning", "category": "meeting" }, { - "date": "2025-01-10", + "date": "2025-01-09", "time": "15:00", - "title": "Dentist", + "title": "Doctor Visit", "category": "personal" }, { - "date": "2025-01-13", + "date": "2025-01-10", + "time": "10:30", + "title": "Project Kickoff", + "category": "meeting" + }, + { + "date": "2025-01-11", + "time": "All Day", + "title": "Weekend Hike", + "category": "personal" + }, + { + "date": "2025-01-12", + "time": "14:00", + "title": "Brunch with Friends", + "category": "personal" + }, + { + "date": "2025-01-14", "time": "09:00", - "title": "Standup", + "title": "Team Sync", "category": "meeting" }, { - "date": "2025-01-13", + "date": "2025-01-15", "time": "11:00", - "title": "Design Review", + "title": "Architecture Review", "category": "work" }, { - "date": "2025-01-15", - "time": "13:00", - "title": "Lunch w/ Sarah", - "category": "personal" + "date": "2025-01-16", + "time": "16:00", + "title": "Performance Review", + "category": "work" }, { "date": "2025-01-17", - "time": "16:00", - "title": "Code Review", + "time": "10:00", + "title": "Training Session", "category": "work" }, + { + "date": "2025-01-18", + "time": "All Day", + "title": "Gym", + "category": "personal" + }, + { + "date": "2025-01-19", + "time": "13:00", + "title": "Family Dinner", + "category": "personal" + }, { "date": "2025-01-20", "time": "All Day", @@ -519,82 +567,136 @@ "category": "holiday" }, { - "date": "2025-01-20", + "date": "2025-01-21", "time": "09:00", - "title": "Standup", + "title": "Weekly Standup", "category": "meeting" }, + { + "date": "2025-01-21", + "time": "14:00", + "title": "Product Demo", + "category": "work" + }, { "date": "2025-01-22", "time": "10:00", - "title": "Sprint Planning", + "title": "Budget Meeting", "category": "meeting" }, { - "date": "2025-01-22", - "time": "14:00", - "title": "Feature Demo", - "category": "work" + "date": "2025-01-23", + "time": "15:00", + "title": "1-on-1 with Manager", + "category": "meeting" }, { "date": "2025-01-24", - "time": "18:00", - "title": "Gym", + "time": "11:00", + "title": "Tech Talk", + "category": "work" + }, + { + "date": "2025-01-25", + "time": "All Day", + "title": "Volunteer Work", + "category": "personal" + }, + { + "date": "2025-01-26", + "time": "12:00", + "title": "Team Lunch", "category": "personal" }, { - "date": "2025-01-27", + "date": "2025-01-28", "time": "09:00", - "title": "Standup", + "title": "Sprint Retrospective", "category": "meeting" }, { - "date": "2025-01-27", - "time": "13:00", - "title": "Budget Review", + "date": "2025-01-29", + "time": "14:00", + "title": "Customer Presentation", "category": "work" }, { - "date": "2025-01-29", - "time": "11:00", - "title": "1-on-1", - "category": "meeting" + "date": "2025-01-30", + "time": "10:00", + "title": "Feature Planning", + "category": "work" }, { "date": "2025-01-31", "time": "15:00", - "title": "Month End", + "title": "Month End Review", "category": "work" }, + { + "date": "2025-02-01", + "time": "All Day", + "title": "Weekend Project", + "category": "personal" + }, { "date": "2025-02-03", "time": "09:00", - "title": "Standup", + "title": "Q1 Planning", "category": "meeting" }, + { + "date": "2025-02-04", + "time": "14:00", + "title": "Security Training", + "category": "work" + }, { "date": "2025-02-05", "time": "10:00", - "title": "Q1 Planning", + "title": "Vendor Meeting", "category": "meeting" }, { - "date": "2025-02-07", - "time": "14:00", - "title": "Training", + "date": "2025-02-06", + "time": "11:00", + "title": "Code Walkthrough", "category": "work" }, + { + "date": "2025-02-07", + "time": "16:00", + "title": "Team Happy Hour", + "category": "personal" + }, + { + "date": "2025-02-08", + "time": "All Day", + "title": "Skiing Trip", + "category": "personal" + }, { "date": "2025-02-10", "time": "09:00", - "title": "Standup", + "title": "All Hands Meeting", "category": "meeting" }, + { + "date": "2025-02-11", + "time": "13:00", + "title": "Infrastructure Review", + "category": "work" + }, { "date": "2025-02-12", - "time": "12:00", - "title": "Team Lunch", - "category": "personal" + "time": "10:00", + "title": "Pair Programming", + "category": "work" + }, + { + "date": "2025-02-13", + "time": "14:00", + "title": "Release Planning", + "category": "meeting" }, { "date": "2025-02-14", @@ -615,39 +717,57 @@ "category": "holiday" }, { - "date": "2025-02-17", + "date": "2025-02-18", "time": "09:00", "title": "Standup", "category": "meeting" }, { "date": "2025-02-19", - "time": "10:30", - "title": "Arch Review", + "time": "11:00", + "title": "Stakeholder Update", + "category": "meeting" + }, + { + "date": "2025-02-20", + "time": "14:00", + "title": "Tech Debt Review", "category": "work" }, { "date": "2025-02-21", - "time": "15:00", - "title": "Doctor Appt", - "category": "personal" + "time": "10:00", + "title": "Onboarding Session", + "category": "work" }, { "date": "2025-02-24", "time": "09:00", - "title": "Standup", + "title": "Weekly Sync", + "category": "meeting" + }, + { + "date": "2025-02-25", + "time": "15:00", + "title": "Product Roadmap", "category": "meeting" }, { "date": "2025-02-26", "time": "11:00", - "title": "Product Launch", + "title": "Launch Preparation", "category": "work" }, { - "date": "2025-02-28", + "date": "2025-02-27", "time": "14:00", - "title": "Sprint Retro", + "title": "Performance Testing", + "category": "work" + }, + { + "date": "2025-02-28", + "time": "10:00", + "title": "Sprint Review", "category": "meeting" } ] From 14c2d9b398ac35dfa85e6c112dd3108934e50498 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:08:53 +0000 Subject: [PATCH 10/12] Fix treebark event display with proper string fields and span tags Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index 599108c1..0fd3b1bc 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -206,6 +206,16 @@ "type": "formula", "as": "event1", "expr": "datum.eventTimes && datum.eventTimes.length > 0 ? datum.eventTimes[0] + ' ' + datum.eventTitles[0] : null" + }, + { + "type": "formula", + "as": "firstEventTitle", + "expr": "datum.eventTitles && datum.eventTitles.length > 0 ? datum.eventTitles[0] : null" + }, + { + "type": "formula", + "as": "firstEventTime", + "expr": "datum.eventTimes && datum.eventTimes.length > 0 ? datum.eventTimes[0] : null" } ] } @@ -407,7 +417,7 @@ "$then": { "div": { "$attr": { - "style": "font-size: 0.75em; color: #666" + "style": "font-size: 0.75em; color: #666; margin-top: 2px" }, "$children": [ "{{eventCount}} event(s)" @@ -418,16 +428,31 @@ }, { "$if": { - "$check": "eventTitles", + "$check": "firstEventTitle", "$then": { - "div": { - "$attr": { - "style": "font-size: 0.7em; margin-top: 2px; color: #333" + "div": [ + { + "span": { + "$attr": { + "style": "font-size: 0.7em; color: #999" + }, + "$children": [ + "{{firstEventTime}}" + ] + } }, - "$children": [ - "{{eventTitles.0}}" - ] - } + " ", + { + "span": { + "$attr": { + "style": "font-size: 0.7em; color: #333" + }, + "$children": [ + "{{firstEventTitle}}" + ] + } + } + ] } } } From 9de3b00b1a640355db68beb0d6d115c863f55b44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:20:58 +0000 Subject: [PATCH 11/12] Fix treebark template - remove $attr, add proper loop over eventTitles, add CSS styling Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 67 +++++++++---------- 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index 0fd3b1bc..0e1fb5ea 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -402,24 +402,16 @@ "$then": { "div": [ { - "div": { - "$attr": { - "style": "font-weight: bold; margin-bottom: 4px" - }, - "$children": [ - "{{dayOfMonth}}" - ] - } + "strong": [ + "{{dayOfMonth}}" + ] }, { "$if": { "$check": "eventCount", "$then": { "div": { - "$attr": { - "style": "font-size: 0.75em; color: #666; margin-top: 2px" - }, - "$children": [ + "small": [ "{{eventCount}} event(s)" ] } @@ -428,31 +420,20 @@ }, { "$if": { - "$check": "firstEventTitle", + "$check": "eventTitles", "$then": { - "div": [ - { - "span": { - "$attr": { - "style": "font-size: 0.7em; color: #999" - }, - "$children": [ - "{{firstEventTime}}" - ] - } - }, - " ", - { - "span": { - "$attr": { - "style": "font-size: 0.7em; color: #333" - }, - "$children": [ - "{{firstEventTitle}}" - ] + "div": { + "$bind": "eventTitles", + "$children": [ + { + "div": { + "small": [ + "{{.}}" + ] + } } - } - ] + ] + } } } } @@ -469,7 +450,8 @@ ] } } - ] + ], + "class": "calendar-wrapper" } ] } @@ -797,5 +779,16 @@ } ] } - ] + ], + "style": { + "css": [ + "body { font-family: system-ui, -apple-system, sans-serif; }", + ".calendar-wrapper table { border-collapse: collapse; width: 100%; }", + ".calendar-wrapper th, .calendar-wrapper td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }", + ".calendar-wrapper th { background-color: #f5f5f5; font-weight: bold; text-align: center; }", + ".calendar-wrapper td { min-height: 80px; }", + ".calendar-wrapper strong { font-size: 1.2em; display: block; margin-bottom: 4px; }", + ".calendar-wrapper small { font-size: 0.75em; color: #666; display: block; margin-top: 2px; }" + ] + } } From 1c42ef3c17e302ebe88731e8e14510dc810d8e45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:32:38 +0000 Subject: [PATCH 12/12] Revert CSS changes and add eventsAsObjects step to combine event properties Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com> --- .../json/month-calendar-with-events.idoc.json | 118 +++++++++++------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/packages/web-deploy/json/month-calendar-with-events.idoc.json b/packages/web-deploy/json/month-calendar-with-events.idoc.json index 0e1fb5ea..44af308e 100644 --- a/packages/web-deploy/json/month-calendar-with-events.idoc.json +++ b/packages/web-deploy/json/month-calendar-with-events.idoc.json @@ -168,6 +168,53 @@ ] } }, + { + "variableId": "eventsAsObjects", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dataSourceNames": [ + "eventsGrouped" + ], + "dataFrameTransformations": [ + { + "type": "formula", + "as": "events", + "expr": "datum.times ? sequence(0, length(datum.times) - 1) : []" + }, + { + "type": "flatten", + "fields": [ + "events" + ], + "as": [ + "eventIndex" + ] + }, + { + "type": "formula", + "as": "event", + "expr": "{time: datum.times[datum.eventIndex], title: datum.titles[datum.eventIndex], category: datum.categories[datum.eventIndex]}" + }, + { + "type": "aggregate", + "groupby": [ + "date" + ], + "ops": [ + "values" + ], + "fields": [ + "event" + ], + "as": [ + "eventObjects" + ] + } + ] + } + }, { "variableId": "calendarWithEvents", "type": "object", @@ -176,46 +223,27 @@ "calculation": { "dataSourceNames": [ "calendarMonthList", - "eventsGrouped" + "eventsAsObjects" ], "dataFrameTransformations": [ { "type": "lookup", - "from": "eventsGrouped", + "from": "eventsAsObjects", "key": "date", "fields": [ "dateKey" ], "values": [ - "times", - "titles", - "categories" + "eventObjects" ], "as": [ - "eventTimes", - "eventTitles", - "eventCategories" + "events" ] }, { "type": "formula", "as": "eventCount", - "expr": "datum.eventTimes ? datum.eventTimes.length : 0" - }, - { - "type": "formula", - "as": "event1", - "expr": "datum.eventTimes && datum.eventTimes.length > 0 ? datum.eventTimes[0] + ' ' + datum.eventTitles[0] : null" - }, - { - "type": "formula", - "as": "firstEventTitle", - "expr": "datum.eventTitles && datum.eventTitles.length > 0 ? datum.eventTitles[0] : null" - }, - { - "type": "formula", - "as": "firstEventTime", - "expr": "datum.eventTimes && datum.eventTimes.length > 0 ? datum.eventTimes[0] : null" + "expr": "datum.events ? datum.events.length : 0" } ] } @@ -299,7 +327,7 @@ { "groupId": "step1", "elements": [ - "## Step 2: All Days of Calendar Surrounding the Selected Month", + "## 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", @@ -315,7 +343,7 @@ { "groupId": "step2", "elements": [ - "## Step 3: Events Grouped by Date", + "## Step 2: Events Grouped by Date", "Using `aggregate` transform to group multiple events per day into arrays.", { "type": "tabulator", @@ -328,6 +356,22 @@ } ] }, + { + "groupId": "step2b", + "elements": [ + "## Step 3: Events as Objects", + "Converting the grouped events into an array of event objects for easier iteration.", + { + "type": "tabulator", + "dataSourceName": "eventsAsObjects", + "tabulatorOptions": { + "autoColumns": true, + "layout": "fitColumns", + "maxHeight": "300px" + } + } + ] + }, { "groupId": "step3", "elements": [ @@ -420,15 +464,15 @@ }, { "$if": { - "$check": "eventTitles", + "$check": "events", "$then": { "div": { - "$bind": "eventTitles", + "$bind": "events", "$children": [ { "div": { "small": [ - "{{.}}" + "{{time}} - {{title}}" ] } } @@ -450,8 +494,7 @@ ] } } - ], - "class": "calendar-wrapper" + ] } ] } @@ -779,16 +822,5 @@ } ] } - ], - "style": { - "css": [ - "body { font-family: system-ui, -apple-system, sans-serif; }", - ".calendar-wrapper table { border-collapse: collapse; width: 100%; }", - ".calendar-wrapper th, .calendar-wrapper td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }", - ".calendar-wrapper th { background-color: #f5f5f5; font-weight: bold; text-align: center; }", - ".calendar-wrapper td { min-height: 80px; }", - ".calendar-wrapper strong { font-size: 1.2em; display: block; margin-bottom: 4px; }", - ".calendar-wrapper small { font-size: 0.75em; color: #666; display: block; margin-top: 2px; }" - ] - } + ] }