diff --git a/docs/YAML_SUPPORT.md b/docs/YAML_SUPPORT.md new file mode 100644 index 00000000..1fd1a6df --- /dev/null +++ b/docs/YAML_SUPPORT.md @@ -0,0 +1,46 @@ +# YAML Support + +The chartifact markdown package now supports YAML as an alternative to JSON in all plugin blocks. + +## Quick Start + +Instead of using `json` prefix, you can now use `yaml`: + +```yaml vega-lite +$schema: "https://vega.github.io/schema/vega-lite/v5.json" +description: "A simple bar chart" +data: + values: + - category: "A" + amount: 28 + - category: "B" + amount: 55 +mark: "bar" +encoding: + x: + field: "category" + type: "nominal" + y: + field: "amount" + type: "quantitative" +``` + +## Supported Formats + +All plugins support both JSON and YAML: +- `yaml vega` / `json vega` +- `yaml vega-lite` / `json vega-lite` +- `yaml textbox` / `json textbox` +- `yaml slider` / `json slider` +- `yaml dropdown` / `json dropdown` +- `yaml checkbox` / `json checkbox` +- And all other plugins... + +## Benefits + +- More readable and concise syntax +- Better for complex nested structures +- Familiar format for configuration files +- Full backward compatibility with JSON + +See the [full documentation](./yaml-support-docs.md) for detailed examples and migration guide. \ No newline at end of file diff --git a/docs/assets/examples/json/features/2.data-sources.idoc.json b/docs/assets/examples/json/features/2.data-sources.idoc.json index f1f31157..b2000d02 100644 --- a/docs/assets/examples/json/features/2.data-sources.idoc.json +++ b/docs/assets/examples/json/features/2.data-sources.idoc.json @@ -126,7 +126,7 @@ "## JSON Data", "Load data from a static JSON array.", { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonData", "variableId": "jsonTable", "tabulatorOptions": { @@ -143,14 +143,8 @@ "## CSV from URL", "Load CSV data from a fixed URL.", { - "type": "table", - "dataSourceName": "csvData", - "variableId": "csvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "csvData" } ] }, @@ -169,7 +163,7 @@ ] }, { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonUrlVariable", "variableId": "jsonUrlTable", "tabulatorOptions": { @@ -186,14 +180,8 @@ "## Inline CSV Data", "You can provide CSV data directly as a single string.", { - "type": "table", - "dataSourceName": "inlineCsvData", - "variableId": "inlineCsvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "100px" - } + "type": "tabulator", + "dataSourceName": "inlineCsvData" } ] } diff --git a/docs/assets/examples/json/features/4.table.idoc.json b/docs/assets/examples/json/features/4.table.idoc.json index edf237f3..45b66d3b 100644 --- a/docs/assets/examples/json/features/4.table.idoc.json +++ b/docs/assets/examples/json/features/4.table.idoc.json @@ -37,20 +37,13 @@ "## Table\nUse tables for displaying and interacting with tabular data.", "### Basic Table", { - "type": "table", - "dataSourceName": "sampleData", - "variableId": "table1", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "sampleData" }, "### Selectable Table", { - "type": "table", + "type": "tabulator", "dataSourceName": "sampleData", - "variableId": "table2", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", diff --git a/docs/assets/examples/json/features/6.data-transformations.idoc.json b/docs/assets/examples/json/features/6.data-transformations.idoc.json index d3beb8e2..61a2d2fb 100644 --- a/docs/assets/examples/json/features/6.data-transformations.idoc.json +++ b/docs/assets/examples/json/features/6.data-transformations.idoc.json @@ -99,25 +99,13 @@ }, "### Filtered Products (in stock, price ≤ ${{maxPrice}})", { - "type": "table", - "dataSourceName": "rawData", - "variableId": "filteredTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "rawData" }, "### Category Statistics", { - "type": "table", - "dataSourceName": "categoryStats", - "variableId": "categoryStatsTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "150px" - } + "type": "tabulator", + "dataSourceName": "categoryStats" } ] } diff --git a/docs/assets/examples/json/features/7.dropdown.idoc.json b/docs/assets/examples/json/features/7.dropdown.idoc.json index 77185655..ea668e91 100644 --- a/docs/assets/examples/json/features/7.dropdown.idoc.json +++ b/docs/assets/examples/json/features/7.dropdown.idoc.json @@ -66,14 +66,8 @@ "Selected: **{{selectedProduct}}**", "### Product Data", { - "type": "table", - "dataSourceName": "productData", - "variableId": "productTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "productData" } ] } diff --git a/docs/assets/examples/json/features/9.mermaid.idoc.json b/docs/assets/examples/json/features/9.mermaid.idoc.json index 76f591ae..be3689a2 100644 --- a/docs/assets/examples/json/features/9.mermaid.idoc.json +++ b/docs/assets/examples/json/features/9.mermaid.idoc.json @@ -122,7 +122,7 @@ "## Data-Driven Mode", "Template-based diagram generation:", { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonData", "variableId": "jsonTable", "editable": true, @@ -193,7 +193,7 @@ "## More Complex Example", "Network diagram with servers and connections:", { - "type": "table", + "type": "tabulator", "dataSourceName": "networkData", "variableId": "networkTable", "editable": true, diff --git a/docs/assets/examples/json/grocery-list.idoc.json b/docs/assets/examples/json/grocery-list.idoc.json index 3bc1a496..77d032d1 100644 --- a/docs/assets/examples/json/grocery-list.idoc.json +++ b/docs/assets/examples/json/grocery-list.idoc.json @@ -171,7 +171,7 @@ "elements": [ "## Select the items you want to buy\n", { - "type": "table", + "type": "tabulator", "variableId": "itemsData_selected", "dataSourceName": "itemsData", "tabulatorOptions": { diff --git a/docs/assets/examples/json/mermaid-org chart.idoc.json b/docs/assets/examples/json/mermaid-org chart.idoc.json index 0a9335f9..cddee1b1 100644 --- a/docs/assets/examples/json/mermaid-org chart.idoc.json +++ b/docs/assets/examples/json/mermaid-org chart.idoc.json @@ -156,7 +156,7 @@ "## JSON Data", "Load data from a static JSON array.", { - "type": "table", + "type": "tabulator", "dataSourceName": "orgChartData", "variableId": "orgChartDataTable", "editable": true, diff --git a/docs/assets/examples/json/sales-dashboard.idoc.json b/docs/assets/examples/json/sales-dashboard.idoc.json index d2ff61a1..3f6e7864 100644 --- a/docs/assets/examples/json/sales-dashboard.idoc.json +++ b/docs/assets/examples/json/sales-dashboard.idoc.json @@ -231,9 +231,8 @@ "elements": [ "### Sales Data", { - "type": "table", - "dataSourceName": "salesData", - "variableId": "salesSelected" + "type": "tabulator", + "dataSourceName": "salesData" } ] } diff --git a/docs/assets/examples/json/sales-report.idoc.json b/docs/assets/examples/json/sales-report.idoc.json new file mode 100644 index 00000000..6b753354 --- /dev/null +++ b/docs/assets/examples/json/sales-report.idoc.json @@ -0,0 +1,320 @@ +{ + "$schema": "https://microsoft.github.io/chartifact/schema/idoc_v1.json", + "title": "Sales Performance Report", + "style": { + "css": [ + "body { font-family: 'Times New Roman', serif; margin: 0; padding: 40px; background: white; line-height: 1.6; color: #333; }", + ".group { max-width: 800px; margin: 0 auto; }", + "h1 { text-align: center; font-size: 2.5em; margin-bottom: 0.5em; color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 20px; }", + "h2 { font-size: 1.8em; margin: 40px 0 20px 0; color: #2c3e50; border-bottom: 1px solid #bdc3c7; padding-bottom: 10px; }", + "h3 { font-size: 1.3em; margin: 30px 0 15px 0; color: #34495e; }", + "blockquote { background: #ecf0f1; padding: 20px; border-left: 5px solid #3498db; margin: 30px 0; font-style: normal; }", + "table { width: 100%; border-collapse: collapse; margin: 20px 0; }", + "td { padding: 10px 20px; border-bottom: 1px solid #ecf0f1; }", + "td:first-child { font-weight: bold; color: #2c3e50; }", + ".chart-container { margin: 30px 0; padding: 20px; background: #fafafa; border: 1px solid #ecf0f1; border-radius: 5px; }", + "p { margin: 15px 0; text-align: justify; }" + ] + }, + "dataLoaders": [ + { + "dataSourceName": "salesData", + "type": "inline", + "format": "json", + "content": [ + { + "timestamp": "2025-08-01", + "order_id": "ORD-1001", + "product": "Wireless Mouse", + "category": "Electronics", + "region": "West", + "salesperson": "Jane Smith", + "units": 3, + "unit_price": 25 + }, + { + "timestamp": "2025-08-02", + "order_id": "ORD-1002", + "product": "Office Chair", + "category": "Furniture", + "region": "East", + "salesperson": "Bob Johnson", + "units": 1, + "unit_price": 199.99 + }, + { + "timestamp": "2025-08-03", + "order_id": "ORD-1003", + "product": "Laptop Stand", + "category": "Electronics", + "region": "North", + "salesperson": "Alice Chen", + "units": 2, + "unit_price": 45.5 + }, + { + "timestamp": "2025-08-05", + "order_id": "ORD-1004", + "product": "Desk Lamp", + "category": "Furniture", + "region": "South", + "salesperson": "Charlie Brown", + "units": 1, + "unit_price": 89 + }, + { + "timestamp": "2025-08-06", + "order_id": "ORD-1005", + "product": "Bluetooth Headphones", + "category": "Electronics", + "region": "West", + "salesperson": "Jane Smith", + "units": 4, + "unit_price": 79.99 + } + ] + } + ], + "variables": [ + { + "variableId": "revenueCalculation", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dependsOn": [ + "salesData" + ], + "vegaExpression": "data('salesData')", + "dataFrameTransformations": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "ops": [ + "sum" + ], + "fields": [ + "revenue" + ], + "as": [ + "total" + ] + } + ] + } + }, + { + "variableId": "totalRevenueFormatted", + "type": "string", + "initialValue": "$0", + "calculation": { + "dependsOn": [ + "revenueCalculation" + ], + "vegaExpression": "'$' + format(data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0, ',.2f')" + } + }, + { + "variableId": "totalOrders", + "type": "number", + "initialValue": 0, + "calculation": { + "dependsOn": [ + "salesData" + ], + "vegaExpression": "length(data('salesData'))" + } + }, + { + "variableId": "averageOrderValue", + "type": "string", + "initialValue": "$0.00", + "calculation": { + "dependsOn": [ + "revenueCalculation", + "totalOrders" + ], + "vegaExpression": "'$' + format((data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0) / (totalOrders > 0 ? totalOrders : 1), ',.2f')" + } + }, + { + "variableId": "categoryRevenue", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dependsOn": [ + "salesData" + ], + "vegaExpression": "data('salesData')", + "dataFrameTransformations": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "groupby": [ + "category" + ], + "ops": [ + "sum" + ], + "fields": [ + "revenue" + ], + "as": [ + "total_revenue" + ] + } + ] + } + } + ], + "groups": [ + { + "groupId": "main", + "elements": [ + "# Sales Performance Report", + "**Reporting Period:** August 1-6, 2025 ", + "**Prepared by:** Sales Analytics Team ", + "**Date:** August 24, 2025", + "", + "## Executive Summary", + "", + "> This report analyzes sales performance for the first week of August 2025. Our analysis reveals total revenue of **{{totalRevenueFormatted}}** across **{{totalOrders}} transactions**, with an average order value of **{{averageOrderValue}}**. The data shows strong performance in the Electronics category, which represents the majority of our revenue during this period.", + "", + "## Key Performance Metrics", + "", + "The following table summarizes our core performance indicators for the reporting period:", + "", + "| Metric | Value |", + "|--------|-------|", + "| Total Revenue | {{totalRevenueFormatted}} |", + "| Number of Orders | {{totalOrders}} |", + "| Average Order Value | {{averageOrderValue}} |", + "| Active Sales Regions | 4 regions |", + "| Product Categories | 2 categories |", + "", + "## Revenue Analysis by Product Category", + "", + "Our product portfolio performed differently across categories during this period. The chart below illustrates the revenue distribution:", + "", + { + "type": "chart", + "chartKey": "categoryChart" + }, + "", + "The Electronics category generated the majority of revenue, driven primarily by strong sales of Bluetooth Headphones and Wireless Mouse products. The Furniture category, while smaller in volume, contributed significant value through high-ticket items such as the Office Chair.", + "", + "## Sales Trend Analysis", + "", + "Daily sales performance shows variability throughout the reporting period, with notable patterns emerging:", + "", + { + "type": "chart", + "chartKey": "trendChart" + }, + "", + "The trend analysis reveals that August 6th recorded the highest single-day revenue of $319.96, primarily due to the sale of 4 units of Bluetooth Headphones. August 2nd also showed strong performance with $199.99 in revenue from the Office Chair sale.", + "", + "## Detailed Transaction Data", + "", + "The complete transaction dataset for the reporting period is presented below for reference and further analysis:", + "", + { + "type": "tabulator", + "dataSourceName": "salesData" + }, + "", + "## Conclusions and Recommendations", + "", + "Based on this analysis, we recommend:", + "", + "1. **Focus on Electronics expansion** - Given the strong performance of electronics products, consider expanding this category's inventory and marketing focus.", + "", + "2. **Leverage high-value furniture sales** - While furniture has lower transaction volume, the high average order values suggest opportunity for targeted premium product strategies.", + "", + "3. **Regional performance review** - Jane Smith's performance in the West region generated strong results and could serve as a model for other regions.", + "", + "4. **Inventory planning** - The Bluetooth Headphones' strong performance suggests increasing stock levels for similar high-margin electronics.", + "", + "*This report was generated using interactive data analysis. All figures are calculated dynamically from the underlying transaction data.*" + ] + } + ], + "resources": { + "charts": { + "categoryChart": { + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "categoryRevenue" + }, + "mark": "bar", + "width": 400, + "height": 250, + "encoding": { + "x": { + "field": "category", + "type": "nominal", + "title": "Product Category" + }, + "y": { + "field": "total_revenue", + "type": "quantitative", + "title": "Revenue ($)" + }, + "color": { + "field": "category", + "type": "nominal", + "scale": { + "range": [ + "#3498db", + "#2c3e50" + ] + } + } + } + }, + "trendChart": { + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "salesData" + }, + "transform": [ + { + "calculate": "datum.units * datum.unit_price", + "as": "revenue" + } + ], + "mark": { + "type": "line", + "point": true, + "strokeWidth": 2 + }, + "width": 500, + "height": 250, + "encoding": { + "x": { + "field": "timestamp", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "revenue", + "type": "quantitative", + "title": "Daily Revenue ($)" + }, + "color": { + "value": "#3498db" + } + } + } + } + } +} \ No newline at end of file diff --git a/docs/assets/examples/json/seattle-weather/4.idoc.json b/docs/assets/examples/json/seattle-weather/4.idoc.json index b4c3e26c..57a23a83 100644 --- a/docs/assets/examples/json/seattle-weather/4.idoc.json +++ b/docs/assets/examples/json/seattle-weather/4.idoc.json @@ -16,9 +16,8 @@ "elements": [ "# Seattle Weather\n\nData table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/seattle-weather/5.idoc.json b/docs/assets/examples/json/seattle-weather/5.idoc.json index 03a973af..10254452 100644 --- a/docs/assets/examples/json/seattle-weather/5.idoc.json +++ b/docs/assets/examples/json/seattle-weather/5.idoc.json @@ -21,9 +21,8 @@ "elements": [ "# Seattle Weather\n\nData table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/seattle-weather/6.idoc.json b/docs/assets/examples/json/seattle-weather/6.idoc.json index cc4cf96f..abe2fb41 100644 --- a/docs/assets/examples/json/seattle-weather/6.idoc.json +++ b/docs/assets/examples/json/seattle-weather/6.idoc.json @@ -34,9 +34,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/seattle-weather/7.idoc.json b/docs/assets/examples/json/seattle-weather/7.idoc.json index 7e2eb715..ec6cc260 100644 --- a/docs/assets/examples/json/seattle-weather/7.idoc.json +++ b/docs/assets/examples/json/seattle-weather/7.idoc.json @@ -90,9 +90,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/seattle-weather/8.idoc.json b/docs/assets/examples/json/seattle-weather/8.idoc.json index 505d94ab..80d3444b 100644 --- a/docs/assets/examples/json/seattle-weather/8.idoc.json +++ b/docs/assets/examples/json/seattle-weather/8.idoc.json @@ -120,9 +120,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/seattle-weather/9.idoc.json b/docs/assets/examples/json/seattle-weather/9.idoc.json index d154097b..c307a1fb 100644 --- a/docs/assets/examples/json/seattle-weather/9.idoc.json +++ b/docs/assets/examples/json/seattle-weather/9.idoc.json @@ -186,9 +186,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/docs/assets/examples/json/slides.idoc.json b/docs/assets/examples/json/slides.idoc.json index 80e8cbcf..f736cbe5 100644 --- a/docs/assets/examples/json/slides.idoc.json +++ b/docs/assets/examples/json/slides.idoc.json @@ -203,7 +203,7 @@ "elements": [ "## Select the items you want to buy\n", { - "type": "table", + "type": "tabulator", "dataSourceName": "itemsData", "variableId": "itemsData_selected", "tabulatorOptions": { diff --git a/docs/assets/examples/markdown/features/1.primer.idoc.md b/docs/assets/examples/markdown/features/1.primer.idoc.md index e4d8f8e4..a171d045 100644 --- a/docs/assets/examples/markdown/features/1.primer.idoc.md +++ b/docs/assets/examples/markdown/features/1.primer.idoc.md @@ -52,111 +52,91 @@ } ``` -# Chartifact Primer +# Chartifact Primer Explore the features below and **view source** to see how markdown elements are used throughout this page. - --- - ### Markdown - Chartifact supports markdown for formatting text, headings, lists, and more. HTML tags are not allowed. - **Bold**, *italic*, `code`, and [links](https://microsoft.com). - 1. Numbered item 2. Another item - - Bullet point - Another bullet - Variables can also be used inside image URLs (`![alt text]({{variableName}})`) and hyperlink URLs (`[link text]({{variableName}})`). - --- - ### Variables - Variables store values you can reference in markdown using `{{variableName}}`. They can be strings, numbers, booleans, arrays, objects, or calculated values. - - **String:** Hello, **{{userName}}**! - - **Number:** Temperature is **{{temperature}}°C**. - - **Boolean:** Feature enabled: **{{isEnabled}}**. - - **Array:** Selected items: **{{selectedItems}}**. - - **Object:** Foo is **{{obj_foo}}**, value is **{{obj_value}}**. - - **String:** Favorite color: **{{selectedColor}}**. - --- - ### Calculated Variables - Calculated variables derive their value from other variables using [Vega's expression language](https://vega.github.io/vega/docs/expressions/). - - **Doubled temperature:** **{{doubled_temperature}}** (calculated from `temperature`) - Note: Some variable names are reserved and cannot be used (e.g. `datum`, `event`, etc.). - --- - ### Input Controls - Input controls let users change variable values interactively. Changes are instantly reflected in markdown and calculations above. -```json textbox -{"variableId":"userName","value":"Alice","label":"Your name"} + +```yaml textbox +variableId: userName +value: Alice +label: Your name ``` -```json slider -{"variableId":"temperature","value":20,"label":"Temperature (°C)","min":-10,"max":40,"step":1} + +```yaml slider +variableId: temperature +value: 20 +label: Temperature (°C) +min: -10 +max: 40 +step: 1 ``` -```json checkbox -{"variableId":"isEnabled","value":true,"label":"Enable feature"} + +```yaml checkbox +variableId: isEnabled +value: true +label: Enable feature ``` -```json dropdown -{ - "variableId": "selectedColor", - "value": "blue", - "label": "Favorite color", - "options": [ - "red", - "green", - "blue", - "yellow" - ] -} + +```yaml dropdown +variableId: selectedColor +value: blue +label: Favorite color +options: + - red + - green + - blue + - yellow ``` -```json dropdown -{ - "variableId": "selectedItems", - "value": [ - "apple", - "banana" - ], - "label": "Pick fruits", - "options": [ - "apple", - "banana", - "orange", - "grape" - ], - "multiple": true, - "size": 3 -} + +```yaml dropdown +variableId: selectedItems +value: + - apple + - banana +label: Pick fruits +options: + - apple + - banana + - orange + - grape +multiple: true +size: 3 ``` ---- +--- ### Tips - - Use variables in any markdown context, including headings and lists. - - Calculated variables update automatically when their dependencies change. - - Only simple variable references are supported (e.g. `{{variableName}}`). \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/10.images.idoc.md b/docs/assets/examples/markdown/features/10.images.idoc.md index 505ff691..cdf331e6 100644 --- a/docs/assets/examples/markdown/features/10.images.idoc.md +++ b/docs/assets/examples/markdown/features/10.images.idoc.md @@ -31,160 +31,128 @@ } ``` -## Server-Generated Images +## Server-Generated Images Images are particularly powerful for server-side generated visualizations: - - **Python plots** - matplotlib, seaborn, plotly exports - - **R visualizations** - ggplot2, base R graphics - - **Dynamic charts** - generated based on current data - - **Custom graphics** - any server-side image generation - - Example server endpoint: `/api/regressionplot?target={{targetVariable}}&model={{modelType}}&theme={{colorTheme}}` - - Images support dynamic URLs with query parameters and can be regenerated in real-time. - *samples below provided by [picsum.photos](https://picsum.photos/)* ## Complete URL Variable (using image plugin) - You can use a single variable for the entire image URL. No URL encoding is applied to the value. -```json dropdown -{ - "variableId": "img_url", - "value": "https://picsum.photos/400/300", - "options": [ - "https://picsum.photos/100/100", - "https://picsum.photos/200/200", - "https://picsum.photos/300/300", - "https://picsum.photos/400/300", - "https://picsum.photos/400/400" - ] -} + +```yaml dropdown +variableId: img_url +value: https://picsum.photos/400/300 +options: + - https://picsum.photos/100/100 + - https://picsum.photos/200/200 + - https://picsum.photos/300/300 + - https://picsum.photos/400/300 + - https://picsum.photos/400/400 ``` -```json image -{ - "url": "{{img_url}}", - "alt": "Complete URL variable example" -} + +```yaml image +url: '{{img_url}}' +alt: Complete URL variable example ``` -## URL Segments (using image plugin) +## URL Segments (using image plugin) You can construct image URLs from multiple variables. Each segment is encoded using `encodeURIComponent`. -```json dropdown -{ - "variableId": "img_height", - "value": "300", - "options": [ - "100", - "200", - "300", - "400" - ] -} + +```yaml dropdown +variableId: img_height +value: '300' +options: + - '100' + - '200' + - '300' + - '400' ``` -```json dropdown -{ - "variableId": "img_width", - "value": "400", - "options": [ - "100", - "200", - "300", - "400" - ] -} + +```yaml dropdown +variableId: img_width +value: '400' +options: + - '100' + - '200' + - '300' + - '400' ``` -```json image -{ - "url": "https://picsum.photos/{{img_width}}/{{img_height}}", - "alt": "URL segments example" -} + +```yaml image +url: https://picsum.photos/{{img_width}}/{{img_height}} +alt: URL segments example ``` -## Markdown Images - Static +## Markdown Images - Static Simple markdown image syntax for static images. - ![Static picsum image](https://picsum.photos/300/200 "Static 300x200 image") - This uses standard markdown: `![alt text](url "title")` ## Markdown Images - Complete URL Variable - Markdown images with dynamic complete URL (no encoding applied to variable value). -```json dropdown -{ - "variableId": "md_img_url", - "value": "https://picsum.photos/400/300", - "options": [ - "https://picsum.photos/150/150", - "https://picsum.photos/250/250", - "https://picsum.photos/350/350", - "https://picsum.photos/400/300" - ] -} + +```yaml dropdown +variableId: md_img_url +value: https://picsum.photos/400/300 +options: + - https://picsum.photos/150/150 + - https://picsum.photos/250/250 + - https://picsum.photos/350/350 + - https://picsum.photos/400/300 ``` -![Complete URL variable]({{md_img_url}} "Dynamic image from complete URL variable") +![Complete URL variable]({{md_img_url}} "Dynamic image from complete URL variable") This uses: `![alt]({{variable_name}} "title")` ## Markdown Images - URL Segments - Markdown images with URL constructed from multiple variables (each variable gets `encodeURIComponent` applied). -```json dropdown -{ - "variableId": "md_img_width", - "value": "300", - "options": [ - "150", - "200", - "250", - "300", - "350" - ] -} + +```yaml dropdown +variableId: md_img_width +value: '300' +options: + - '150' + - '200' + - '250' + - '300' + - '350' ``` -```json dropdown -{ - "variableId": "md_img_height", - "value": "200", - "options": [ - "100", - "150", - "200", - "250" - ] -} + +```yaml dropdown +variableId: md_img_height +value: '200' +options: + - '100' + - '150' + - '200' + - '250' ``` -![URL segments](https://picsum.photos/{{md_img_width}}/{{md_img_height}} "Dynamic {{md_img_width}}x{{md_img_height}} image") +![URL segments](https://picsum.photos/{{md_img_width}}/{{md_img_height}} "Dynamic {{md_img_width}}x{{md_img_height}} image") This uses: `![alt](https://example.com/{{var1}}/{{var2}} "title")` - Note: Markdown images don't support width/height attributes - use the JSON image component for that. ## Markdown Inline Image - You can use markdown inline images within text: - Here is an inline image ![inline](https://picsum.photos/40/40 "Inline 40x40") in a sentence. - Syntax: `Here is an inline image ![inline](url "title") in a sentence.` \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/2.data-sources.idoc.md b/docs/assets/examples/markdown/features/2.data-sources.idoc.md index d367e076..165fc2a8 100644 --- a/docs/assets/examples/markdown/features/2.data-sources.idoc.md +++ b/docs/assets/examples/markdown/features/2.data-sources.idoc.md @@ -7,18 +7,10 @@ "name": "jsonTable", "update": "data('jsonTable')" }, - { - "name": "csvTable", - "update": "data('csvTable')" - }, { "name": "jsonUrlTable", "update": "data('jsonUrlTable')" }, - { - "name": "inlineCsvTable", - "update": "data('inlineCsvTable')" - }, { "name": "json_url", "value": "https://vega.github.io/editor/data/barley.json" @@ -167,18 +159,10 @@ } ] }, - { - "name": "inlineCsvTable", - "values": [] - }, { "name": "jsonUrlTable", "values": [] }, - { - "name": "csvTable", - "values": [] - }, { "name": "jsonTable", "values": [] @@ -187,79 +171,68 @@ } ``` -## JSON Data +## JSON Data Load data from a static JSON array. + ```json tabulator { "dataSourceName": "jsonData", - "variableId": "jsonTable", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", "maxHeight": "100px" - } + }, + "variableId": "jsonTable" } ``` -## CSV from URL +## CSV from URL Load CSV data from a fixed URL. + ```json tabulator { - "dataSourceName": "csvData", - "variableId": "csvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "dataSourceName": "csvData" } ``` -## Complete URL Variable (JSON) +## Complete URL Variable (JSON) You can use a single variable for the entire JSON URL. No URL encoding is applied to the value. - Note: You can also construct URLs from multiple segments (e.g., `{{host}}/{{path}}`). In that case, each segment is encoded using `encodeURIComponent`. -```json dropdown -{ - "variableId": "json_url", - "value": "https://vega.github.io/editor/data/barley.json", - "options": [ - "https://vega.github.io/editor/data/barley.json", - "https://vega.github.io/editor/data/cars.json" - ] -} + +```yaml dropdown +variableId: json_url +value: https://vega.github.io/editor/data/barley.json +options: + - https://vega.github.io/editor/data/barley.json + - https://vega.github.io/editor/data/cars.json ``` + ```json tabulator { "dataSourceName": "jsonUrlVariable", - "variableId": "jsonUrlTable", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", "maxHeight": "200px" - } + }, + "variableId": "jsonUrlTable" } ``` -## Inline CSV Data +## Inline CSV Data You can provide CSV data directly as a single string. + ```json tabulator { - "dataSourceName": "inlineCsvData", - "variableId": "inlineCsvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "100px" - } + "dataSourceName": "inlineCsvData" } ``` \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/3.chart.idoc.md b/docs/assets/examples/markdown/features/3.chart.idoc.md index 7f9df30a..c1ae74ba 100644 --- a/docs/assets/examples/markdown/features/3.chart.idoc.md +++ b/docs/assets/examples/markdown/features/3.chart.idoc.md @@ -38,9 +38,11 @@ } ``` + ## Chart Use charts for data visualizations with Vega-Lite specifications. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/features/4.table.idoc.md b/docs/assets/examples/markdown/features/4.table.idoc.md index e1a227ee..49f773e8 100644 --- a/docs/assets/examples/markdown/features/4.table.idoc.md +++ b/docs/assets/examples/markdown/features/4.table.idoc.md @@ -3,14 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "table1", - "update": "data('table1')" - }, - { - "name": "table2", - "update": "data('table2')" - }, { "name": "sampleData", "update": "data('sampleData')" @@ -41,42 +33,30 @@ "city": "Seattle" } ] - }, - { - "name": "table2", - "values": [] - }, - { - "name": "table1", - "values": [] } ] } ``` + ## Table Use tables for displaying and interacting with tabular data. - ### Basic Table + ```json tabulator { - "dataSourceName": "sampleData", - "variableId": "table1", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "dataSourceName": "sampleData" } ``` + ### Selectable Table + ```json tabulator { "dataSourceName": "sampleData", - "variableId": "table2", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", diff --git a/docs/assets/examples/markdown/features/5.styling.idoc.md b/docs/assets/examples/markdown/features/5.styling.idoc.md index e94bae64..177b4855 100644 --- a/docs/assets/examples/markdown/features/5.styling.idoc.md +++ b/docs/assets/examples/markdown/features/5.styling.idoc.md @@ -6,6 +6,7 @@ #demo h2 { color: #28a745; } ``` + ```json google-fonts { "googleFontsUrl": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Source+Code+Pro:wght@400;600&family=Inter:wght@300;400;500&family=Roboto+Slab:wght@400;700&display=swap", @@ -26,35 +27,28 @@ } ``` + ::: group {#main} + ## CSS Styling Use CSS to style documents with custom layouts and appearance. - ### How CSS Works - - Each group gets a `.group` className automatically - - Each group gets an `#id` based on the groupId - - Add custom CSS in the `layout.css` property - - Style headers, backgrounds, borders, and spacing - - CSS applies to all groups in the document ::: - ::: group {#demo} -## Demo Section +## Demo Section This section has `groupId="demo"` so it gets `id="demo"` and can be styled with `#demo`. - Notice this section has a green theme instead of blue, applied via the `#demo` CSS selector. ::: - ::: group {#google-fonts-demo} + ## Google Fonts Demo This section demonstrates the Google Fonts plugin with cascading font behavior and semantic mapping. - ```json google-fonts { "googleFontsUrl": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Source+Code+Pro:wght@400;600&family=Inter:wght@300;400;500&family=Roboto+Slab:wght@400;700&display=swap", @@ -74,43 +68,30 @@ This section demonstrates the Google Fonts plugin with cascading font behavior a } } ``` - # Hero Headline Level 1 - ## Hero Headline Level 2 - ### Hero Headline Level 3 - #### Regular Heading Level 4 - This is regular paragraph text that demonstrates the body font. Lorem ipsum dolor sit amet, consectetur adipiscing elit. - - Bulleted list item - Another list item - Third item - 1. Numbered list item one 2. Second numbered item 3. Third numbered item - **Bold text** and *italic text* also inherit the body font. - Here's some `inline code` and a code block: - ```javascript // Code blocks use the code font mapping function example() { console.log("Hello, World!"); } ``` - #### Tables - | Column 1 | Column 2 | Column 3 | |----------|----------|----------| | Cell 1 | Cell 2 | Cell 3 | | Data A | Data B | Data C | | Numbers | 123.45 | 678.90 | - This content lets you see how different fonts apply to different semantic elements without any distracting references to the font names. ::: \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/6.data-transformations.idoc.md b/docs/assets/examples/markdown/features/6.data-transformations.idoc.md index 61bc44c9..4b7c4a36 100644 --- a/docs/assets/examples/markdown/features/6.data-transformations.idoc.md +++ b/docs/assets/examples/markdown/features/6.data-transformations.idoc.md @@ -3,14 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "filteredTable", - "update": "data('filteredTable')" - }, - { - "name": "categoryStatsTable", - "update": "data('categoryStatsTable')" - }, { "name": "maxPrice", "value": 100 @@ -66,14 +58,6 @@ } ] }, - { - "name": "categoryStatsTable", - "values": [] - }, - { - "name": "filteredTable", - "values": [] - }, { "name": "categoryStats", "source": [ @@ -104,37 +88,36 @@ } ``` + ## Data Transformations Use data transformations to filter, aggregate, and manipulate data. -```json slider -{"variableId":"maxPrice","value":100,"label":"Maximum price filter:","min":0,"max":500,"step":10} + +```yaml slider +variableId: maxPrice +value: 100 +label: 'Maximum price filter:' +min: 0 +max: 500 +step: 10 ``` + ### Filtered Products (in stock, price ≤ ${{maxPrice}}) + ```json tabulator { - "dataSourceName": "rawData", - "variableId": "filteredTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "dataSourceName": "rawData" } ``` + ### Category Statistics + ```json tabulator { - "dataSourceName": "categoryStats", - "variableId": "categoryStatsTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "150px" - } + "dataSourceName": "categoryStats" } ``` \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/7.dropdown.idoc.md b/docs/assets/examples/markdown/features/7.dropdown.idoc.md index 123b5df5..0b1c31c9 100644 --- a/docs/assets/examples/markdown/features/7.dropdown.idoc.md +++ b/docs/assets/examples/markdown/features/7.dropdown.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "productTable", - "update": "data('productTable')" - }, { "name": "selectedProduct", "value": "Laptop Pro" @@ -51,46 +47,34 @@ "price": 25 } ] - }, - { - "name": "productTable", - "values": [] } ] } ``` + ## Dropdown Use dropdowns with data-driven options from data sources. - ### Data-Driven Options - Dropdown options populated from data using `dynamicOptions`: -```json dropdown -{ - "variableId": "selectedProduct", - "value": "Laptop Pro", - "label": "Choose product:", - "dynamicOptions": { - "dataSourceName": "productData", - "fieldName": "name" - } -} + +```yaml dropdown +variableId: selectedProduct +value: Laptop Pro +label: 'Choose product:' +dynamicOptions: + dataSourceName: productData + fieldName: name ``` -Selected: **{{selectedProduct}}** +Selected: **{{selectedProduct}}** ### Product Data + ```json tabulator { - "dataSourceName": "productData", - "variableId": "productTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "dataSourceName": "productData" } ``` \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/8.presets.idoc.md b/docs/assets/examples/markdown/features/8.presets.idoc.md index b241ee60..8c0d2d19 100644 --- a/docs/assets/examples/markdown/features/8.presets.idoc.md +++ b/docs/assets/examples/markdown/features/8.presets.idoc.md @@ -15,51 +15,47 @@ } ``` + ## Presets Use presets to provide predefined state configurations. -```json presets -[ - { - "name": "Small & Red", - "state": { - "size": 2, - "color": "red" - } - }, - { - "name": "Large & Green", - "state": { - "size": 10, - "color": "green" - } - }, - { - "name": "Medium & Gray", - "state": { - "size": 7, - "color": "gray" - } - } -] + +```yaml presets +- name: Small & Red + state: + size: 2 + color: red +- name: Large & Green + state: + size: 10 + color: green +- name: Medium & Gray + state: + size: 7 + color: gray ``` -```json slider -{"variableId":"size","value":5,"label":"Size:","min":1,"max":15,"step":1} + +```yaml slider +variableId: size +value: 5 +label: 'Size:' +min: 1 +max: 15 +step: 1 ``` -```json dropdown -{ - "variableId": "color", - "value": "blue", - "label": "Color:", - "options": [ - "red", - "green", - "blue", - "gray" - ] -} + +```yaml dropdown +variableId: color +value: blue +label: 'Color:' +options: + - red + - green + - blue + - gray ``` + Current state: Size **{{size}}**, Color **{{color}}** \ No newline at end of file diff --git a/docs/assets/examples/markdown/features/9.mermaid.idoc.md b/docs/assets/examples/markdown/features/9.mermaid.idoc.md index 91bd72ae..04625e1e 100644 --- a/docs/assets/examples/markdown/features/9.mermaid.idoc.md +++ b/docs/assets/examples/markdown/features/9.mermaid.idoc.md @@ -119,14 +119,14 @@ } ``` -# Mermaid Plugin Examples +# Mermaid Plugin Examples This page demonstrates the mermaid plugin in Chartifact, including raw text, data-driven, and dynamic modes. ## Raw Text Mode - Simple flowchart using raw mermaid syntax: + ```mermaid flowchart TD A[Start] --> B{Is it?} @@ -135,14 +135,14 @@ flowchart TD C --> D ``` -## Data-Driven Mode +## Data-Driven Mode Template-based diagram generation: + ```json tabulator { "dataSourceName": "jsonData", - "variableId": "jsonTable", "tabulatorOptions": { "columns": [ { @@ -186,39 +186,36 @@ Template-based diagram generation: "layout": "fitColumns", "maxHeight": "150px" }, - "editable": true + "editable": true, + "variableId": "jsonTable" } ``` -```json mermaid -{ - "template": { - "dataSourceName": "jsonTable", - "header": "flowchart TD", - "lineTemplates": { - "node": "{{id}}[{{label}}]", - "edge": "{{from}} --> {{to}}", - "labeledEdge": "{{from}} -->|{{label}}| {{to}}" - } - }, - "variableId": "flowchartOutput" -} + +```yaml mermaid +template: + dataSourceName: jsonTable + header: flowchart TD + lineTemplates: + node: '{{id}}[{{label}}]' + edge: '{{from}} --> {{to}}' + labeledEdge: '{{from}} -->|{{label}}| {{to}}' +variableId: flowchartOutput ``` -### Generated Mermaid Source: +### Generated Mermaid Source: ``` {{flowchartOutput}} ``` ## More Complex Example - Network diagram with servers and connections: + ```json tabulator { "dataSourceName": "networkData", - "variableId": "networkTable", "tabulatorOptions": { "columns": [ { @@ -264,39 +261,43 @@ Network diagram with servers and connections: "layout": "fitColumns", "maxHeight": "200px" }, - "editable": true + "editable": true, + "variableId": "networkTable" } ``` -```json mermaid -{ - "template": { - "dataSourceName": "networkTable", - "header": "graph LR", - "lineTemplates": { - "server": "{{id}}[{{name}}
{{ip}}]", - "connection": "{{from}} --- {{to}}", - "secureConnection": "{{from}} -.->|SSL| {{to}}", - "subgraph": "subgraph {{name}}", - "end": "end" - } - }, - "variableId": "networkOutput" -} + +```yaml mermaid +template: + dataSourceName: networkTable + header: graph LR + lineTemplates: + server: '{{id}}[{{name}}
{{ip}}]' + connection: '{{from}} --- {{to}}' + secureConnection: '{{from}} -.->|SSL| {{to}}' + subgraph: subgraph {{name}} + end: end +variableId: networkOutput ``` + ### Generated Network Diagram Source: -```json textbox -{"variableId":"networkOutput","value":"","multiline":true} + +```yaml textbox +variableId: networkOutput +value: '' +multiline: true ``` -## String Input Mode +## String Input Mode This example shows how to consume the generated Mermaid text from above and render it directly: -```json mermaid -{"variableId":"networkOutput"} + +```yaml mermaid +variableId: networkOutput ``` + This demonstrates the flexible input capability - the same `dataSourceName` property can handle string or array input. \ No newline at end of file diff --git a/docs/assets/examples/markdown/grocery-list.idoc.md b/docs/assets/examples/markdown/grocery-list.idoc.md index 2979f4c9..f3f3df39 100644 --- a/docs/assets/examples/markdown/grocery-list.idoc.md +++ b/docs/assets/examples/markdown/grocery-list.idoc.md @@ -175,13 +175,13 @@ } ``` + ## Select the items you want to buy ```json tabulator { "dataSourceName": "itemsData", - "variableId": "itemsData_selected", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", @@ -196,16 +196,19 @@ "hozAlign": "center", "width": 40 } - } + }, + "variableId": "itemsData_selected" } ``` + ## Total Price ${{total}} ### Categories + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/mermaid-org chart.idoc.md b/docs/assets/examples/markdown/mermaid-org chart.idoc.md index 4ff73510..15417aa9 100644 --- a/docs/assets/examples/markdown/mermaid-org chart.idoc.md +++ b/docs/assets/examples/markdown/mermaid-org chart.idoc.md @@ -139,43 +139,38 @@ } ``` -# Mermaid Plugin Example - Org Chart +# Mermaid Plugin Example - Org Chart Template-based org chart generation: -```json mermaid -{ - "template": { - "dataSourceName": "orgChartDataTable", - "header": "graph {{direction}}", - "lineTemplates": { - "person": "{{id}}[{{label}}]", - "orgEdge": "{{from}} --> {{to}}" - } - }, - "variableId": "orgChartOutput" -} + +```yaml mermaid +template: + dataSourceName: orgChartDataTable + header: graph {{direction}} + lineTemplates: + person: '{{id}}[{{label}}]' + orgEdge: '{{from}} --> {{to}}' +variableId: orgChartOutput ``` -```json dropdown -{ - "variableId": "direction", - "value": "TD", - "options": [ - "TD", - "LR" - ] -} + +```yaml dropdown +variableId: direction +value: TD +options: + - TD + - LR ``` -## JSON Data +## JSON Data Load data from a static JSON array. + ```json tabulator { "dataSourceName": "orgChartData", - "variableId": "orgChartDataTable", "tabulatorOptions": { "columns": [ { @@ -207,6 +202,7 @@ Load data from a static JSON array. "layout": "fitColumns", "maxHeight": "300px" }, - "editable": true + "editable": true, + "variableId": "orgChartDataTable" } ``` \ No newline at end of file diff --git a/docs/assets/examples/markdown/sales-dashboard.idoc.md b/docs/assets/examples/markdown/sales-dashboard.idoc.md index 26ef01ad..c385261d 100644 --- a/docs/assets/examples/markdown/sales-dashboard.idoc.md +++ b/docs/assets/examples/markdown/sales-dashboard.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "salesSelected", - "update": "data('salesSelected')" - }, { "name": "revenueCalculation", "update": "data('revenueCalculation')" @@ -91,10 +87,6 @@ } ] }, - { - "name": "salesSelected", - "values": [] - }, { "name": "revenueCalculation", "source": [ @@ -152,6 +144,7 @@ } ``` + ```css body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #f5f7fa; } body { display: grid; grid-template-areas: 'header header header' 'revenue orders avg' 'category trend trend' 'data data data'; grid-template-columns: 1fr 1fr 1fr; gap: 20px; max-width: 1400px; margin: 0 auto; } @@ -168,31 +161,31 @@ h2 { margin: 10px 0; font-size: 2em; color: #333; } h3 { margin: 0 0 10px 0; font-size: 1em; color: #666; text-transform: uppercase; } ``` + ::: group {#header} + # Sales Performance Dashboard ::: - ::: group {#revenue} -### Total Revenue +### Total Revenue ## {{totalRevenueFormatted}} ::: - ::: group {#orders} -### Total Orders +### Total Orders ## {{totalOrders}} ::: - ::: group {#avg} -### Average Order Value +### Average Order Value ## {{averageOrderValue}} ::: - ::: group {#category} + ### Sales by Category + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -225,11 +218,14 @@ h3 { margin: 0 0 10px 0; font-size: 1em; color: #666; text-transform: uppercase; } } ``` -::: + +::: ::: group {#trend} + ### Sales Trend + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -265,15 +261,19 @@ h3 { margin: 0 0 10px 0; font-size: 1em; color: #666; text-transform: uppercase; } } ``` -::: + +::: ::: group {#data} + ### Sales Data + ```json tabulator { - "dataSourceName": "salesData", - "variableId": "salesSelected" + "dataSourceName": "salesData" } ``` + + ::: \ No newline at end of file diff --git a/docs/assets/examples/markdown/sales-report.idoc.md b/docs/assets/examples/markdown/sales-report.idoc.md new file mode 100644 index 00000000..663830ef --- /dev/null +++ b/docs/assets/examples/markdown/sales-report.idoc.md @@ -0,0 +1,298 @@ +```json vega +{ + "$schema": "https://vega.github.io/schema/vega/v5.json", + "description": "This is the central brain of the page", + "signals": [ + { + "name": "revenueCalculation", + "update": "data('revenueCalculation')" + }, + { + "name": "totalOrders", + "value": 0, + "update": "length(data('salesData'))" + }, + { + "name": "categoryRevenue", + "update": "data('categoryRevenue')" + }, + { + "name": "totalRevenueFormatted", + "value": "$0", + "update": "'$' + format(data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0, ',.2f')" + }, + { + "name": "averageOrderValue", + "value": "$0.00", + "update": "'$' + format((data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0) / (totalOrders > 0 ? totalOrders : 1), ',.2f')" + }, + { + "name": "salesData", + "update": "data('salesData')" + } + ], + "data": [ + { + "name": "salesData", + "values": [ + { + "timestamp": "2025-08-01", + "order_id": "ORD-1001", + "product": "Wireless Mouse", + "category": "Electronics", + "region": "West", + "salesperson": "Jane Smith", + "units": 3, + "unit_price": 25 + }, + { + "timestamp": "2025-08-02", + "order_id": "ORD-1002", + "product": "Office Chair", + "category": "Furniture", + "region": "East", + "salesperson": "Bob Johnson", + "units": 1, + "unit_price": 199.99 + }, + { + "timestamp": "2025-08-03", + "order_id": "ORD-1003", + "product": "Laptop Stand", + "category": "Electronics", + "region": "North", + "salesperson": "Alice Chen", + "units": 2, + "unit_price": 45.5 + }, + { + "timestamp": "2025-08-05", + "order_id": "ORD-1004", + "product": "Desk Lamp", + "category": "Furniture", + "region": "South", + "salesperson": "Charlie Brown", + "units": 1, + "unit_price": 89 + }, + { + "timestamp": "2025-08-06", + "order_id": "ORD-1005", + "product": "Bluetooth Headphones", + "category": "Electronics", + "region": "West", + "salesperson": "Jane Smith", + "units": 4, + "unit_price": 79.99 + } + ] + }, + { + "name": "revenueCalculation", + "source": [ + "salesData" + ], + "transform": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "ops": [ + "sum" + ], + "fields": [ + "revenue" + ], + "as": [ + "total" + ] + } + ] + }, + { + "name": "categoryRevenue", + "source": [ + "salesData" + ], + "transform": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "groupby": [ + "category" + ], + "ops": [ + "sum" + ], + "fields": [ + "revenue" + ], + "as": [ + "total_revenue" + ] + } + ] + } + ] +} +``` + + +```css +body { font-family: 'Times New Roman', serif; margin: 0; padding: 40px; background: white; line-height: 1.6; color: #333; } +.group { max-width: 800px; margin: 0 auto; } +h1 { text-align: center; font-size: 2.5em; margin-bottom: 0.5em; color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 20px; } +h2 { font-size: 1.8em; margin: 40px 0 20px 0; color: #2c3e50; border-bottom: 1px solid #bdc3c7; padding-bottom: 10px; } +h3 { font-size: 1.3em; margin: 30px 0 15px 0; color: #34495e; } +blockquote { background: #ecf0f1; padding: 20px; border-left: 5px solid #3498db; margin: 30px 0; font-style: normal; } +table { width: 100%; border-collapse: collapse; margin: 20px 0; } +td { padding: 10px 20px; border-bottom: 1px solid #ecf0f1; } +td:first-child { font-weight: bold; color: #2c3e50; } +.chart-container { margin: 30px 0; padding: 20px; background: #fafafa; border: 1px solid #ecf0f1; border-radius: 5px; } +p { margin: 15px 0; text-align: justify; } +``` + + +::: group {#main} + +# Sales Performance Report +**Reporting Period:** August 1-6, 2025 +**Prepared by:** Sales Analytics Team +**Date:** August 24, 2025 + +## Executive Summary + +> This report analyzes sales performance for the first week of August 2025. Our analysis reveals total revenue of **{{totalRevenueFormatted}}** across **{{totalOrders}} transactions**, with an average order value of **{{averageOrderValue}}**. The data shows strong performance in the Electronics category, which represents the majority of our revenue during this period. + +## Key Performance Metrics + +The following table summarizes our core performance indicators for the reporting period: + +| Metric | Value | +|--------|-------| +| Total Revenue | {{totalRevenueFormatted}} | +| Number of Orders | {{totalOrders}} | +| Average Order Value | {{averageOrderValue}} | +| Active Sales Regions | 4 regions | +| Product Categories | 2 categories | + +## Revenue Analysis by Product Category + +Our product portfolio performed differently across categories during this period. The chart below illustrates the revenue distribution: + + +```json vega-lite +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "categoryRevenue" + }, + "mark": "bar", + "width": 400, + "height": 250, + "encoding": { + "x": { + "field": "category", + "type": "nominal", + "title": "Product Category" + }, + "y": { + "field": "total_revenue", + "type": "quantitative", + "title": "Revenue ($)" + }, + "color": { + "field": "category", + "type": "nominal", + "scale": { + "range": [ + "#3498db", + "#2c3e50" + ] + } + } + } +} +``` + + +The Electronics category generated the majority of revenue, driven primarily by strong sales of Bluetooth Headphones and Wireless Mouse products. The Furniture category, while smaller in volume, contributed significant value through high-ticket items such as the Office Chair. + +## Sales Trend Analysis + +Daily sales performance shows variability throughout the reporting period, with notable patterns emerging: + + +```json vega-lite +{ + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "salesData" + }, + "transform": [ + { + "calculate": "datum.units * datum.unit_price", + "as": "revenue" + } + ], + "mark": { + "type": "line", + "point": true, + "strokeWidth": 2 + }, + "width": 500, + "height": 250, + "encoding": { + "x": { + "field": "timestamp", + "type": "temporal", + "title": "Date" + }, + "y": { + "field": "revenue", + "type": "quantitative", + "title": "Daily Revenue ($)" + }, + "color": { + "value": "#3498db" + } + } +} +``` + + +The trend analysis reveals that August 6th recorded the highest single-day revenue of $319.96, primarily due to the sale of 4 units of Bluetooth Headphones. August 2nd also showed strong performance with $199.99 in revenue from the Office Chair sale. + +## Detailed Transaction Data + +The complete transaction dataset for the reporting period is presented below for reference and further analysis: + + +```json tabulator +{ + "dataSourceName": "salesData" +} +``` + + +## Conclusions and Recommendations + +Based on this analysis, we recommend: + +1. **Focus on Electronics expansion** - Given the strong performance of electronics products, consider expanding this category's inventory and marketing focus. + +2. **Leverage high-value furniture sales** - While furniture has lower transaction volume, the high average order values suggest opportunity for targeted premium product strategies. + +3. **Regional performance review** - Jane Smith's performance in the West region generated strong results and could serve as a model for other regions. + +4. **Inventory planning** - The Bluetooth Headphones' strong performance suggests increasing stock levels for similar high-margin electronics. + +*This report was generated using interactive data analysis. All figures are calculated dynamically from the underlying transaction data.* +::: \ No newline at end of file diff --git a/docs/assets/examples/markdown/seattle-weather/1.idoc.md b/docs/assets/examples/markdown/seattle-weather/1.idoc.md index 216ec50f..cf647c99 100644 --- a/docs/assets/examples/markdown/seattle-weather/1.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/1.idoc.md @@ -4,6 +4,7 @@ Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/2.idoc.md b/docs/assets/examples/markdown/seattle-weather/2.idoc.md index 0b3712e3..4ad35bc8 100644 --- a/docs/assets/examples/markdown/seattle-weather/2.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/2.idoc.md @@ -4,6 +4,7 @@ Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -47,11 +48,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/3.idoc.md b/docs/assets/examples/markdown/seattle-weather/3.idoc.md index 10f7ff4f..d91631fb 100644 --- a/docs/assets/examples/markdown/seattle-weather/3.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/3.idoc.md @@ -20,12 +20,14 @@ } ``` + # Seattle Weather Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -69,11 +71,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/4.idoc.md b/docs/assets/examples/markdown/seattle-weather/4.idoc.md index 8c791126..990b11f9 100644 --- a/docs/assets/examples/markdown/seattle-weather/4.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/4.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "seattle_weather", "update": "data('seattle_weather')" @@ -19,30 +15,29 @@ "format": { "type": "csv" } - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -86,11 +81,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/5.idoc.md b/docs/assets/examples/markdown/seattle-weather/5.idoc.md index 86349ef0..5e96bb37 100644 --- a/docs/assets/examples/markdown/seattle-weather/5.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/5.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "seattle_weather", "update": "data('seattle_weather')" @@ -25,30 +21,29 @@ "expr": "datum.weather=='sun'" } ] - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -92,11 +87,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/6.idoc.md b/docs/assets/examples/markdown/seattle-weather/6.idoc.md index e049f730..ee1e7343 100644 --- a/docs/assets/examples/markdown/seattle-weather/6.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/6.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "IncludeSun", "value": true @@ -29,34 +25,37 @@ "expr": "IncludeSun ? true : datum.weather != 'sun'" } ] - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather -```json checkbox -{"variableId":"IncludeSun","value":true,"label":"Include Sun:"} + +```yaml checkbox +variableId: IncludeSun +value: true +label: 'Include Sun:' ``` + Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -100,11 +99,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/7.idoc.md b/docs/assets/examples/markdown/seattle-weather/7.idoc.md index 847590ee..52189d56 100644 --- a/docs/assets/examples/markdown/seattle-weather/7.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/7.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "IncludeSun", "value": true @@ -61,50 +57,65 @@ "expr": "IncludeSnow ? true : datum.weather != 'snow'" } ] - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather -```json checkbox -{"variableId":"IncludeSun","value":true,"label":"Include Sun:"} + +```yaml checkbox +variableId: IncludeSun +value: true +label: 'Include Sun:' ``` -```json checkbox -{"variableId":"IncludeFog","value":true,"label":"Include Fog:"} + +```yaml checkbox +variableId: IncludeFog +value: true +label: 'Include Fog:' ``` -```json checkbox -{"variableId":"IncludeDrizzle","value":true,"label":"Include Drizzle:"} + +```yaml checkbox +variableId: IncludeDrizzle +value: true +label: 'Include Drizzle:' ``` -```json checkbox -{"variableId":"IncludeRain","value":true,"label":"Include Rain:"} + +```yaml checkbox +variableId: IncludeRain +value: true +label: 'Include Rain:' ``` -```json checkbox -{"variableId":"IncludeSnow","value":true,"label":"Include Snow:"} + +```yaml checkbox +variableId: IncludeSnow +value: true +label: 'Include Snow:' ``` + Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -148,11 +159,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/8.idoc.md b/docs/assets/examples/markdown/seattle-weather/8.idoc.md index 51961d34..f7999cdc 100644 --- a/docs/assets/examples/markdown/seattle-weather/8.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/8.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "IncludeSun", "value": true @@ -73,58 +69,85 @@ "expr": "datum.wind >= MinWind && datum.wind <= MaxWind" } ] - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather -```json checkbox -{"variableId":"IncludeSun","value":true,"label":"Include Sun:"} + +```yaml checkbox +variableId: IncludeSun +value: true +label: 'Include Sun:' ``` -```json checkbox -{"variableId":"IncludeFog","value":true,"label":"Include Fog:"} + +```yaml checkbox +variableId: IncludeFog +value: true +label: 'Include Fog:' ``` -```json checkbox -{"variableId":"IncludeDrizzle","value":true,"label":"Include Drizzle:"} + +```yaml checkbox +variableId: IncludeDrizzle +value: true +label: 'Include Drizzle:' ``` -```json checkbox -{"variableId":"IncludeRain","value":true,"label":"Include Rain:"} + +```yaml checkbox +variableId: IncludeRain +value: true +label: 'Include Rain:' ``` -```json checkbox -{"variableId":"IncludeSnow","value":true,"label":"Include Snow:"} + +```yaml checkbox +variableId: IncludeSnow +value: true +label: 'Include Snow:' ``` -```json slider -{"variableId":"MinWind","value":0,"label":"Min Wind:","min":0,"max":30,"step":1} + +```yaml slider +variableId: MinWind +value: 0 +label: 'Min Wind:' +min: 0 +max: 30 +step: 1 ``` -```json slider -{"variableId":"MaxWind","value":30,"label":"Max Wind:","min":0,"max":30,"step":1} + +```yaml slider +variableId: MaxWind +value: 30 +label: 'Max Wind:' +min: 0 +max: 30 +step: 1 ``` + Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -168,11 +191,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/seattle-weather/9.idoc.md b/docs/assets/examples/markdown/seattle-weather/9.idoc.md index e58c310e..cef9d7de 100644 --- a/docs/assets/examples/markdown/seattle-weather/9.idoc.md +++ b/docs/assets/examples/markdown/seattle-weather/9.idoc.md @@ -3,10 +3,6 @@ "$schema": "https://vega.github.io/schema/vega/v5.json", "description": "This is the central brain of the page", "signals": [ - { - "name": "seattle_weather_selected", - "update": "data('seattle_weather_selected')" - }, { "name": "IncludeSun", "value": true @@ -73,125 +69,137 @@ "expr": "datum.wind >= MinWind && datum.wind <= MaxWind" } ] - }, - { - "name": "seattle_weather_selected", - "values": [] } ] } ``` + # Seattle Weather -```json checkbox -{"variableId":"IncludeSun","value":true,"label":"Include Sun:"} + +```yaml checkbox +variableId: IncludeSun +value: true +label: 'Include Sun:' ``` -```json checkbox -{"variableId":"IncludeFog","value":true,"label":"Include Fog:"} + +```yaml checkbox +variableId: IncludeFog +value: true +label: 'Include Fog:' ``` -```json checkbox -{"variableId":"IncludeDrizzle","value":true,"label":"Include Drizzle:"} + +```yaml checkbox +variableId: IncludeDrizzle +value: true +label: 'Include Drizzle:' ``` -```json checkbox -{"variableId":"IncludeRain","value":true,"label":"Include Rain:"} + +```yaml checkbox +variableId: IncludeRain +value: true +label: 'Include Rain:' ``` -```json checkbox -{"variableId":"IncludeSnow","value":true,"label":"Include Snow:"} + +```yaml checkbox +variableId: IncludeSnow +value: true +label: 'Include Snow:' ``` -```json slider -{"variableId":"MinWind","value":0,"label":"Min Wind:","min":0,"max":30,"step":1} + +```yaml slider +variableId: MinWind +value: 0 +label: 'Min Wind:' +min: 0 +max: 30 +step: 1 ``` -```json slider -{"variableId":"MaxWind","value":30,"label":"Max Wind:","min":0,"max":30,"step":1} + +```yaml slider +variableId: MaxWind +value: 30 +label: 'Max Wind:' +min: 0 +max: 30 +step: 1 ``` + Presets allow you to go pre-chosen values: -```json presets -[ - { - "name": "Basic Gloom", - "state": { - "IncludeSun": false, - "IncludeFog": true, - "IncludeDrizzle": true, - "IncludeRain": false, - "IncludeSnow": false, - "MinWind": 0, - "MaxWind": 5 - } - }, - { - "name": "reset", - "state": { - "MinWind": 0, - "MaxWind": 30, - "IncludeSun": true, - "IncludeFog": true, - "IncludeDrizzle": true, - "IncludeRain": true, - "IncludeSnow": true - } - }, - { - "name": "sunny and calm", - "state": { - "IncludeSun": true, - "IncludeFog": false, - "IncludeDrizzle": false, - "IncludeRain": false, - "IncludeSnow": false, - "MinWind": 0, - "MaxWind": 5 - } - }, - { - "name": "stormy weather", - "state": { - "IncludeSun": false, - "IncludeFog": false, - "IncludeDrizzle": true, - "IncludeRain": true, - "IncludeSnow": true, - "MinWind": 5, - "MaxWind": 30 - } - }, - { - "name": "just snow", - "state": { - "IncludeSun": false, - "IncludeFog": false, - "IncludeDrizzle": false, - "IncludeRain": false, - "IncludeSnow": true, - "MinWind": 0, - "MaxWind": 10 - } - } -] + +```yaml presets +- name: Basic Gloom + state: + IncludeSun: false + IncludeFog: true + IncludeDrizzle: true + IncludeRain: false + IncludeSnow: false + MinWind: 0 + MaxWind: 5 +- name: reset + state: + MinWind: 0 + MaxWind: 30 + IncludeSun: true + IncludeFog: true + IncludeDrizzle: true + IncludeRain: true + IncludeSnow: true +- name: sunny and calm + state: + IncludeSun: true + IncludeFog: false + IncludeDrizzle: false + IncludeRain: false + IncludeSnow: false + MinWind: 0 + MaxWind: 5 +- name: stormy weather + state: + IncludeSun: false + IncludeFog: false + IncludeDrizzle: true + IncludeRain: true + IncludeSnow: true + MinWind: 5 + MaxWind: 30 +- name: just snow + state: + IncludeSun: false + IncludeFog: false + IncludeDrizzle: false + IncludeRain: false + IncludeSnow: true + MinWind: 0 + MaxWind: 10 ``` + Data table: + ```json tabulator { - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "dataSourceName": "seattle_weather" } ``` + Here is a stacked bar chart of Seattle weather: Each bar represents the count of weather types for each month. The colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -235,11 +243,13 @@ The colors distinguish between different weather conditions such as sun, fog, dr } ``` + This section introduces a heatmap visualization for the Seattle weather dataset. The heatmap is designed to display the distribution and intensity of weather-related variables, such as temperature, precipitation, or frequency of weather events, across different time periods or categories. It provides an intuitive way to identify patterns, trends, and anomalies in the dataset. + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", diff --git a/docs/assets/examples/markdown/slides.idoc.md b/docs/assets/examples/markdown/slides.idoc.md index f01b71e4..27004b47 100644 --- a/docs/assets/examples/markdown/slides.idoc.md +++ b/docs/assets/examples/markdown/slides.idoc.md @@ -175,6 +175,7 @@ } ``` + ```css html, body { height: 100%; margin: 0; padding: 0; scroll-behavior: smooth; overflow-y: auto; } body { scroll-snap-type: y mandatory; } @@ -185,36 +186,33 @@ body { scroll-snap-type: y mandatory; } #slide4 { background: #e74c3c; } ``` + ::: group {#slide1} -## Welcome to the Shopping List App +## Welcome to the Shopping List App ::: - ::: group {#slide2} -## Here you can select items to buy from a variety of categories. +## Here you can select items to buy from a variety of categories. ### Categories include Fruits, Bakery, Dairy, Meat, Vegetables, and Pantry items. - ### You can select multiple items and see the total price calculated automatically. ::: - ::: group {#slide3} -## Let's start by selecting some items from the list below. +## Let's start by selecting some items from the list below. ### Click on the checkboxes next to the items you want to add to your shopping list. ::: - ::: group {#slide4} + ## Select the items you want to buy ```json tabulator { "dataSourceName": "itemsData", - "variableId": "itemsData_selected", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", @@ -229,16 +227,19 @@ body { scroll-snap-type: y mandatory; } "hozAlign": "center", "width": 40 } - } + }, + "variableId": "itemsData_selected" } ``` + ## Total Price ${{total}} ### Categories + ```json vega-lite { "$schema": "https://vega.github.io/schema/vega-lite/v6.json", @@ -267,4 +268,6 @@ ${{total}} } } ``` + + ::: \ No newline at end of file diff --git a/docs/assets/examples/markdown/why-chartifact.idoc.md b/docs/assets/examples/markdown/why-chartifact.idoc.md index b80713bc..9c1aeddb 100644 --- a/docs/assets/examples/markdown/why-chartifact.idoc.md +++ b/docs/assets/examples/markdown/why-chartifact.idoc.md @@ -20,6 +20,7 @@ } ``` + ```css html, body { height: 100%; margin: 0; padding: 0; scroll-behavior: smooth; overflow-y: auto; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { scroll-snap-type: y mandatory; } @@ -42,154 +43,106 @@ li { margin: 0.3em 0; } @media (max-width: 480px) { .group { padding: 1.5em 0.8em; } h1 { font-size: 1.8em; } h2 { font-size: 1.4em; } h3 { font-size: 1.2em; } p, li { font-size: 0.9em; line-height: 1.3; } ul, ol { padding-left: 0.8em; } li { margin: 0.2em 0; } } ``` + ::: group {#slide1} -# Why Chartifact? +# Why Chartifact? ### A Document Format for the LLM Age - ## The industry lacks a shareable document format designed for AI-assisted knowledge work ::: - ::: group {#slide2} -## 🤖 The LLM Revolution Changed Everything +## 🤖 The LLM Revolution Changed Everything ### We're living in the age of AI-assisted knowledge work - - Large Language Models transform how we create and consume information - - Knowledge workers expect **interactive**, **dynamic** content - - Traditional document formats feel static and outdated - - **But we're still using document formats from the pre-AI era** ::: - ::: group {#slide3} -## 📄 The Current Landscape is Broken +## 📄 The Current Landscape is Broken ### PDF: Made for **paper** - ❌ Not human-editable - ❌ Not interactive - ❌ Static forever - - ### HTML: - ✅ Powerful & interactive - ❌ Security nightmare - ❌ Corporate systems won't serve it - - ### Markdown: **Clear winner** so far - ✅ Human-readable - ✅ Version controllable - ❌ Limited interactivity ::: - ::: group {#slide4} -## ⚡ HTML: From Hero to Villain +## ⚡ HTML: From Hero to Villain ### HTML was supposed to be the document format of the internet - - Started as a simple markup language for documents - - Became **too powerful** - now it's a full application shell - - Unlimited execution capabilities = security risk - - Corporate IT departments **block HTML files** by default - - What was meant to democratize publishing became a developer-only tool - - ### 🎭 *"You were the chosen one, Anakin!"* ::: - ::: group {#slide5} -## 🔒 Proprietary Solutions Miss the Point +## 🔒 Proprietary Solutions Miss the Point ### Most LLMs build apps, not documents - - Proprietary and platform-locked 🔐 - - Force information workers to become developers 👩‍💻 - - **All they wanted to do was remix a presentation!** - - Sometimes trapped in vendor ecosystem - - Require hosting and maintenance - - ### What we actually need: - **Open, portable, interactive documents that travel like PDFs but work like apps** ::: - ::: group {#slide6} -## 📊 Interactive Content is Exponentially More Valuable +## 📊 Interactive Content is Exponentially More Valuable ### If a picture is worth 1,000 words... - # {{calculatedWorth}} - ### Then an interactive is worth **{{calculatedWorth}}** words -```json slider -{"variableId":"interactiveValue","value":1000,"label":"Adjust the multiplier","min":1000,"max":100000,"step":1000} + +```yaml slider +variableId: interactiveValue +value: 1000 +label: Adjust the multiplier +min: 1000 +max: 100000 +step: 1000 ``` + ### ⚡ This slide proves the point - you just experienced it! ::: - ::: group {#slide7} -## 🎯 What the Industry Actually Needs +## 🎯 What the Industry Actually Needs ### A new document format that is: - - **📱 Portable** - travels everywhere like PDF - - **🔓 Open Source** - not locked to any vendor - - **⚡ Interactive** - reactive and dynamic - - **🛡️ Safe** - secure by design, no arbitrary code execution - - **👥 Human-friendly** - editable by knowledge workers - - **🤖 AI-ready** - designed for LLM generation and editing - - ### 🚫 **No apps that rot. No hosting required.** ::: - ::: group {#slide8} -# 🎯 Enter Chartifact +# 🎯 Enter Chartifact ### The missing document format for the LLM age - ## ✨ **Declarative, interactive data documents** - ## 📄 **Travels like a document** - ## 📱 **Works like a mini app** - ## 🤖 **Designed for AI collaboration** - - ### Ready to reshape how we share knowledge in {{currentYear}}? ::: \ No newline at end of file diff --git a/docs/dist/v1/chartifact.compiler.umd.js b/docs/dist/v1/chartifact.compiler.umd.js index 4daf2da7..3f842a29 100644 --- a/docs/dist/v1/chartifact.compiler.umd.js +++ b/docs/dist/v1/chartifact.compiler.umd.js @@ -1,10 +1,27 @@ (function(global, factory) { - typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vega")) : typeof define === "function" && define.amd ? define(["exports", "vega"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.Chartifact = global.Chartifact || {}, global.vega)); -})(this, (function(exports2, vega) { + typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vega"), require("js-yaml")) : typeof define === "function" && define.amd ? define(["exports", "vega", "js-yaml"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.Chartifact = global.Chartifact || {}, global.vega, global.jsyaml)); +})(this, (function(exports2, vega, yaml) { "use strict";var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + function _interopNamespaceDefault(e) { + const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } }); + if (e) { + for (const k in e) { + if (k !== "default") { + const d = Object.getOwnPropertyDescriptor(e, k); + Object.defineProperty(n, k, d.get ? d : { + enumerable: true, + get: () => e[k] + }); + } + } + } + n.default = e; + return Object.freeze(n); + } + const yaml__namespace = /* @__PURE__ */ _interopNamespaceDefault(yaml); const defaultCommonOptions = { dataSignalPrefix: "data_signal:", groupClassName: "group" @@ -189,15 +206,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return sorted; } - function createSpecWithVariables(variables, tableElements, stubDataLoaders) { + function createSpecWithVariables(variables, tabulatorElements, stubDataLoaders) { const spec = { $schema: "https://vega.github.io/schema/vega/v5.json", description: "This is the central brain of the page", signals: [], data: [] }; - tableElements.forEach((table) => { - const { variableId } = table; + tabulatorElements.forEach((tabulator) => { + const { variableId } = tabulator; + if (!variableId) { + return; + } spec.signals.push(dataAsSignal(variableId)); spec.data.unshift({ name: variableId, @@ -314,27 +334,58 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } const defaultJsonIndent = 2; function tickWrap(plugin, content) { - return `\`\`\`${plugin} + return ` + + +\`\`\`${plugin} ${content} -\`\`\``; +\`\`\` + + +`; } function jsonWrap(type, content) { return tickWrap("json " + type, content); } + function yamlWrap(type, content) { + return tickWrap("yaml " + type, trimTrailingNewline(content)); + } function chartWrap(spec) { const chartType = getChartType(spec); return jsonWrap(chartType, JSON.stringify(spec, null, defaultJsonIndent)); } + function chartWrapYaml(spec) { + const chartType = getChartType(spec); + return yamlWrap(chartType, yaml__namespace.dump(spec, { indent: defaultJsonIndent })); + } function mdContainerWrap(classname, id, content) { return `::: ${classname} {#${id}} + ${content} :::`; } + const defaultPluginFormat = { + "*": "yaml", + "tabulator": "json", + "vega": "json", + "vega-lite": "json" + }; const defaultOptions = { - extraNewlineCount: 1 + extraNewlines: 2, + pluginFormat: defaultPluginFormat }; + function getPluginFormat(pluginName, pluginFormat) { + if (pluginFormat[pluginName]) { + return pluginFormat[pluginName]; + } + if (pluginFormat["*"]) { + return pluginFormat["*"]; + } + return "json"; + } function targetMarkdown(page, options) { const finalOptions = { ...defaultOptions, ...options }; + const finalPluginFormat = { ...defaultPluginFormat, ...options == null ? void 0 : options.pluginFormat }; const mdSections = []; const dataLoaders = page.dataLoaders || []; const variables = page.variables || []; @@ -353,16 +404,17 @@ ${content} mdSections.push(jsonWrap("google-fonts", JSON.stringify(style.googleFonts, null, defaultJsonIndent))); } } - const tableElements = page.groups.flatMap((group) => group.elements.filter((e) => typeof e !== "string" && e.type === "table")); - const vegaScope = dataLoaderMarkdown(dataLoaders.filter((dl) => dl.type !== "spec"), variables, tableElements); + const tabulatorElements = page.groups.flatMap((group) => group.elements.filter((e) => typeof e !== "string" && e.type === "tabulator")); + const vegaScope = dataLoaderMarkdown(dataLoaders.filter((dl) => dl.type !== "spec"), variables, tabulatorElements); for (const dataLoader of dataLoaders.filter((dl) => dl.type === "spec")) { - mdSections.push(chartWrap(dataLoader.spec)); + const useYaml = getPluginFormat("vega", finalPluginFormat) === "yaml"; + mdSections.push(useYaml ? chartWrapYaml(dataLoader.spec) : chartWrap(dataLoader.spec)); } for (const group of page.groups) { mdSections.push(mdContainerWrap( defaultCommonOptions.groupClassName, group.groupId, - groupMarkdown(group, variables, vegaScope, page.resources) + groupMarkdown(group, variables, vegaScope, page.resources, finalPluginFormat) )); } const { data, signals } = vegaScope.spec; @@ -380,7 +432,8 @@ ${content} delete vegaScope.spec.signals; } if (vegaScope.spec.data || vegaScope.spec.signals) { - mdSections.unshift(chartWrap(vegaScope.spec)); + const useYaml = getPluginFormat("vega", finalPluginFormat) === "yaml"; + mdSections.unshift(useYaml ? chartWrapYaml(vegaScope.spec) : chartWrap(vegaScope.spec)); } if (page.notes) { if (Array.isArray(page.notes)) { @@ -397,12 +450,11 @@ ${content} mdSections.unshift(tickWrap("#", JSON.stringify(page.notes, null, defaultJsonIndent))); } } - const newLines = "\n".repeat(1 + finalOptions.extraNewlineCount); - const markdown = mdSections.join(newLines); - return markdown; + const markdown = mdSections.join("\n"); + return normalizeNewlines(markdown, finalOptions.extraNewlines).trim(); } - function dataLoaderMarkdown(dataSources, variables, tableElements) { - const spec = createSpecWithVariables(variables, tableElements); + function dataLoaderMarkdown(dataSources, variables, tabulatorElements) { + const spec = createSpecWithVariables(variables, tabulatorElements); const vegaScope = new VegaScope(spec); for (const dataSource of dataSources) { switch (dataSource.type) { @@ -422,12 +474,18 @@ ${content} } return vegaScope; } - function groupMarkdown(group, variables, vegaScope, resources) { + function groupMarkdown(group, variables, vegaScope, resources, pluginFormat) { var _a, _b, _c, _d, _e; const mdElements = []; const addSpec = (pluginName, spec, indent = true) => { - const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); - mdElements.push(jsonWrap(pluginName, content)); + const format = getPluginFormat(pluginName, pluginFormat); + if (format === "yaml") { + const content = indent ? yaml__namespace.dump(spec, { indent: defaultJsonIndent }) : yaml__namespace.dump(spec); + mdElements.push(yamlWrap(pluginName, content)); + } else { + const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); + mdElements.push(jsonWrap(pluginName, content)); + } }; for (const element of group.elements) { if (typeof element === "string") { @@ -440,7 +498,9 @@ ${content} if (!spec) { mdElements.push("![Chart Spinner](/img/chart-spinner.gif)"); } else { - mdElements.push(chartWrap(spec)); + const chartType = getChartType(spec); + const useYaml = getPluginFormat(chartType, pluginFormat) === "yaml"; + mdElements.push(useYaml ? chartWrapYaml(spec) : chartWrap(spec)); } break; } @@ -527,10 +587,13 @@ ${content} addSpec("slider", sliderSpec, false); break; } - case "table": { + case "tabulator": { const { dataSourceName, variableId, tabulatorOptions, editable } = element; - const tableSpec = { dataSourceName, variableId, tabulatorOptions, editable }; - addSpec("tabulator", tableSpec); + const tabulatorSpec = { dataSourceName, tabulatorOptions, editable }; + if (variableId) { + tabulatorSpec.variableId = variableId; + } + addSpec("tabulator", tabulatorSpec); break; } case "textbox": { @@ -553,11 +616,21 @@ ${content} mdElements.push(tickWrap("#", JSON.stringify(element))); } } - const markdown = mdElements.join("\n\n"); - return markdown; + const markdown = mdElements.join("\n"); + return trimTrailingNewline(markdown); + } + function trimTrailingNewline(s) { + if (s.endsWith("\n")) { + return s.slice(0, -1); + } + return s; + } + function normalizeNewlines(text, extra) { + return text.replace(/(\n\s*){4,}/g, "\n".repeat(1 + extra)) + "\n"; } const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, + normalizeNewlines, targetMarkdown }, Symbol.toStringTag, { value: "Module" })); exports2.common = index$1; diff --git a/docs/dist/v1/chartifact.editor.umd.js b/docs/dist/v1/chartifact.editor.umd.js index 11c3041c..9cd43746 100644 --- a/docs/dist/v1/chartifact.editor.umd.js +++ b/docs/dist/v1/chartifact.editor.umd.js @@ -123,7 +123,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy if (tokens.length === 1 && tokens[0].type === "variable") { return tokens[0].name; } - const escape = (str) => `'${str.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; + const escape = (str2) => `'${str2.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; return tokens.map((token) => token.type === "literal" ? escape(token.value) : `${funcName}(${token.name})`).join(" + "); } function encodeTemplateVariables(input) { @@ -190,15 +190,18 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy } return sorted; } - function createSpecWithVariables(variables, tableElements, stubDataLoaders) { + function createSpecWithVariables(variables, tabulatorElements, stubDataLoaders) { const spec = { $schema: "https://vega.github.io/schema/vega/v5.json", description: "This is the central brain of the page", signals: [], data: [] }; - tableElements.forEach((table) => { - const { variableId } = table; + tabulatorElements.forEach((tabulator) => { + const { variableId } = tabulator; + if (!variableId) { + return; + } spec.signals.push(dataAsSignal(variableId)); spec.data.unshift({ name: variableId, @@ -314,29 +317,1410 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy return signal; } } + /*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */ + function isNothing(subject) { + return typeof subject === "undefined" || subject === null; + } + function isObject(subject) { + return typeof subject === "object" && subject !== null; + } + function toArray(sequence) { + if (Array.isArray(sequence)) return sequence; + else if (isNothing(sequence)) return []; + return [sequence]; + } + function extend(target, source) { + var index2, length, key, sourceKeys; + if (source) { + sourceKeys = Object.keys(source); + for (index2 = 0, length = sourceKeys.length; index2 < length; index2 += 1) { + key = sourceKeys[index2]; + target[key] = source[key]; + } + } + return target; + } + function repeat(string, count) { + var result = "", cycle; + for (cycle = 0; cycle < count; cycle += 1) { + result += string; + } + return result; + } + function isNegativeZero(number) { + return number === 0 && Number.NEGATIVE_INFINITY === 1 / number; + } + var isNothing_1 = isNothing; + var isObject_1 = isObject; + var toArray_1 = toArray; + var repeat_1 = repeat; + var isNegativeZero_1 = isNegativeZero; + var extend_1 = extend; + var common = { + isNothing: isNothing_1, + isObject: isObject_1, + toArray: toArray_1, + repeat: repeat_1, + isNegativeZero: isNegativeZero_1, + extend: extend_1 + }; + function formatError(exception2, compact) { + var where = "", message = exception2.reason || "(unknown reason)"; + if (!exception2.mark) return message; + if (exception2.mark.name) { + where += 'in "' + exception2.mark.name + '" '; + } + where += "(" + (exception2.mark.line + 1) + ":" + (exception2.mark.column + 1) + ")"; + if (!compact && exception2.mark.snippet) { + where += "\n\n" + exception2.mark.snippet; + } + return message + " " + where; + } + function YAMLException$1(reason, mark) { + Error.call(this); + this.name = "YAMLException"; + this.reason = reason; + this.mark = mark; + this.message = formatError(this, false); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } else { + this.stack = new Error().stack || ""; + } + } + YAMLException$1.prototype = Object.create(Error.prototype); + YAMLException$1.prototype.constructor = YAMLException$1; + YAMLException$1.prototype.toString = function toString(compact) { + return this.name + ": " + formatError(this, compact); + }; + var exception = YAMLException$1; + var TYPE_CONSTRUCTOR_OPTIONS = [ + "kind", + "multi", + "resolve", + "construct", + "instanceOf", + "predicate", + "represent", + "representName", + "defaultStyle", + "styleAliases" + ]; + var YAML_NODE_KINDS = [ + "scalar", + "sequence", + "mapping" + ]; + function compileStyleAliases(map2) { + var result = {}; + if (map2 !== null) { + Object.keys(map2).forEach(function(style) { + map2[style].forEach(function(alias) { + result[String(alias)] = style; + }); + }); + } + return result; + } + function Type$1(tag, options) { + options = options || {}; + Object.keys(options).forEach(function(name) { + if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { + throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); + } + }); + this.options = options; + this.tag = tag; + this.kind = options["kind"] || null; + this.resolve = options["resolve"] || function() { + return true; + }; + this.construct = options["construct"] || function(data) { + return data; + }; + this.instanceOf = options["instanceOf"] || null; + this.predicate = options["predicate"] || null; + this.represent = options["represent"] || null; + this.representName = options["representName"] || null; + this.defaultStyle = options["defaultStyle"] || null; + this.multi = options["multi"] || false; + this.styleAliases = compileStyleAliases(options["styleAliases"] || null); + if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { + throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + } + } + var type = Type$1; + function compileList(schema2, name) { + var result = []; + schema2[name].forEach(function(currentType) { + var newIndex = result.length; + result.forEach(function(previousType, previousIndex) { + if (previousType.tag === currentType.tag && previousType.kind === currentType.kind && previousType.multi === currentType.multi) { + newIndex = previousIndex; + } + }); + result[newIndex] = currentType; + }); + return result; + } + function compileMap() { + var result = { + scalar: {}, + sequence: {}, + mapping: {}, + fallback: {}, + multi: { + scalar: [], + sequence: [], + mapping: [], + fallback: [] + } + }, index2, length; + function collectType(type2) { + if (type2.multi) { + result.multi[type2.kind].push(type2); + result.multi["fallback"].push(type2); + } else { + result[type2.kind][type2.tag] = result["fallback"][type2.tag] = type2; + } + } + for (index2 = 0, length = arguments.length; index2 < length; index2 += 1) { + arguments[index2].forEach(collectType); + } + return result; + } + function Schema$1(definition) { + return this.extend(definition); + } + Schema$1.prototype.extend = function extend2(definition) { + var implicit = []; + var explicit = []; + if (definition instanceof type) { + explicit.push(definition); + } else if (Array.isArray(definition)) { + explicit = explicit.concat(definition); + } else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) { + if (definition.implicit) implicit = implicit.concat(definition.implicit); + if (definition.explicit) explicit = explicit.concat(definition.explicit); + } else { + throw new exception("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })"); + } + implicit.forEach(function(type$1) { + if (!(type$1 instanceof type)) { + throw new exception("Specified list of YAML types (or a single Type object) contains a non-Type object."); + } + if (type$1.loadKind && type$1.loadKind !== "scalar") { + throw new exception("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."); + } + if (type$1.multi) { + throw new exception("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit."); + } + }); + explicit.forEach(function(type$1) { + if (!(type$1 instanceof type)) { + throw new exception("Specified list of YAML types (or a single Type object) contains a non-Type object."); + } + }); + var result = Object.create(Schema$1.prototype); + result.implicit = (this.implicit || []).concat(implicit); + result.explicit = (this.explicit || []).concat(explicit); + result.compiledImplicit = compileList(result, "implicit"); + result.compiledExplicit = compileList(result, "explicit"); + result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); + return result; + }; + var schema = Schema$1; + var str = new type("tag:yaml.org,2002:str", { + kind: "scalar", + construct: function(data) { + return data !== null ? data : ""; + } + }); + var seq = new type("tag:yaml.org,2002:seq", { + kind: "sequence", + construct: function(data) { + return data !== null ? data : []; + } + }); + var map = new type("tag:yaml.org,2002:map", { + kind: "mapping", + construct: function(data) { + return data !== null ? data : {}; + } + }); + var failsafe = new schema({ + explicit: [ + str, + seq, + map + ] + }); + function resolveYamlNull(data) { + if (data === null) return true; + var max = data.length; + return max === 1 && data === "~" || max === 4 && (data === "null" || data === "Null" || data === "NULL"); + } + function constructYamlNull() { + return null; + } + function isNull(object) { + return object === null; + } + var _null = new type("tag:yaml.org,2002:null", { + kind: "scalar", + resolve: resolveYamlNull, + construct: constructYamlNull, + predicate: isNull, + represent: { + canonical: function() { + return "~"; + }, + lowercase: function() { + return "null"; + }, + uppercase: function() { + return "NULL"; + }, + camelcase: function() { + return "Null"; + }, + empty: function() { + return ""; + } + }, + defaultStyle: "lowercase" + }); + function resolveYamlBoolean(data) { + if (data === null) return false; + var max = data.length; + return max === 4 && (data === "true" || data === "True" || data === "TRUE") || max === 5 && (data === "false" || data === "False" || data === "FALSE"); + } + function constructYamlBoolean(data) { + return data === "true" || data === "True" || data === "TRUE"; + } + function isBoolean(object) { + return Object.prototype.toString.call(object) === "[object Boolean]"; + } + var bool = new type("tag:yaml.org,2002:bool", { + kind: "scalar", + resolve: resolveYamlBoolean, + construct: constructYamlBoolean, + predicate: isBoolean, + represent: { + lowercase: function(object) { + return object ? "true" : "false"; + }, + uppercase: function(object) { + return object ? "TRUE" : "FALSE"; + }, + camelcase: function(object) { + return object ? "True" : "False"; + } + }, + defaultStyle: "lowercase" + }); + function isHexCode(c) { + return 48 <= c && c <= 57 || 65 <= c && c <= 70 || 97 <= c && c <= 102; + } + function isOctCode(c) { + return 48 <= c && c <= 55; + } + function isDecCode(c) { + return 48 <= c && c <= 57; + } + function resolveYamlInteger(data) { + if (data === null) return false; + var max = data.length, index2 = 0, hasDigits = false, ch; + if (!max) return false; + ch = data[index2]; + if (ch === "-" || ch === "+") { + ch = data[++index2]; + } + if (ch === "0") { + if (index2 + 1 === max) return true; + ch = data[++index2]; + if (ch === "b") { + index2++; + for (; index2 < max; index2++) { + ch = data[index2]; + if (ch === "_") continue; + if (ch !== "0" && ch !== "1") return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + if (ch === "x") { + index2++; + for (; index2 < max; index2++) { + ch = data[index2]; + if (ch === "_") continue; + if (!isHexCode(data.charCodeAt(index2))) return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + if (ch === "o") { + index2++; + for (; index2 < max; index2++) { + ch = data[index2]; + if (ch === "_") continue; + if (!isOctCode(data.charCodeAt(index2))) return false; + hasDigits = true; + } + return hasDigits && ch !== "_"; + } + } + if (ch === "_") return false; + for (; index2 < max; index2++) { + ch = data[index2]; + if (ch === "_") continue; + if (!isDecCode(data.charCodeAt(index2))) { + return false; + } + hasDigits = true; + } + if (!hasDigits || ch === "_") return false; + return true; + } + function constructYamlInteger(data) { + var value = data, sign = 1, ch; + if (value.indexOf("_") !== -1) { + value = value.replace(/_/g, ""); + } + ch = value[0]; + if (ch === "-" || ch === "+") { + if (ch === "-") sign = -1; + value = value.slice(1); + ch = value[0]; + } + if (value === "0") return 0; + if (ch === "0") { + if (value[1] === "b") return sign * parseInt(value.slice(2), 2); + if (value[1] === "x") return sign * parseInt(value.slice(2), 16); + if (value[1] === "o") return sign * parseInt(value.slice(2), 8); + } + return sign * parseInt(value, 10); + } + function isInteger(object) { + return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 === 0 && !common.isNegativeZero(object)); + } + var int = new type("tag:yaml.org,2002:int", { + kind: "scalar", + resolve: resolveYamlInteger, + construct: constructYamlInteger, + predicate: isInteger, + represent: { + binary: function(obj) { + return obj >= 0 ? "0b" + obj.toString(2) : "-0b" + obj.toString(2).slice(1); + }, + octal: function(obj) { + return obj >= 0 ? "0o" + obj.toString(8) : "-0o" + obj.toString(8).slice(1); + }, + decimal: function(obj) { + return obj.toString(10); + }, + /* eslint-disable max-len */ + hexadecimal: function(obj) { + return obj >= 0 ? "0x" + obj.toString(16).toUpperCase() : "-0x" + obj.toString(16).toUpperCase().slice(1); + } + }, + defaultStyle: "decimal", + styleAliases: { + binary: [2, "bin"], + octal: [8, "oct"], + decimal: [10, "dec"], + hexadecimal: [16, "hex"] + } + }); + var YAML_FLOAT_PATTERN = new RegExp( + // 2.5e4, 2.5 and integers + "^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$" + ); + function resolveYamlFloat(data) { + if (data === null) return false; + if (!YAML_FLOAT_PATTERN.test(data) || // Quick hack to not allow integers end with `_` + // Probably should update regexp & check speed + data[data.length - 1] === "_") { + return false; + } + return true; + } + function constructYamlFloat(data) { + var value, sign; + value = data.replace(/_/g, "").toLowerCase(); + sign = value[0] === "-" ? -1 : 1; + if ("+-".indexOf(value[0]) >= 0) { + value = value.slice(1); + } + if (value === ".inf") { + return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; + } else if (value === ".nan") { + return NaN; + } + return sign * parseFloat(value, 10); + } + var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; + function representYamlFloat(object, style) { + var res; + if (isNaN(object)) { + switch (style) { + case "lowercase": + return ".nan"; + case "uppercase": + return ".NAN"; + case "camelcase": + return ".NaN"; + } + } else if (Number.POSITIVE_INFINITY === object) { + switch (style) { + case "lowercase": + return ".inf"; + case "uppercase": + return ".INF"; + case "camelcase": + return ".Inf"; + } + } else if (Number.NEGATIVE_INFINITY === object) { + switch (style) { + case "lowercase": + return "-.inf"; + case "uppercase": + return "-.INF"; + case "camelcase": + return "-.Inf"; + } + } else if (common.isNegativeZero(object)) { + return "-0.0"; + } + res = object.toString(10); + return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res; + } + function isFloat(object) { + return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 !== 0 || common.isNegativeZero(object)); + } + var float = new type("tag:yaml.org,2002:float", { + kind: "scalar", + resolve: resolveYamlFloat, + construct: constructYamlFloat, + predicate: isFloat, + represent: representYamlFloat, + defaultStyle: "lowercase" + }); + var json = failsafe.extend({ + implicit: [ + _null, + bool, + int, + float + ] + }); + var core = json; + var YAML_DATE_REGEXP = new RegExp( + "^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$" + ); + var YAML_TIMESTAMP_REGEXP = new RegExp( + "^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$" + ); + function resolveYamlTimestamp(data) { + if (data === null) return false; + if (YAML_DATE_REGEXP.exec(data) !== null) return true; + if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; + return false; + } + function constructYamlTimestamp(data) { + var match, year, month, day, hour, minute, second, fraction = 0, delta = null, tz_hour, tz_minute, date; + match = YAML_DATE_REGEXP.exec(data); + if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); + if (match === null) throw new Error("Date resolve error"); + year = +match[1]; + month = +match[2] - 1; + day = +match[3]; + if (!match[4]) { + return new Date(Date.UTC(year, month, day)); + } + hour = +match[4]; + minute = +match[5]; + second = +match[6]; + if (match[7]) { + fraction = match[7].slice(0, 3); + while (fraction.length < 3) { + fraction += "0"; + } + fraction = +fraction; + } + if (match[9]) { + tz_hour = +match[10]; + tz_minute = +(match[11] || 0); + delta = (tz_hour * 60 + tz_minute) * 6e4; + if (match[9] === "-") delta = -delta; + } + date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); + if (delta) date.setTime(date.getTime() - delta); + return date; + } + function representYamlTimestamp(object) { + return object.toISOString(); + } + var timestamp = new type("tag:yaml.org,2002:timestamp", { + kind: "scalar", + resolve: resolveYamlTimestamp, + construct: constructYamlTimestamp, + instanceOf: Date, + represent: representYamlTimestamp + }); + function resolveYamlMerge(data) { + return data === "<<" || data === null; + } + var merge = new type("tag:yaml.org,2002:merge", { + kind: "scalar", + resolve: resolveYamlMerge + }); + var BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r"; + function resolveYamlBinary(data) { + if (data === null) return false; + var code, idx, bitlen = 0, max = data.length, map2 = BASE64_MAP; + for (idx = 0; idx < max; idx++) { + code = map2.indexOf(data.charAt(idx)); + if (code > 64) continue; + if (code < 0) return false; + bitlen += 6; + } + return bitlen % 8 === 0; + } + function constructYamlBinary(data) { + var idx, tailbits, input = data.replace(/[\r\n=]/g, ""), max = input.length, map2 = BASE64_MAP, bits = 0, result = []; + for (idx = 0; idx < max; idx++) { + if (idx % 4 === 0 && idx) { + result.push(bits >> 16 & 255); + result.push(bits >> 8 & 255); + result.push(bits & 255); + } + bits = bits << 6 | map2.indexOf(input.charAt(idx)); + } + tailbits = max % 4 * 6; + if (tailbits === 0) { + result.push(bits >> 16 & 255); + result.push(bits >> 8 & 255); + result.push(bits & 255); + } else if (tailbits === 18) { + result.push(bits >> 10 & 255); + result.push(bits >> 2 & 255); + } else if (tailbits === 12) { + result.push(bits >> 4 & 255); + } + return new Uint8Array(result); + } + function representYamlBinary(object) { + var result = "", bits = 0, idx, tail, max = object.length, map2 = BASE64_MAP; + for (idx = 0; idx < max; idx++) { + if (idx % 3 === 0 && idx) { + result += map2[bits >> 18 & 63]; + result += map2[bits >> 12 & 63]; + result += map2[bits >> 6 & 63]; + result += map2[bits & 63]; + } + bits = (bits << 8) + object[idx]; + } + tail = max % 3; + if (tail === 0) { + result += map2[bits >> 18 & 63]; + result += map2[bits >> 12 & 63]; + result += map2[bits >> 6 & 63]; + result += map2[bits & 63]; + } else if (tail === 2) { + result += map2[bits >> 10 & 63]; + result += map2[bits >> 4 & 63]; + result += map2[bits << 2 & 63]; + result += map2[64]; + } else if (tail === 1) { + result += map2[bits >> 2 & 63]; + result += map2[bits << 4 & 63]; + result += map2[64]; + result += map2[64]; + } + return result; + } + function isBinary(obj) { + return Object.prototype.toString.call(obj) === "[object Uint8Array]"; + } + var binary = new type("tag:yaml.org,2002:binary", { + kind: "scalar", + resolve: resolveYamlBinary, + construct: constructYamlBinary, + predicate: isBinary, + represent: representYamlBinary + }); + var _hasOwnProperty$3 = Object.prototype.hasOwnProperty; + var _toString$2 = Object.prototype.toString; + function resolveYamlOmap(data) { + if (data === null) return true; + var objectKeys = [], index2, length, pair, pairKey, pairHasKey, object = data; + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + pair = object[index2]; + pairHasKey = false; + if (_toString$2.call(pair) !== "[object Object]") return false; + for (pairKey in pair) { + if (_hasOwnProperty$3.call(pair, pairKey)) { + if (!pairHasKey) pairHasKey = true; + else return false; + } + } + if (!pairHasKey) return false; + if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); + else return false; + } + return true; + } + function constructYamlOmap(data) { + return data !== null ? data : []; + } + var omap = new type("tag:yaml.org,2002:omap", { + kind: "sequence", + resolve: resolveYamlOmap, + construct: constructYamlOmap + }); + var _toString$1 = Object.prototype.toString; + function resolveYamlPairs(data) { + if (data === null) return true; + var index2, length, pair, keys, result, object = data; + result = new Array(object.length); + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + pair = object[index2]; + if (_toString$1.call(pair) !== "[object Object]") return false; + keys = Object.keys(pair); + if (keys.length !== 1) return false; + result[index2] = [keys[0], pair[keys[0]]]; + } + return true; + } + function constructYamlPairs(data) { + if (data === null) return []; + var index2, length, pair, keys, result, object = data; + result = new Array(object.length); + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + pair = object[index2]; + keys = Object.keys(pair); + result[index2] = [keys[0], pair[keys[0]]]; + } + return result; + } + var pairs = new type("tag:yaml.org,2002:pairs", { + kind: "sequence", + resolve: resolveYamlPairs, + construct: constructYamlPairs + }); + var _hasOwnProperty$2 = Object.prototype.hasOwnProperty; + function resolveYamlSet(data) { + if (data === null) return true; + var key, object = data; + for (key in object) { + if (_hasOwnProperty$2.call(object, key)) { + if (object[key] !== null) return false; + } + } + return true; + } + function constructYamlSet(data) { + return data !== null ? data : {}; + } + var set = new type("tag:yaml.org,2002:set", { + kind: "mapping", + resolve: resolveYamlSet, + construct: constructYamlSet + }); + var _default = core.extend({ + implicit: [ + timestamp, + merge + ], + explicit: [ + binary, + omap, + pairs, + set + ] + }); + function simpleEscapeSequence(c) { + return c === 48 ? "\0" : c === 97 ? "\x07" : c === 98 ? "\b" : c === 116 ? " " : c === 9 ? " " : c === 110 ? "\n" : c === 118 ? "\v" : c === 102 ? "\f" : c === 114 ? "\r" : c === 101 ? "\x1B" : c === 32 ? " " : c === 34 ? '"' : c === 47 ? "/" : c === 92 ? "\\" : c === 78 ? "…" : c === 95 ? " " : c === 76 ? "\u2028" : c === 80 ? "\u2029" : ""; + } + var simpleEscapeCheck = new Array(256); + var simpleEscapeMap = new Array(256); + for (var i = 0; i < 256; i++) { + simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; + simpleEscapeMap[i] = simpleEscapeSequence(i); + } + var _toString = Object.prototype.toString; + var _hasOwnProperty = Object.prototype.hasOwnProperty; + var CHAR_BOM = 65279; + var CHAR_TAB = 9; + var CHAR_LINE_FEED = 10; + var CHAR_CARRIAGE_RETURN = 13; + var CHAR_SPACE = 32; + var CHAR_EXCLAMATION = 33; + var CHAR_DOUBLE_QUOTE = 34; + var CHAR_SHARP = 35; + var CHAR_PERCENT = 37; + var CHAR_AMPERSAND = 38; + var CHAR_SINGLE_QUOTE = 39; + var CHAR_ASTERISK = 42; + var CHAR_COMMA = 44; + var CHAR_MINUS = 45; + var CHAR_COLON = 58; + var CHAR_EQUALS = 61; + var CHAR_GREATER_THAN = 62; + var CHAR_QUESTION = 63; + var CHAR_COMMERCIAL_AT = 64; + var CHAR_LEFT_SQUARE_BRACKET = 91; + var CHAR_RIGHT_SQUARE_BRACKET = 93; + var CHAR_GRAVE_ACCENT = 96; + var CHAR_LEFT_CURLY_BRACKET = 123; + var CHAR_VERTICAL_LINE = 124; + var CHAR_RIGHT_CURLY_BRACKET = 125; + var ESCAPE_SEQUENCES = {}; + ESCAPE_SEQUENCES[0] = "\\0"; + ESCAPE_SEQUENCES[7] = "\\a"; + ESCAPE_SEQUENCES[8] = "\\b"; + ESCAPE_SEQUENCES[9] = "\\t"; + ESCAPE_SEQUENCES[10] = "\\n"; + ESCAPE_SEQUENCES[11] = "\\v"; + ESCAPE_SEQUENCES[12] = "\\f"; + ESCAPE_SEQUENCES[13] = "\\r"; + ESCAPE_SEQUENCES[27] = "\\e"; + ESCAPE_SEQUENCES[34] = '\\"'; + ESCAPE_SEQUENCES[92] = "\\\\"; + ESCAPE_SEQUENCES[133] = "\\N"; + ESCAPE_SEQUENCES[160] = "\\_"; + ESCAPE_SEQUENCES[8232] = "\\L"; + ESCAPE_SEQUENCES[8233] = "\\P"; + var DEPRECATED_BOOLEANS_SYNTAX = [ + "y", + "Y", + "yes", + "Yes", + "YES", + "on", + "On", + "ON", + "n", + "N", + "no", + "No", + "NO", + "off", + "Off", + "OFF" + ]; + var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/; + function compileStyleMap(schema2, map2) { + var result, keys, index2, length, tag, style, type2; + if (map2 === null) return {}; + result = {}; + keys = Object.keys(map2); + for (index2 = 0, length = keys.length; index2 < length; index2 += 1) { + tag = keys[index2]; + style = String(map2[tag]); + if (tag.slice(0, 2) === "!!") { + tag = "tag:yaml.org,2002:" + tag.slice(2); + } + type2 = schema2.compiledTypeMap["fallback"][tag]; + if (type2 && _hasOwnProperty.call(type2.styleAliases, style)) { + style = type2.styleAliases[style]; + } + result[tag] = style; + } + return result; + } + function encodeHex(character) { + var string, handle, length; + string = character.toString(16).toUpperCase(); + if (character <= 255) { + handle = "x"; + length = 2; + } else if (character <= 65535) { + handle = "u"; + length = 4; + } else if (character <= 4294967295) { + handle = "U"; + length = 8; + } else { + throw new exception("code point within a string may not be greater than 0xFFFFFFFF"); + } + return "\\" + handle + common.repeat("0", length - string.length) + string; + } + var QUOTING_TYPE_SINGLE = 1, QUOTING_TYPE_DOUBLE = 2; + function State(options) { + this.schema = options["schema"] || _default; + this.indent = Math.max(1, options["indent"] || 2); + this.noArrayIndent = options["noArrayIndent"] || false; + this.skipInvalid = options["skipInvalid"] || false; + this.flowLevel = common.isNothing(options["flowLevel"]) ? -1 : options["flowLevel"]; + this.styleMap = compileStyleMap(this.schema, options["styles"] || null); + this.sortKeys = options["sortKeys"] || false; + this.lineWidth = options["lineWidth"] || 80; + this.noRefs = options["noRefs"] || false; + this.noCompatMode = options["noCompatMode"] || false; + this.condenseFlow = options["condenseFlow"] || false; + this.quotingType = options["quotingType"] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE; + this.forceQuotes = options["forceQuotes"] || false; + this.replacer = typeof options["replacer"] === "function" ? options["replacer"] : null; + this.implicitTypes = this.schema.compiledImplicit; + this.explicitTypes = this.schema.compiledExplicit; + this.tag = null; + this.result = ""; + this.duplicates = []; + this.usedDuplicates = null; + } + function indentString(string, spaces) { + var ind = common.repeat(" ", spaces), position = 0, next = -1, result = "", line, length = string.length; + while (position < length) { + next = string.indexOf("\n", position); + if (next === -1) { + line = string.slice(position); + position = length; + } else { + line = string.slice(position, next + 1); + position = next + 1; + } + if (line.length && line !== "\n") result += ind; + result += line; + } + return result; + } + function generateNextLine(state, level) { + return "\n" + common.repeat(" ", state.indent * level); + } + function testImplicitResolving(state, str2) { + var index2, length, type2; + for (index2 = 0, length = state.implicitTypes.length; index2 < length; index2 += 1) { + type2 = state.implicitTypes[index2]; + if (type2.resolve(str2)) { + return true; + } + } + return false; + } + function isWhitespace(c) { + return c === CHAR_SPACE || c === CHAR_TAB; + } + function isPrintable(c) { + return 32 <= c && c <= 126 || 161 <= c && c <= 55295 && c !== 8232 && c !== 8233 || 57344 <= c && c <= 65533 && c !== CHAR_BOM || 65536 <= c && c <= 1114111; + } + function isNsCharOrWhitespace(c) { + return isPrintable(c) && c !== CHAR_BOM && c !== CHAR_CARRIAGE_RETURN && c !== CHAR_LINE_FEED; + } + function isPlainSafe(c, prev, inblock) { + var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c); + var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c); + return ( + // ns-plain-safe + (inblock ? ( + // c = flow-in + cIsNsCharOrWhitespace + ) : cIsNsCharOrWhitespace && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET) && c !== CHAR_SHARP && !(prev === CHAR_COLON && !cIsNsChar) || isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP || prev === CHAR_COLON && cIsNsChar + ); + } + function isPlainSafeFirst(c) { + return isPrintable(c) && c !== CHAR_BOM && !isWhitespace(c) && c !== CHAR_MINUS && c !== CHAR_QUESTION && c !== CHAR_COLON && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET && c !== CHAR_SHARP && c !== CHAR_AMPERSAND && c !== CHAR_ASTERISK && c !== CHAR_EXCLAMATION && c !== CHAR_VERTICAL_LINE && c !== CHAR_EQUALS && c !== CHAR_GREATER_THAN && c !== CHAR_SINGLE_QUOTE && c !== CHAR_DOUBLE_QUOTE && c !== CHAR_PERCENT && c !== CHAR_COMMERCIAL_AT && c !== CHAR_GRAVE_ACCENT; + } + function isPlainSafeLast(c) { + return !isWhitespace(c) && c !== CHAR_COLON; + } + function codePointAt(string, pos) { + var first = string.charCodeAt(pos), second; + if (first >= 55296 && first <= 56319 && pos + 1 < string.length) { + second = string.charCodeAt(pos + 1); + if (second >= 56320 && second <= 57343) { + return (first - 55296) * 1024 + second - 56320 + 65536; + } + } + return first; + } + function needIndentIndicator(string) { + var leadingSpaceRe = /^\n* /; + return leadingSpaceRe.test(string); + } + var STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5; + function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType, quotingType, forceQuotes, inblock) { + var i2; + var char = 0; + var prevChar = null; + var hasLineBreak = false; + var hasFoldableLine = false; + var shouldTrackWidth = lineWidth !== -1; + var previousLineBreak = -1; + var plain = isPlainSafeFirst(codePointAt(string, 0)) && isPlainSafeLast(codePointAt(string, string.length - 1)); + if (singleLineOnly || forceQuotes) { + for (i2 = 0; i2 < string.length; char >= 65536 ? i2 += 2 : i2++) { + char = codePointAt(string, i2); + if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; + } + } else { + for (i2 = 0; i2 < string.length; char >= 65536 ? i2 += 2 : i2++) { + char = codePointAt(string, i2); + if (char === CHAR_LINE_FEED) { + hasLineBreak = true; + if (shouldTrackWidth) { + hasFoldableLine = hasFoldableLine || // Foldable line = too long, and not more-indented. + i2 - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "; + previousLineBreak = i2; + } + } else if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + plain = plain && isPlainSafe(char, prevChar, inblock); + prevChar = char; + } + hasFoldableLine = hasFoldableLine || shouldTrackWidth && (i2 - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " "); + } + if (!hasLineBreak && !hasFoldableLine) { + if (plain && !forceQuotes && !testAmbiguousType(string)) { + return STYLE_PLAIN; + } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; + } + if (indentPerLevel > 9 && needIndentIndicator(string)) { + return STYLE_DOUBLE; + } + if (!forceQuotes) { + return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL; + } + return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE; + } + function writeScalar(state, string, level, iskey, inblock) { + state.dump = (function() { + if (string.length === 0) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''"; + } + if (!state.noCompatMode) { + if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) { + return state.quotingType === QUOTING_TYPE_DOUBLE ? '"' + string + '"' : "'" + string + "'"; + } + } + var indent = state.indent * Math.max(1, level); + var lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); + var singleLineOnly = iskey || state.flowLevel > -1 && level >= state.flowLevel; + function testAmbiguity(string2) { + return testImplicitResolving(state, string2); + } + switch (chooseScalarStyle( + string, + singleLineOnly, + state.indent, + lineWidth, + testAmbiguity, + state.quotingType, + state.forceQuotes && !iskey, + inblock + )) { + case STYLE_PLAIN: + return string; + case STYLE_SINGLE: + return "'" + string.replace(/'/g, "''") + "'"; + case STYLE_LITERAL: + return "|" + blockHeader(string, state.indent) + dropEndingNewline(indentString(string, indent)); + case STYLE_FOLDED: + return ">" + blockHeader(string, state.indent) + dropEndingNewline(indentString(foldString(string, lineWidth), indent)); + case STYLE_DOUBLE: + return '"' + escapeString(string) + '"'; + default: + throw new exception("impossible error: invalid scalar style"); + } + })(); + } + function blockHeader(string, indentPerLevel) { + var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ""; + var clip = string[string.length - 1] === "\n"; + var keep = clip && (string[string.length - 2] === "\n" || string === "\n"); + var chomp = keep ? "+" : clip ? "" : "-"; + return indentIndicator + chomp + "\n"; + } + function dropEndingNewline(string) { + return string[string.length - 1] === "\n" ? string.slice(0, -1) : string; + } + function foldString(string, width) { + var lineRe = /(\n+)([^\n]*)/g; + var result = (function() { + var nextLF = string.indexOf("\n"); + nextLF = nextLF !== -1 ? nextLF : string.length; + lineRe.lastIndex = nextLF; + return foldLine(string.slice(0, nextLF), width); + })(); + var prevMoreIndented = string[0] === "\n" || string[0] === " "; + var moreIndented; + var match; + while (match = lineRe.exec(string)) { + var prefix = match[1], line = match[2]; + moreIndented = line[0] === " "; + result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width); + prevMoreIndented = moreIndented; + } + return result; + } + function foldLine(line, width) { + if (line === "" || line[0] === " ") return line; + var breakRe = / [^ ]/g; + var match; + var start = 0, end, curr = 0, next = 0; + var result = ""; + while (match = breakRe.exec(line)) { + next = match.index; + if (next - start > width) { + end = curr > start ? curr : next; + result += "\n" + line.slice(start, end); + start = end + 1; + } + curr = next; + } + result += "\n"; + if (line.length - start > width && curr > start) { + result += line.slice(start, curr) + "\n" + line.slice(curr + 1); + } else { + result += line.slice(start); + } + return result.slice(1); + } + function escapeString(string) { + var result = ""; + var char = 0; + var escapeSeq; + for (var i2 = 0; i2 < string.length; char >= 65536 ? i2 += 2 : i2++) { + char = codePointAt(string, i2); + escapeSeq = ESCAPE_SEQUENCES[char]; + if (!escapeSeq && isPrintable(char)) { + result += string[i2]; + if (char >= 65536) result += string[i2 + 1]; + } else { + result += escapeSeq || encodeHex(char); + } + } + return result; + } + function writeFlowSequence(state, level, object) { + var _result = "", _tag = state.tag, index2, length, value; + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + value = object[index2]; + if (state.replacer) { + value = state.replacer.call(object, String(index2), value); + } + if (writeNode(state, level, value, false, false) || typeof value === "undefined" && writeNode(state, level, null, false, false)) { + if (_result !== "") _result += "," + (!state.condenseFlow ? " " : ""); + _result += state.dump; + } + } + state.tag = _tag; + state.dump = "[" + _result + "]"; + } + function writeBlockSequence(state, level, object, compact) { + var _result = "", _tag = state.tag, index2, length, value; + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + value = object[index2]; + if (state.replacer) { + value = state.replacer.call(object, String(index2), value); + } + if (writeNode(state, level + 1, value, true, true, false, true) || typeof value === "undefined" && writeNode(state, level + 1, null, true, true, false, true)) { + if (!compact || _result !== "") { + _result += generateNextLine(state, level); + } + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + _result += "-"; + } else { + _result += "- "; + } + _result += state.dump; + } + } + state.tag = _tag; + state.dump = _result || "[]"; + } + function writeFlowMapping(state, level, object) { + var _result = "", _tag = state.tag, objectKeyList = Object.keys(object), index2, length, objectKey, objectValue, pairBuffer; + for (index2 = 0, length = objectKeyList.length; index2 < length; index2 += 1) { + pairBuffer = ""; + if (_result !== "") pairBuffer += ", "; + if (state.condenseFlow) pairBuffer += '"'; + objectKey = objectKeyList[index2]; + objectValue = object[objectKey]; + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); + } + if (!writeNode(state, level, objectKey, false, false)) { + continue; + } + if (state.dump.length > 1024) pairBuffer += "? "; + pairBuffer += state.dump + (state.condenseFlow ? '"' : "") + ":" + (state.condenseFlow ? "" : " "); + if (!writeNode(state, level, objectValue, false, false)) { + continue; + } + pairBuffer += state.dump; + _result += pairBuffer; + } + state.tag = _tag; + state.dump = "{" + _result + "}"; + } + function writeBlockMapping(state, level, object, compact) { + var _result = "", _tag = state.tag, objectKeyList = Object.keys(object), index2, length, objectKey, objectValue, explicitPair, pairBuffer; + if (state.sortKeys === true) { + objectKeyList.sort(); + } else if (typeof state.sortKeys === "function") { + objectKeyList.sort(state.sortKeys); + } else if (state.sortKeys) { + throw new exception("sortKeys must be a boolean or a function"); + } + for (index2 = 0, length = objectKeyList.length; index2 < length; index2 += 1) { + pairBuffer = ""; + if (!compact || _result !== "") { + pairBuffer += generateNextLine(state, level); + } + objectKey = objectKeyList[index2]; + objectValue = object[objectKey]; + if (state.replacer) { + objectValue = state.replacer.call(object, objectKey, objectValue); + } + if (!writeNode(state, level + 1, objectKey, true, true, true)) { + continue; + } + explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024; + if (explicitPair) { + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += "?"; + } else { + pairBuffer += "? "; + } + } + pairBuffer += state.dump; + if (explicitPair) { + pairBuffer += generateNextLine(state, level); + } + if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { + continue; + } + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += ":"; + } else { + pairBuffer += ": "; + } + pairBuffer += state.dump; + _result += pairBuffer; + } + state.tag = _tag; + state.dump = _result || "{}"; + } + function detectType(state, object, explicit) { + var _result, typeList, index2, length, type2, style; + typeList = explicit ? state.explicitTypes : state.implicitTypes; + for (index2 = 0, length = typeList.length; index2 < length; index2 += 1) { + type2 = typeList[index2]; + if ((type2.instanceOf || type2.predicate) && (!type2.instanceOf || typeof object === "object" && object instanceof type2.instanceOf) && (!type2.predicate || type2.predicate(object))) { + if (explicit) { + if (type2.multi && type2.representName) { + state.tag = type2.representName(object); + } else { + state.tag = type2.tag; + } + } else { + state.tag = "?"; + } + if (type2.represent) { + style = state.styleMap[type2.tag] || type2.defaultStyle; + if (_toString.call(type2.represent) === "[object Function]") { + _result = type2.represent(object, style); + } else if (_hasOwnProperty.call(type2.represent, style)) { + _result = type2.represent[style](object, style); + } else { + throw new exception("!<" + type2.tag + '> tag resolver accepts not "' + style + '" style'); + } + state.dump = _result; + } + return true; + } + } + return false; + } + function writeNode(state, level, object, block, compact, iskey, isblockseq) { + state.tag = null; + state.dump = object; + if (!detectType(state, object, false)) { + detectType(state, object, true); + } + var type2 = _toString.call(state.dump); + var inblock = block; + var tagStr; + if (block) { + block = state.flowLevel < 0 || state.flowLevel > level; + } + var objectOrArray = type2 === "[object Object]" || type2 === "[object Array]", duplicateIndex, duplicate; + if (objectOrArray) { + duplicateIndex = state.duplicates.indexOf(object); + duplicate = duplicateIndex !== -1; + } + if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) { + compact = false; + } + if (duplicate && state.usedDuplicates[duplicateIndex]) { + state.dump = "*ref_" + duplicateIndex; + } else { + if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { + state.usedDuplicates[duplicateIndex] = true; + } + if (type2 === "[object Object]") { + if (block && Object.keys(state.dump).length !== 0) { + writeBlockMapping(state, level, state.dump, compact); + if (duplicate) { + state.dump = "&ref_" + duplicateIndex + state.dump; + } + } else { + writeFlowMapping(state, level, state.dump); + if (duplicate) { + state.dump = "&ref_" + duplicateIndex + " " + state.dump; + } + } + } else if (type2 === "[object Array]") { + if (block && state.dump.length !== 0) { + if (state.noArrayIndent && !isblockseq && level > 0) { + writeBlockSequence(state, level - 1, state.dump, compact); + } else { + writeBlockSequence(state, level, state.dump, compact); + } + if (duplicate) { + state.dump = "&ref_" + duplicateIndex + state.dump; + } + } else { + writeFlowSequence(state, level, state.dump); + if (duplicate) { + state.dump = "&ref_" + duplicateIndex + " " + state.dump; + } + } + } else if (type2 === "[object String]") { + if (state.tag !== "?") { + writeScalar(state, state.dump, level, iskey, inblock); + } + } else if (type2 === "[object Undefined]") { + return false; + } else { + if (state.skipInvalid) return false; + throw new exception("unacceptable kind of an object to dump " + type2); + } + if (state.tag !== null && state.tag !== "?") { + tagStr = encodeURI( + state.tag[0] === "!" ? state.tag.slice(1) : state.tag + ).replace(/!/g, "%21"); + if (state.tag[0] === "!") { + tagStr = "!" + tagStr; + } else if (tagStr.slice(0, 18) === "tag:yaml.org,2002:") { + tagStr = "!!" + tagStr.slice(18); + } else { + tagStr = "!<" + tagStr + ">"; + } + state.dump = tagStr + " " + state.dump; + } + } + return true; + } + function getDuplicateReferences(object, state) { + var objects = [], duplicatesIndexes = [], index2, length; + inspectNode(object, objects, duplicatesIndexes); + for (index2 = 0, length = duplicatesIndexes.length; index2 < length; index2 += 1) { + state.duplicates.push(objects[duplicatesIndexes[index2]]); + } + state.usedDuplicates = new Array(length); + } + function inspectNode(object, objects, duplicatesIndexes) { + var objectKeyList, index2, length; + if (object !== null && typeof object === "object") { + index2 = objects.indexOf(object); + if (index2 !== -1) { + if (duplicatesIndexes.indexOf(index2) === -1) { + duplicatesIndexes.push(index2); + } + } else { + objects.push(object); + if (Array.isArray(object)) { + for (index2 = 0, length = object.length; index2 < length; index2 += 1) { + inspectNode(object[index2], objects, duplicatesIndexes); + } + } else { + objectKeyList = Object.keys(object); + for (index2 = 0, length = objectKeyList.length; index2 < length; index2 += 1) { + inspectNode(object[objectKeyList[index2]], objects, duplicatesIndexes); + } + } + } + } + } + function dump$1(input, options) { + options = options || {}; + var state = new State(options); + if (!state.noRefs) getDuplicateReferences(input, state); + var value = input; + if (state.replacer) { + value = state.replacer.call({ "": value }, "", value); + } + if (writeNode(state, 0, value, true, true)) return state.dump + "\n"; + return ""; + } + var dump_1 = dump$1; + var dumper = { + dump: dump_1 + }; + var dump = dumper.dump; const defaultJsonIndent = 2; function tickWrap(plugin, content) { - return `\`\`\`${plugin} + return ` + + +\`\`\`${plugin} ${content} -\`\`\``; +\`\`\` + + +`; + } + function jsonWrap(type2, content) { + return tickWrap("json " + type2, content); } - function jsonWrap(type, content) { - return tickWrap("json " + type, content); + function yamlWrap(type2, content) { + return tickWrap("yaml " + type2, trimTrailingNewline(content)); } function chartWrap(spec) { const chartType = getChartType(spec); return jsonWrap(chartType, JSON.stringify(spec, null, defaultJsonIndent)); } + function chartWrapYaml(spec) { + const chartType = getChartType(spec); + return yamlWrap(chartType, dump(spec, { indent: defaultJsonIndent })); + } function mdContainerWrap(classname, id, content) { return `::: ${classname} {#${id}} + ${content} :::`; } + const defaultPluginFormat = { + "*": "yaml", + "tabulator": "json", + "vega": "json", + "vega-lite": "json" + }; const defaultOptions = { - extraNewlineCount: 1 + extraNewlines: 2, + pluginFormat: defaultPluginFormat }; + function getPluginFormat(pluginName, pluginFormat) { + if (pluginFormat[pluginName]) { + return pluginFormat[pluginName]; + } + if (pluginFormat["*"]) { + return pluginFormat["*"]; + } + return "json"; + } function targetMarkdown(page, options) { const finalOptions = { ...defaultOptions, ...options }; + const finalPluginFormat = { ...defaultPluginFormat, ...options == null ? void 0 : options.pluginFormat }; const mdSections = []; const dataLoaders = page.dataLoaders || []; const variables = page.variables || []; @@ -355,13 +1739,14 @@ ${content} mdSections.push(jsonWrap("google-fonts", JSON.stringify(style.googleFonts, null, defaultJsonIndent))); } } - const tableElements = page.groups.flatMap((group) => group.elements.filter((e) => typeof e !== "string" && e.type === "table")); - const vegaScope = dataLoaderMarkdown(dataLoaders.filter((dl) => dl.type !== "spec"), variables, tableElements); + const tabulatorElements = page.groups.flatMap((group) => group.elements.filter((e) => typeof e !== "string" && e.type === "tabulator")); + const vegaScope = dataLoaderMarkdown(dataLoaders.filter((dl) => dl.type !== "spec"), variables, tabulatorElements); for (const dataLoader of dataLoaders.filter((dl) => dl.type === "spec")) { - mdSections.push(chartWrap(dataLoader.spec)); + const useYaml = getPluginFormat("vega", finalPluginFormat) === "yaml"; + mdSections.push(useYaml ? chartWrapYaml(dataLoader.spec) : chartWrap(dataLoader.spec)); } for (const group of page.groups) { - mdSections.push(mdContainerWrap(defaultCommonOptions.groupClassName, group.groupId, groupMarkdown(group, variables, vegaScope, page.resources))); + mdSections.push(mdContainerWrap(defaultCommonOptions.groupClassName, group.groupId, groupMarkdown(group, variables, vegaScope, page.resources, finalPluginFormat))); } const { data, signals } = vegaScope.spec; if ((data == null ? void 0 : data.length) === 0) { @@ -378,7 +1763,8 @@ ${content} delete vegaScope.spec.signals; } if (vegaScope.spec.data || vegaScope.spec.signals) { - mdSections.unshift(chartWrap(vegaScope.spec)); + const useYaml = getPluginFormat("vega", finalPluginFormat) === "yaml"; + mdSections.unshift(useYaml ? chartWrapYaml(vegaScope.spec) : chartWrap(vegaScope.spec)); } if (page.notes) { if (Array.isArray(page.notes)) { @@ -395,12 +1781,11 @@ ${content} mdSections.unshift(tickWrap("#", JSON.stringify(page.notes, null, defaultJsonIndent))); } } - const newLines = "\n".repeat(1 + finalOptions.extraNewlineCount); - const markdown = mdSections.join(newLines); - return markdown; + const markdown = mdSections.join("\n"); + return normalizeNewlines(markdown, finalOptions.extraNewlines).trim(); } - function dataLoaderMarkdown(dataSources, variables, tableElements) { - const spec = createSpecWithVariables(variables, tableElements); + function dataLoaderMarkdown(dataSources, variables, tabulatorElements) { + const spec = createSpecWithVariables(variables, tabulatorElements); const vegaScope = new VegaScope(spec); for (const dataSource of dataSources) { switch (dataSource.type) { @@ -420,12 +1805,18 @@ ${content} } return vegaScope; } - function groupMarkdown(group, variables, vegaScope, resources) { + function groupMarkdown(group, variables, vegaScope, resources, pluginFormat) { var _a, _b, _c, _d, _e; const mdElements = []; const addSpec = (pluginName, spec, indent = true) => { - const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); - mdElements.push(jsonWrap(pluginName, content)); + const format = getPluginFormat(pluginName, pluginFormat); + if (format === "yaml") { + const content = indent ? dump(spec, { indent: defaultJsonIndent }) : dump(spec); + mdElements.push(yamlWrap(pluginName, content)); + } else { + const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); + mdElements.push(jsonWrap(pluginName, content)); + } }; for (const element of group.elements) { if (typeof element === "string") { @@ -438,7 +1829,9 @@ ${content} if (!spec) { mdElements.push("![Chart Spinner](/img/chart-spinner.gif)"); } else { - mdElements.push(chartWrap(spec)); + const chartType = getChartType(spec); + const useYaml = getPluginFormat(chartType, pluginFormat) === "yaml"; + mdElements.push(useYaml ? chartWrapYaml(spec) : chartWrap(spec)); } break; } @@ -525,10 +1918,13 @@ ${content} addSpec("slider", sliderSpec, false); break; } - case "table": { + case "tabulator": { const { dataSourceName, variableId, tabulatorOptions, editable } = element; - const tableSpec = { dataSourceName, variableId, tabulatorOptions, editable }; - addSpec("tabulator", tableSpec); + const tabulatorSpec = { dataSourceName, tabulatorOptions, editable }; + if (variableId) { + tabulatorSpec.variableId = variableId; + } + addSpec("tabulator", tabulatorSpec); break; } case "textbox": { @@ -551,11 +1947,21 @@ ${content} mdElements.push(tickWrap("#", JSON.stringify(element))); } } - const markdown = mdElements.join("\n\n"); - return markdown; + const markdown = mdElements.join("\n"); + return trimTrailingNewline(markdown); + } + function trimTrailingNewline(s) { + if (s.endsWith("\n")) { + return s.slice(0, -1); + } + return s; + } + function normalizeNewlines(text, extra) { + return text.replace(/(\n\s*){4,}/g, "\n".repeat(1 + extra)) + "\n"; } const index$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, + normalizeNewlines, targetMarkdown }, Symbol.toStringTag, { value: "Module" })); const rendererHtml = ` @@ -727,6 +2133,7 @@ document.addEventListener('DOMContentLoaded', () => { + @@ -118,7 +119,7 @@ "## JSON Data", "Load data from a static JSON array.", { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonData", "variableId": "jsonTable", "tabulatorOptions": { @@ -135,7 +136,7 @@ "## CSV from URL", "Load CSV data from a fixed URL.", { - "type": "table", + "type": "tabulator", "dataSourceName": "csvData", "variableId": "csvTable", "tabulatorOptions": { @@ -161,7 +162,7 @@ ] }, { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonUrlVariable", "variableId": "jsonUrlTable", "tabulatorOptions": { @@ -178,7 +179,7 @@ "## Inline CSV Data", "You can provide CSV data directly as a single string.", { - "type": "table", + "type": "tabulator", "dataSourceName": "inlineCsvData", "variableId": "inlineCsvTable", "tabulatorOptions": { diff --git a/packages/compiler/package.json b/packages/compiler/package.json index b1ae7c9d..625185b2 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -24,5 +24,9 @@ "url": "https://github.com/microsoft/chartifact/issues" }, "homepage": "https://github.com/microsoft/chartifact#readme", - "description": "" -} \ No newline at end of file + "description": "", + "dependencies": { + "@types/js-yaml": "^4.0.9", + "js-yaml": "^4.1.0" + } +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 10443e0b..60e25f6a 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -2,4 +2,4 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ -export { targetMarkdown } from './md.js'; +export { targetMarkdown, normalizeNewlines } from './md.js'; diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts index db42e9a9..dfd09379 100644 --- a/packages/compiler/src/md.ts +++ b/packages/compiler/src/md.ts @@ -4,45 +4,81 @@ */ import { Spec as VegaSpec } from 'vega-typings'; import { TopLevelSpec as VegaLiteSpec } from "vega-lite"; -import { DataSource, ElementGroup, InteractiveDocument, TableElement, Variable } from '@microsoft/chartifact-schema'; +import { DataSource, ElementGroup, InteractiveDocument, TabulatorElement, Variable } from '@microsoft/chartifact-schema'; import { getChartType } from './util.js'; import { addDynamicDataLoaderToSpec, addStaticDataLoaderToSpec } from './loader.js'; import { Plugins } from '@microsoft/chartifact-markdown'; import { VegaScope } from './scope.js'; import { createSpecWithVariables } from './spec.js'; import { defaultCommonOptions } from 'common'; +import * as yaml from 'js-yaml'; const defaultJsonIndent = 2; function tickWrap(plugin: string, content: string) { - return `\`\`\`${plugin}\n${content}\n\`\`\``; + return `\n\n\n\`\`\`${plugin}\n${content}\n\`\`\`\n\n\n`; } function jsonWrap(type: string, content: string) { return tickWrap('json ' + type, content); } +function yamlWrap(type: string, content: string) { + return tickWrap('yaml ' + type, trimTrailingNewline(content)); +} + function chartWrap(spec: VegaSpec | VegaLiteSpec) { const chartType = getChartType(spec); return jsonWrap(chartType, JSON.stringify(spec, null, defaultJsonIndent)); } +function chartWrapYaml(spec: VegaSpec | VegaLiteSpec) { + const chartType = getChartType(spec); + return yamlWrap(chartType, yaml.dump(spec, { indent: defaultJsonIndent })); +} + function mdContainerWrap(classname: string, id: string, content: string) { return `::: ${classname} {#${id}} + ${content} :::`; } export interface TargetMarkdownOptions { - extraNewlineCount?: number; + extraNewlines?: number; + pluginFormat?: Record; } +const defaultPluginFormat: Record = { + "*": "yaml", + "tabulator": "json", + "vega": "json", + "vega-lite": "json" +}; + const defaultOptions: TargetMarkdownOptions = { - extraNewlineCount: 1, + extraNewlines: 2, + pluginFormat: defaultPluginFormat, }; +function getPluginFormat(pluginName: string, pluginFormat: Record): "json" | "yaml" { + // Check for specific plugin name first + if (pluginFormat[pluginName]) { + return pluginFormat[pluginName]; + } + // Fall back to wildcard + if (pluginFormat["*"]) { + return pluginFormat["*"]; + } + // Ultimate fallback + return "json"; +} + export function targetMarkdown(page: InteractiveDocument, options?: TargetMarkdownOptions) { const finalOptions = { ...defaultOptions, ...options }; + // Merge plugin format with defaults, user options take precedence + const finalPluginFormat = { ...defaultPluginFormat, ...options?.pluginFormat }; + const mdSections: string[] = []; const dataLoaders = page.dataLoaders || []; const variables = page.variables || []; @@ -63,19 +99,20 @@ export function targetMarkdown(page: InteractiveDocument, options?: TargetMarkdo } } - const tableElements = page.groups.flatMap(group => group.elements.filter(e => typeof e !== 'string' && e.type === 'table')); + const tabulatorElements = page.groups.flatMap(group => group.elements.filter(e => typeof e !== 'string' && e.type === 'tabulator')); - const vegaScope = dataLoaderMarkdown(dataLoaders.filter(dl => dl.type !== 'spec'), variables, tableElements); + const vegaScope = dataLoaderMarkdown(dataLoaders.filter(dl => dl.type !== 'spec'), variables, tabulatorElements); for (const dataLoader of dataLoaders.filter(dl => dl.type === 'spec')) { - mdSections.push(chartWrap(dataLoader.spec)); + const useYaml = getPluginFormat('vega', finalPluginFormat) === 'yaml'; + mdSections.push(useYaml ? chartWrapYaml(dataLoader.spec) : chartWrap(dataLoader.spec)); } for (const group of page.groups) { mdSections.push(mdContainerWrap( defaultCommonOptions.groupClassName, group.groupId, - groupMarkdown(group, variables, vegaScope, page.resources) + groupMarkdown(group, variables, vegaScope, page.resources, finalPluginFormat) )); } @@ -97,7 +134,8 @@ export function targetMarkdown(page: InteractiveDocument, options?: TargetMarkdo if (vegaScope.spec.data || vegaScope.spec.signals) { //spec is towards the top of the markdown file - mdSections.unshift(chartWrap(vegaScope.spec)); + const useYaml = getPluginFormat('vega', finalPluginFormat) === 'yaml'; + mdSections.unshift(useYaml ? chartWrapYaml(vegaScope.spec) : chartWrap(vegaScope.spec)); } if (page.notes) { @@ -118,16 +156,15 @@ export function targetMarkdown(page: InteractiveDocument, options?: TargetMarkdo } } - const newLines = '\n'.repeat(1 + finalOptions.extraNewlineCount); + const markdown = mdSections.join('\n'); - const markdown = mdSections.join(newLines); - return markdown; + return normalizeNewlines(markdown, finalOptions.extraNewlines).trim(); } -function dataLoaderMarkdown(dataSources: DataSource[], variables: Variable[], tableElements: TableElement[]) { +function dataLoaderMarkdown(dataSources: DataSource[], variables: Variable[], tabulatorElements: TabulatorElement[]) { //create a Vega spec with all variables - const spec = createSpecWithVariables(variables, tableElements); + const spec = createSpecWithVariables(variables, tabulatorElements); const vegaScope = new VegaScope(spec); for (const dataSource of dataSources) { @@ -152,12 +189,18 @@ function dataLoaderMarkdown(dataSources: DataSource[], variables: Variable[], ta type pluginSpecs = Plugins.CheckboxSpec | Plugins.DropdownSpec | Plugins.ImageSpec | Plugins.MermaidSpec | Plugins.PresetsSpec | Plugins.SliderSpec | Plugins.TabulatorSpec | Plugins.TextboxSpec; -function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: VegaScope, resources: { charts?: { [chartKey: string]: VegaSpec | VegaLiteSpec } }) { +function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: VegaScope, resources: { charts?: { [chartKey: string]: VegaSpec | VegaLiteSpec } }, pluginFormat: Record) { const mdElements: string[] = []; const addSpec = (pluginName: Plugins.PluginNames, spec: pluginSpecs, indent = true) => { - const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); - mdElements.push(jsonWrap(pluginName, content)); + const format = getPluginFormat(pluginName, pluginFormat); + if (format === 'yaml') { + const content = indent ? yaml.dump(spec, { indent: defaultJsonIndent }) : yaml.dump(spec); + mdElements.push(yamlWrap(pluginName, content)); + } else { + const content = indent ? JSON.stringify(spec, null, defaultJsonIndent) : JSON.stringify(spec); + mdElements.push(jsonWrap(pluginName, content)); + } } for (const element of group.elements) { @@ -173,7 +216,9 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve //add a markdown element (not a chart element) with an image of the spinner at /img/chart-spinner.gif mdElements.push('![Chart Spinner](/img/chart-spinner.gif)'); } else { - mdElements.push(chartWrap(spec)); + const chartType = getChartType(spec); + const useYaml = getPluginFormat(chartType, pluginFormat) === 'yaml'; + mdElements.push(useYaml ? chartWrapYaml(spec) : chartWrap(spec)); } break; } @@ -264,10 +309,13 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve addSpec('slider', sliderSpec, false); break; } - case 'table': { + case 'tabulator': { const { dataSourceName, variableId, tabulatorOptions, editable } = element; - const tableSpec: Plugins.TabulatorSpec = { dataSourceName, variableId, tabulatorOptions, editable }; - addSpec('tabulator', tableSpec); + const tabulatorSpec: Plugins.TabulatorSpec = { dataSourceName, tabulatorOptions, editable }; + if (variableId) { + tabulatorSpec.variableId = variableId; + } + addSpec('tabulator', tabulatorSpec); break; } case 'textbox': { @@ -292,6 +340,18 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve mdElements.push(tickWrap('#', JSON.stringify(element))); } } - const markdown = mdElements.join('\n\n'); - return markdown; + + const markdown = mdElements.join('\n'); + return trimTrailingNewline(markdown) +} + +function trimTrailingNewline(s: string) { + if (s.endsWith('\n')) { + return s.slice(0, -1); + } + return s; +} + +export function normalizeNewlines(text: string, extra: number) { + return text.replace(/(\n\s*){4,}/g, '\n'.repeat(1 + extra)) + '\n'; } diff --git a/packages/compiler/src/spec.ts b/packages/compiler/src/spec.ts index aa348da1..1e2a8674 100644 --- a/packages/compiler/src/spec.ts +++ b/packages/compiler/src/spec.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. */ import { Spec as VegaSpec } from 'vega-typings'; -import { Variable, DataLoader, TableElement } from '@microsoft/chartifact-schema'; +import { Variable, DataLoader, TabulatorElement } from '@microsoft/chartifact-schema'; import { SourceData, ValuesData, Signal, NewSignal } from "vega"; import { topologicalSort } from "./sort.js"; export const $schema = "https://vega.github.io/schema/vega/v5.json"; -export function createSpecWithVariables(variables: Variable[], tableElements: TableElement[], stubDataLoaders?: DataLoader[]) { +export function createSpecWithVariables(variables: Variable[], tabulatorElements: TabulatorElement[], stubDataLoaders?: DataLoader[]) { //preload with variables as signals const spec: VegaSpec = { @@ -19,9 +19,12 @@ export function createSpecWithVariables(variables: Variable[], tableElements: Ta data: [], }; - //add table elements as data sources - tableElements.forEach((table) => { - const { variableId } = table; + //add tabulator elements as data sources + tabulatorElements.forEach((tabulator) => { + const { variableId } = tabulator; + if (!variableId) { + return; + } spec.signals.push(dataAsSignal(variableId)); spec.data.unshift({ name: variableId, diff --git a/packages/compiler/vite.bundle.config.js b/packages/compiler/vite.bundle.config.js index 00c44387..2a39dfb8 100644 --- a/packages/compiler/vite.bundle.config.js +++ b/packages/compiler/vite.bundle.config.js @@ -9,6 +9,7 @@ const commonOutputConfig = { globals: { 'vega': 'vega', 'vega-lite': 'vegaLite', + 'js-yaml': 'jsyaml', }, entryFileNames: 'chartifact.compiler.umd.js', }; @@ -22,7 +23,7 @@ export default defineConfig({ emptyOutDir: false, rollupOptions: { // External dependencies that the library expects consumers to provide - external: ['vega', 'vega-lite'], + external: ['vega', 'vega-lite', 'js-yaml'], output: [ { ...commonOutputConfig, diff --git a/packages/editor/index.html b/packages/editor/index.html index f35bb85f..1da7abd3 100644 --- a/packages/editor/index.html +++ b/packages/editor/index.html @@ -12,6 +12,7 @@ + diff --git a/packages/editor/src/app.tsx b/packages/editor/src/app.tsx index a564dd95..4fc718f3 100644 --- a/packages/editor/src/app.tsx +++ b/packages/editor/src/app.tsx @@ -176,9 +176,8 @@ const initialPage: InteractiveDocument = { "elements": [ "# Seattle Weather\n\nData table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/features/2.data-sources.idoc.json b/packages/examples/json/features/2.data-sources.idoc.json index 6f133bab..5ff1c4b5 100644 --- a/packages/examples/json/features/2.data-sources.idoc.json +++ b/packages/examples/json/features/2.data-sources.idoc.json @@ -54,7 +54,7 @@ "## JSON Data", "Load data from a static JSON array.", { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonData", "variableId": "jsonTable", "tabulatorOptions": { @@ -71,14 +71,8 @@ "## CSV from URL", "Load CSV data from a fixed URL.", { - "type": "table", - "dataSourceName": "csvData", - "variableId": "csvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "csvData" } ] }, @@ -97,7 +91,7 @@ ] }, { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonUrlVariable", "variableId": "jsonUrlTable", "tabulatorOptions": { @@ -114,14 +108,8 @@ "## Inline CSV Data", "You can provide CSV data directly as a single string.", { - "type": "table", - "dataSourceName": "inlineCsvData", - "variableId": "inlineCsvTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "100px" - } + "type": "tabulator", + "dataSourceName": "inlineCsvData" } ] } diff --git a/packages/examples/json/features/4.table.idoc.json b/packages/examples/json/features/4.table.idoc.json index 4b32e904..1f732598 100644 --- a/packages/examples/json/features/4.table.idoc.json +++ b/packages/examples/json/features/4.table.idoc.json @@ -21,20 +21,13 @@ "## Table\nUse tables for displaying and interacting with tabular data.", "### Basic Table", { - "type": "table", - "dataSourceName": "sampleData", - "variableId": "table1", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "sampleData" }, "### Selectable Table", { - "type": "table", + "type": "tabulator", "dataSourceName": "sampleData", - "variableId": "table2", "tabulatorOptions": { "autoColumns": true, "layout": "fitColumns", diff --git a/packages/examples/json/features/6.data-transformations.idoc.json b/packages/examples/json/features/6.data-transformations.idoc.json index b9095cb3..69a2a6b0 100644 --- a/packages/examples/json/features/6.data-transformations.idoc.json +++ b/packages/examples/json/features/6.data-transformations.idoc.json @@ -61,25 +61,13 @@ }, "### Filtered Products (in stock, price ≤ ${{maxPrice}})", { - "type": "table", - "dataSourceName": "rawData", - "variableId": "filteredTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "rawData" }, "### Category Statistics", { - "type": "table", - "dataSourceName": "categoryStats", - "variableId": "categoryStatsTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "150px" - } + "type": "tabulator", + "dataSourceName": "categoryStats" } ] } diff --git a/packages/examples/json/features/7.dropdown.idoc.json b/packages/examples/json/features/7.dropdown.idoc.json index 749a7946..afeb5c86 100644 --- a/packages/examples/json/features/7.dropdown.idoc.json +++ b/packages/examples/json/features/7.dropdown.idoc.json @@ -42,14 +42,8 @@ "Selected: **{{selectedProduct}}**", "### Product Data", { - "type": "table", - "dataSourceName": "productData", - "variableId": "productTable", - "tabulatorOptions": { - "autoColumns": true, - "layout": "fitColumns", - "maxHeight": "200px" - } + "type": "tabulator", + "dataSourceName": "productData" } ] } diff --git a/packages/examples/json/features/9.mermaid.idoc.json b/packages/examples/json/features/9.mermaid.idoc.json index 5cf46169..e27bec54 100644 --- a/packages/examples/json/features/9.mermaid.idoc.json +++ b/packages/examples/json/features/9.mermaid.idoc.json @@ -122,7 +122,7 @@ "## Data-Driven Mode", "Template-based diagram generation:", { - "type": "table", + "type": "tabulator", "dataSourceName": "jsonData", "variableId": "jsonTable", "editable": true, @@ -193,7 +193,7 @@ "## More Complex Example", "Network diagram with servers and connections:", { - "type": "table", + "type": "tabulator", "dataSourceName": "networkData", "variableId": "networkTable", "editable": true, diff --git a/packages/examples/json/grocery-list.idoc.json b/packages/examples/json/grocery-list.idoc.json index 1f75f4ca..69baf245 100644 --- a/packages/examples/json/grocery-list.idoc.json +++ b/packages/examples/json/grocery-list.idoc.json @@ -171,7 +171,7 @@ "elements": [ "## Select the items you want to buy\n", { - "type": "table", + "type": "tabulator", "variableId": "itemsData_selected", "dataSourceName": "itemsData", "tabulatorOptions": { diff --git a/packages/examples/json/mermaid-org chart.idoc.json b/packages/examples/json/mermaid-org chart.idoc.json index 9de990b6..568c4784 100644 --- a/packages/examples/json/mermaid-org chart.idoc.json +++ b/packages/examples/json/mermaid-org chart.idoc.json @@ -156,7 +156,7 @@ "## JSON Data", "Load data from a static JSON array.", { - "type": "table", + "type": "tabulator", "dataSourceName": "orgChartData", "variableId": "orgChartDataTable", "editable": true, diff --git a/packages/examples/json/sales-dashboard.idoc.json b/packages/examples/json/sales-dashboard.idoc.json index 278138c7..72048290 100644 --- a/packages/examples/json/sales-dashboard.idoc.json +++ b/packages/examples/json/sales-dashboard.idoc.json @@ -161,9 +161,8 @@ "elements": [ "### Sales Data", { - "type": "table", - "dataSourceName": "salesData", - "variableId": "salesSelected" + "type": "tabulator", + "dataSourceName": "salesData" } ] } diff --git a/packages/examples/json/sales-report.idoc.json b/packages/examples/json/sales-report.idoc.json new file mode 100644 index 00000000..a95e12dd --- /dev/null +++ b/packages/examples/json/sales-report.idoc.json @@ -0,0 +1,216 @@ +{ + "$schema": "../../../docs/schema/idoc_v1.json", + "title": "Sales Performance Report", + "style": { + "css": [ + "body { font-family: 'Times New Roman', serif; margin: 0; padding: 40px; background: white; line-height: 1.6; color: #333; }", + ".group { max-width: 800px; margin: 0 auto; }", + "h1 { text-align: center; font-size: 2.5em; margin-bottom: 0.5em; color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 20px; }", + "h2 { font-size: 1.8em; margin: 40px 0 20px 0; color: #2c3e50; border-bottom: 1px solid #bdc3c7; padding-bottom: 10px; }", + "h3 { font-size: 1.3em; margin: 30px 0 15px 0; color: #34495e; }", + "blockquote { background: #ecf0f1; padding: 20px; border-left: 5px solid #3498db; margin: 30px 0; font-style: normal; }", + "table { width: 100%; border-collapse: collapse; margin: 20px 0; }", + "td { padding: 10px 20px; border-bottom: 1px solid #ecf0f1; }", + "td:first-child { font-weight: bold; color: #2c3e50; }", + ".chart-container { margin: 30px 0; padding: 20px; background: #fafafa; border: 1px solid #ecf0f1; border-radius: 5px; }", + "p { margin: 15px 0; text-align: justify; }" + ] + }, + "dataLoaders": [ + { + "dataSourceName": "salesData", + "type": "inline", + "format": "json", + "content": [ + {"timestamp": "2025-08-01", "order_id": "ORD-1001", "product": "Wireless Mouse", "category": "Electronics", "region": "West", "salesperson": "Jane Smith", "units": 3, "unit_price": 25.00}, + {"timestamp": "2025-08-02", "order_id": "ORD-1002", "product": "Office Chair", "category": "Furniture", "region": "East", "salesperson": "Bob Johnson", "units": 1, "unit_price": 199.99}, + {"timestamp": "2025-08-03", "order_id": "ORD-1003", "product": "Laptop Stand", "category": "Electronics", "region": "North", "salesperson": "Alice Chen", "units": 2, "unit_price": 45.50}, + {"timestamp": "2025-08-05", "order_id": "ORD-1004", "product": "Desk Lamp", "category": "Furniture", "region": "South", "salesperson": "Charlie Brown", "units": 1, "unit_price": 89.00}, + {"timestamp": "2025-08-06", "order_id": "ORD-1005", "product": "Bluetooth Headphones", "category": "Electronics", "region": "West", "salesperson": "Jane Smith", "units": 4, "unit_price": 79.99} + ] + } + ], + "variables": [ + { + "variableId": "revenueCalculation", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dependsOn": ["salesData"], + "vegaExpression": "data('salesData')", + "dataFrameTransformations": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "ops": ["sum"], + "fields": ["revenue"], + "as": ["total"] + } + ] + } + }, + { + "variableId": "totalRevenueFormatted", + "type": "string", + "initialValue": "$0", + "calculation": { + "dependsOn": ["revenueCalculation"], + "vegaExpression": "'$' + format(data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0, ',.2f')" + } + }, + { + "variableId": "totalOrders", + "type": "number", + "initialValue": 0, + "calculation": { + "dependsOn": ["salesData"], + "vegaExpression": "length(data('salesData'))" + } + }, + { + "variableId": "averageOrderValue", + "type": "string", + "initialValue": "$0.00", + "calculation": { + "dependsOn": ["revenueCalculation", "totalOrders"], + "vegaExpression": "'$' + format((data('revenueCalculation')[0] ? data('revenueCalculation')[0].total : 0) / (totalOrders > 0 ? totalOrders : 1), ',.2f')" + } + }, + { + "variableId": "categoryRevenue", + "type": "object", + "isArray": true, + "initialValue": [], + "calculation": { + "dependsOn": ["salesData"], + "vegaExpression": "data('salesData')", + "dataFrameTransformations": [ + { + "type": "formula", + "expr": "datum.units * datum.unit_price", + "as": "revenue" + }, + { + "type": "aggregate", + "groupby": ["category"], + "ops": ["sum"], + "fields": ["revenue"], + "as": ["total_revenue"] + } + ] + } + } + ], + "groups": [ + { + "groupId": "main", + "elements": [ + "# Sales Performance Report", + "**Reporting Period:** August 1-6, 2025 ", + "**Prepared by:** Sales Analytics Team ", + "**Date:** August 24, 2025", + "", + "## Executive Summary", + "", + "> This report analyzes sales performance for the first week of August 2025. Our analysis reveals total revenue of **{{totalRevenueFormatted}}** across **{{totalOrders}} transactions**, with an average order value of **{{averageOrderValue}}**. The data shows strong performance in the Electronics category, which represents the majority of our revenue during this period.", + "", + "## Key Performance Metrics", + "", + "The following table summarizes our core performance indicators for the reporting period:", + "", + "| Metric | Value |", + "|--------|-------|", + "| Total Revenue | {{totalRevenueFormatted}} |", + "| Number of Orders | {{totalOrders}} |", + "| Average Order Value | {{averageOrderValue}} |", + "| Active Sales Regions | 4 regions |", + "| Product Categories | 2 categories |", + "", + "## Revenue Analysis by Product Category", + "", + "Our product portfolio performed differently across categories during this period. The chart below illustrates the revenue distribution:", + "", + { + "type": "chart", + "chartKey": "categoryChart" + }, + "", + "The Electronics category generated the majority of revenue, driven primarily by strong sales of Bluetooth Headphones and Wireless Mouse products. The Furniture category, while smaller in volume, contributed significant value through high-ticket items such as the Office Chair.", + "", + "## Sales Trend Analysis", + "", + "Daily sales performance shows variability throughout the reporting period, with notable patterns emerging:", + "", + { + "type": "chart", + "chartKey": "trendChart" + }, + "", + "The trend analysis reveals that August 6th recorded the highest single-day revenue of $319.96, primarily due to the sale of 4 units of Bluetooth Headphones. August 2nd also showed strong performance with $199.99 in revenue from the Office Chair sale.", + "", + "## Detailed Transaction Data", + "", + "The complete transaction dataset for the reporting period is presented below for reference and further analysis:", + "", + { + "type": "tabulator", + "dataSourceName": "salesData" + }, + "", + "## Conclusions and Recommendations", + "", + "Based on this analysis, we recommend:", + "", + "1. **Focus on Electronics expansion** - Given the strong performance of electronics products, consider expanding this category's inventory and marketing focus.", + "", + "2. **Leverage high-value furniture sales** - While furniture has lower transaction volume, the high average order values suggest opportunity for targeted premium product strategies.", + "", + "3. **Regional performance review** - Jane Smith's performance in the West region generated strong results and could serve as a model for other regions.", + "", + "4. **Inventory planning** - The Bluetooth Headphones' strong performance suggests increasing stock levels for similar high-margin electronics.", + "", + "*This report was generated using interactive data analysis. All figures are calculated dynamically from the underlying transaction data.*" + ] + } + ], + "resources": { + "charts": { + "categoryChart": { + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "categoryRevenue" + }, + "mark": "bar", + "width": 400, + "height": 250, + "encoding": { + "x": {"field": "category", "type": "nominal", "title": "Product Category"}, + "y": {"field": "total_revenue", "type": "quantitative", "title": "Revenue ($)"}, + "color": {"field": "category", "type": "nominal", "scale": {"range": ["#3498db", "#2c3e50"]}} + } + }, + "trendChart": { + "$schema": "https://vega.github.io/schema/vega-lite/v6.json", + "data": { + "name": "salesData" + }, + "transform": [ + {"calculate": "datum.units * datum.unit_price", "as": "revenue"} + ], + "mark": {"type": "line", "point": true, "strokeWidth": 2}, + "width": 500, + "height": 250, + "encoding": { + "x": {"field": "timestamp", "type": "temporal", "title": "Date"}, + "y": {"field": "revenue", "type": "quantitative", "title": "Daily Revenue ($)"}, + "color": {"value": "#3498db"} + } + } + } + } +} diff --git a/packages/examples/json/seattle-weather/4.idoc.json b/packages/examples/json/seattle-weather/4.idoc.json index 5f116926..296e55c1 100644 --- a/packages/examples/json/seattle-weather/4.idoc.json +++ b/packages/examples/json/seattle-weather/4.idoc.json @@ -16,9 +16,8 @@ "elements": [ "# Seattle Weather\n\nData table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/seattle-weather/5.idoc.json b/packages/examples/json/seattle-weather/5.idoc.json index 1ef9c484..a9146019 100644 --- a/packages/examples/json/seattle-weather/5.idoc.json +++ b/packages/examples/json/seattle-weather/5.idoc.json @@ -21,9 +21,8 @@ "elements": [ "# Seattle Weather\n\nData table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/seattle-weather/6.idoc.json b/packages/examples/json/seattle-weather/6.idoc.json index 78b64b58..1a4c8d4e 100644 --- a/packages/examples/json/seattle-weather/6.idoc.json +++ b/packages/examples/json/seattle-weather/6.idoc.json @@ -34,9 +34,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/seattle-weather/7.idoc.json b/packages/examples/json/seattle-weather/7.idoc.json index 455d3f49..5220bf9e 100644 --- a/packages/examples/json/seattle-weather/7.idoc.json +++ b/packages/examples/json/seattle-weather/7.idoc.json @@ -90,9 +90,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/seattle-weather/8.idoc.json b/packages/examples/json/seattle-weather/8.idoc.json index deebf0c8..927b7c98 100644 --- a/packages/examples/json/seattle-weather/8.idoc.json +++ b/packages/examples/json/seattle-weather/8.idoc.json @@ -120,9 +120,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/seattle-weather/9.idoc.json b/packages/examples/json/seattle-weather/9.idoc.json index dce4af14..c1beacf9 100644 --- a/packages/examples/json/seattle-weather/9.idoc.json +++ b/packages/examples/json/seattle-weather/9.idoc.json @@ -186,9 +186,8 @@ }, "Data table:", { - "type": "table", - "dataSourceName": "seattle_weather", - "variableId": "seattle_weather_selected" + "type": "tabulator", + "dataSourceName": "seattle_weather" }, "Here is a stacked bar chart of Seattle weather:\nEach bar represents the count of weather types for each month.\nThe colors distinguish between different weather conditions such as sun, fog, drizzle, rain, and snow.", { diff --git a/packages/examples/json/slides.idoc.json b/packages/examples/json/slides.idoc.json index 950c60c3..4a3c2e7e 100644 --- a/packages/examples/json/slides.idoc.json +++ b/packages/examples/json/slides.idoc.json @@ -203,7 +203,7 @@ "elements": [ "## Select the items you want to buy\n", { - "type": "table", + "type": "tabulator", "dataSourceName": "itemsData", "variableId": "itemsData_selected", "tabulatorOptions": { diff --git a/packages/examples/src/convert.mts b/packages/examples/src/convert.mts index 8700cc26..25124d3e 100644 --- a/packages/examples/src/convert.mts +++ b/packages/examples/src/convert.mts @@ -1,4 +1,4 @@ -import { targetMarkdown } from '@microsoft/chartifact-compiler'; +import { targetMarkdown, normalizeNewlines } from '@microsoft/chartifact-compiler'; import { InteractiveDocument } from '@microsoft/chartifact-schema'; import fs from 'node:fs'; @@ -14,7 +14,7 @@ function convertToMarkdown(interactiveDocument: InteractiveDocument): string { .join('\n'); } - return markdown; + return normalizeNewlines(markdown, 2).trim(); } async function convertJsonFiles(sourceDir: string, destDir: string, jsonCopyBase: string) { diff --git a/packages/host/dev/index.ts b/packages/host/dev/index.ts index ba8b0326..445ee7d4 100644 --- a/packages/host/dev/index.ts +++ b/packages/host/dev/index.ts @@ -20,6 +20,7 @@ class LocalSandbox extends Sandbox { + diff --git a/packages/markdown/index.html b/packages/markdown/index.html index 0b67fc1b..78a1b6d5 100644 --- a/packages/markdown/index.html +++ b/packages/markdown/index.html @@ -9,6 +9,7 @@ + diff --git a/packages/markdown/package.json b/packages/markdown/package.json index 2717423f..8f4e490e 100644 --- a/packages/markdown/package.json +++ b/packages/markdown/package.json @@ -39,5 +39,9 @@ "homepage": "https://github.com/microsoft/chartifact#readme", "devDependencies": { "@types/css-tree": "^2.3.10" + }, + "dependencies": { + "@types/js-yaml": "^4.0.9", + "js-yaml": "^4.1.0" } } diff --git a/packages/markdown/src/factory.ts b/packages/markdown/src/factory.ts index 58a260c2..3f664f96 100644 --- a/packages/markdown/src/factory.ts +++ b/packages/markdown/src/factory.ts @@ -131,6 +131,14 @@ export function create() { return jsonPlugin; } } + // Fourth priority: Check if it starts with "yaml " and extract the plugin name + else if (info.startsWith('yaml ')) { + const yamlPluginName = info.slice(5).trim(); + const yamlPlugin = findPlugin(yamlPluginName); + if (yamlPlugin) { + return yamlPlugin; + } + } } // Fallback to the original fence renderer if no plugin matches diff --git a/packages/markdown/src/plugins/checkbox.ts b/packages/markdown/src/plugins/checkbox.ts index ae3d53d1..2d18450f 100644 --- a/packages/markdown/src/plugins/checkbox.ts +++ b/packages/markdown/src/plugins/checkbox.ts @@ -6,7 +6,7 @@ import { VariableControl } from '@microsoft/chartifact-schema'; import { Batch, IInstance, Plugin } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; interface CheckboxInstance { @@ -23,7 +23,7 @@ const pluginName: PluginNames = 'checkbox'; const className = pluginClassName(pluginName); export const checkboxPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const checkboxInstances: CheckboxInstance[] = []; diff --git a/packages/markdown/src/plugins/config.ts b/packages/markdown/src/plugins/config.ts index c4761d67..6aeefcca 100644 --- a/packages/markdown/src/plugins/config.ts +++ b/packages/markdown/src/plugins/config.ts @@ -7,21 +7,35 @@ import { sanitizedHTML } from "../sanitize.js"; import { PluginNames } from "./interfaces.js"; import { getJsonScriptTag } from "./util.js"; import { SpecReview } from 'common'; +import * as yaml from 'js-yaml'; -export function flaggableJsonPlugin(pluginName: PluginNames, className: string, flagger?: (spec: T) => RawFlaggableSpec, attrs?: object) { +/** + * Creates a plugin that can parse both JSON and YAML formats + */ +export function flaggablePlugin(pluginName: PluginNames, className: string, flagger?: (spec: T) => RawFlaggableSpec, attrs?: object) { const plugin: Plugin = { name: pluginName, fence: (token, index) => { - let json = token.content.trim(); + let content = token.content.trim(); let spec: T; let flaggableSpec: RawFlaggableSpec; + + // Determine format from token info + const info = token.info.trim(); + const isYaml = info.startsWith('yaml '); + const formatName = isYaml ? 'YAML' : 'JSON'; + try { - spec = JSON.parse(json); + if (isYaml) { + spec = yaml.load(content) as T; + } else { + spec = JSON.parse(content); + } } catch (e) { flaggableSpec = { spec: null, hasFlags: true, - reasons: [`malformed JSON`], + reasons: [`malformed ${formatName}`], }; } if (spec) { @@ -32,9 +46,9 @@ export function flaggableJsonPlugin(pluginName: PluginNames, className: strin } } if (flaggableSpec) { - json = JSON.stringify(flaggableSpec); + content = JSON.stringify(flaggableSpec); } - return sanitizedHTML('div', { class: className, id: `${pluginName}-${index}`, ...attrs }, json, true); + return sanitizedHTML('div', { class: className, id: `${pluginName}-${index}`, ...attrs }, content, true); }, hydrateSpecs: (renderer, errorHandler) => { const flagged: SpecReview[] = []; @@ -56,3 +70,5 @@ export function flaggableJsonPlugin(pluginName: PluginNames, className: strin }; return plugin; } + + diff --git a/packages/markdown/src/plugins/css.ts b/packages/markdown/src/plugins/css.ts index 65e45bc8..4f6840ec 100644 --- a/packages/markdown/src/plugins/css.ts +++ b/packages/markdown/src/plugins/css.ts @@ -7,7 +7,7 @@ import { IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; import { sanitizedHTML } from '../sanitize.js'; import * as Csstree from 'css-tree'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; // CSS Tree is expected to be available as a global variable @@ -317,7 +317,7 @@ const pluginName: PluginNames = 'css'; const className = pluginClassName(pluginName); export const cssPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), fence: (token, index) => { const cssContent = token.content.trim(); // Parse and categorize CSS content diff --git a/packages/markdown/src/plugins/dropdown.ts b/packages/markdown/src/plugins/dropdown.ts index 218f9530..2390d515 100644 --- a/packages/markdown/src/plugins/dropdown.ts +++ b/packages/markdown/src/plugins/dropdown.ts @@ -6,7 +6,7 @@ import { DropdownElementProps } from '@microsoft/chartifact-schema'; import { Batch, IInstance, Plugin } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; interface DropdownInstance { @@ -23,7 +23,7 @@ const pluginName: PluginNames = 'dropdown'; const className = pluginClassName(pluginName); export const dropdownPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const dropdownInstances: DropdownInstance[] = []; diff --git a/packages/markdown/src/plugins/google-fonts.ts b/packages/markdown/src/plugins/google-fonts.ts index e32dec9b..67cd6316 100644 --- a/packages/markdown/src/plugins/google-fonts.ts +++ b/packages/markdown/src/plugins/google-fonts.ts @@ -5,7 +5,7 @@ import { GoogleFontsSpec } from '@microsoft/chartifact-schema'; import { IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; import { pluginClassName } from './util.js'; @@ -165,7 +165,7 @@ function inspectGoogleFontsSpec(spec: GoogleFontsSpec): RawFlaggableSpec = { - ...flaggableJsonPlugin(pluginName, className, inspectGoogleFontsSpec), + ...flaggablePlugin(pluginName, className, inspectGoogleFontsSpec), hydrateComponent: async (renderer, errorHandler, specs) => { const googleFontsInstances: GoogleFontsInstance[] = []; let emitted = false; diff --git a/packages/markdown/src/plugins/image.ts b/packages/markdown/src/plugins/image.ts index 6fbdf6aa..6e3ceb33 100644 --- a/packages/markdown/src/plugins/image.ts +++ b/packages/markdown/src/plugins/image.ts @@ -6,7 +6,7 @@ import { ImageElementProps } from '@microsoft/chartifact-schema'; import { IInstance, Plugin } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; import { DynamicUrl } from './url.js'; import { ErrorHandler } from '../renderer.js'; @@ -32,7 +32,7 @@ const pluginName: PluginNames = 'image'; const className = pluginClassName(pluginName); export const imagePlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const imageInstances: ImageInstance[] = []; for (let index = 0; index < specs.length; index++) { diff --git a/packages/markdown/src/plugins/mermaid.ts b/packages/markdown/src/plugins/mermaid.ts index 61b28f59..c8e566e7 100644 --- a/packages/markdown/src/plugins/mermaid.ts +++ b/packages/markdown/src/plugins/mermaid.ts @@ -48,13 +48,14 @@ import { Plugin, RawFlaggableSpec, IInstance } from '../factory.js'; import { ErrorHandler } from '../renderer.js'; import { sanitizedHTML } from '../sanitize.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { pluginClassName } from './util.js'; import { PluginNames } from './interfaces.js'; import { TemplateToken, tokenizeTemplate } from 'common'; import { MermaidConfig } from 'mermaid'; import type Mermaid from 'mermaid'; import { MermaidElementProps, MermaidTemplate } from '@microsoft/chartifact-schema'; +import * as yaml from 'js-yaml'; interface MermaidInstance { id: string; @@ -166,23 +167,34 @@ function loadMermaidFromCDN(): Promise { } export const mermaidPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), fence: (token, index) => { const content = token.content.trim(); let spec: MermaidSpec; let flaggableSpec: RawFlaggableSpec; - // Try to parse as JSON first + // Determine format from token info (like flaggablePlugin does) + const info = token.info.trim(); + const isYaml = info.startsWith('yaml '); + const formatName = isYaml ? 'YAML' : 'JSON'; + + // Try to parse as YAML or JSON based on format try { - const parsed = JSON.parse(content) as MermaidSpec; + let parsed: any; + if (isYaml) { + parsed = yaml.load(content); + } else { + parsed = JSON.parse(content); + } + if (parsed && typeof parsed === 'object') { - spec = parsed; + spec = parsed as MermaidSpec; } else { - // If it's JSON but not a valid MermaidSpec, treat as raw text + // If it's valid YAML/JSON but not a proper MermaidSpec object, treat as raw text spec = { diagramText: content }; } } catch (e) { - // If JSON parsing fails, treat as raw text + // If YAML/JSON parsing fails, treat as raw text spec = { diagramText: content }; } diff --git a/packages/markdown/src/plugins/presets.ts b/packages/markdown/src/plugins/presets.ts index 478f32ed..720be28d 100644 --- a/packages/markdown/src/plugins/presets.ts +++ b/packages/markdown/src/plugins/presets.ts @@ -6,7 +6,7 @@ import { Preset } from '@microsoft/chartifact-schema'; import { Batch, IInstance, Plugin, PrioritizedSignal } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; export type PresetsSpec = Preset[]; @@ -21,7 +21,7 @@ const pluginName: PluginNames = 'presets'; const className = pluginClassName(pluginName); export const presetsPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const presetsInstances: PresetsInstance[] = []; diff --git a/packages/markdown/src/plugins/slider.ts b/packages/markdown/src/plugins/slider.ts index 5497d3ec..b3fa2142 100644 --- a/packages/markdown/src/plugins/slider.ts +++ b/packages/markdown/src/plugins/slider.ts @@ -6,7 +6,7 @@ import { VariableControl, SliderElementProps } from '@microsoft/chartifact-schema'; import { Batch, IInstance, Plugin } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; interface SliderInstance { @@ -23,7 +23,7 @@ const pluginName: PluginNames = 'slider'; const className = pluginClassName(pluginName); export const sliderPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const sliderInstances: SliderInstance[] = []; diff --git a/packages/markdown/src/plugins/tabulator.ts b/packages/markdown/src/plugins/tabulator.ts index 22f96472..c9e09121 100644 --- a/packages/markdown/src/plugins/tabulator.ts +++ b/packages/markdown/src/plugins/tabulator.ts @@ -6,8 +6,8 @@ import { Batch, IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; import { Tabulator as TabulatorType, Options as TabulatorOptions } from 'tabulator-tables'; import { newId, pluginClassName } from './util.js'; -import { TableElementProps } from '@microsoft/chartifact-schema'; -import { flaggableJsonPlugin } from './config.js'; +import { TabulatorElementProps } from '@microsoft/chartifact-schema'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; interface TabulatorInstance { @@ -20,7 +20,7 @@ interface TabulatorInstance { listening: boolean; } -export interface TabulatorSpec extends TableElementProps { +export interface TabulatorSpec extends TabulatorElementProps { tabulatorOptions?: TabulatorOptions; //recast the default with strong typing } @@ -38,7 +38,7 @@ const pluginName: PluginNames = 'tabulator'; const className = pluginClassName(pluginName); export const tabulatorPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className, inspectTabulatorSpec, { style: 'box-sizing: border-box;' }), + ...flaggablePlugin(pluginName, className, inspectTabulatorSpec, { style: 'box-sizing: border-box;' }), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const tabulatorInstances: TabulatorInstance[] = []; @@ -75,8 +75,8 @@ export const tabulatorPlugin: Plugin = { continue; } - if (!spec.dataSourceName || !spec.variableId) { - errorHandler(new Error('Tabulator requires dataSourceName and variableId'), pluginName, index, 'init', container); + if (!spec.dataSourceName) { + errorHandler(new Error('Tabulator requires dataSourceName'), pluginName, index, 'init', container); continue; } else if (spec.dataSourceName === spec.variableId) { errorHandler(new Error('Tabulator dataSourceName and variableId cannot be the same'), pluginName, index, 'init', container); diff --git a/packages/markdown/src/plugins/textbox.ts b/packages/markdown/src/plugins/textbox.ts index 5f02b90b..2202e6d1 100644 --- a/packages/markdown/src/plugins/textbox.ts +++ b/packages/markdown/src/plugins/textbox.ts @@ -6,7 +6,7 @@ import { TextboxElementProps } from '@microsoft/chartifact-schema'; import { Batch, IInstance, Plugin } from '../factory.js'; import { pluginClassName } from './util.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { PluginNames } from './interfaces.js'; interface TextboxInstance { @@ -23,7 +23,7 @@ const pluginName: PluginNames = 'textbox'; const className = pluginClassName(pluginName); export const textboxPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; const textboxInstances: TextboxInstance[] = []; diff --git a/packages/markdown/src/plugins/vega-lite.ts b/packages/markdown/src/plugins/vega-lite.ts index 5761c4c4..7ab1bd90 100644 --- a/packages/markdown/src/plugins/vega-lite.ts +++ b/packages/markdown/src/plugins/vega-lite.ts @@ -5,29 +5,40 @@ import { Plugin, RawFlaggableSpec } from '../factory.js'; import { sanitizedHTML } from '../sanitize.js'; -import { flaggableJsonPlugin } from './config.js'; +import { flaggablePlugin } from './config.js'; import { pluginClassName } from './util.js'; import { inspectVegaSpec, vegaPlugin } from './vega.js'; import { compile, TopLevelSpec } from 'vega-lite'; import { Spec } from 'vega'; import { PluginNames } from './interfaces.js'; +import * as yaml from 'js-yaml'; const pluginName: PluginNames = 'vega-lite'; const className = pluginClassName(pluginName); export const vegaLitePlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className), + ...flaggablePlugin(pluginName, className), fence: (token, index) => { - let json = token.content.trim(); + let content = token.content.trim(); let spec: TopLevelSpec; let flaggableSpec: RawFlaggableSpec; + + // Determine format from token info + const info = token.info.trim(); + const isYaml = info.startsWith('yaml '); + const formatName = isYaml ? 'YAML' : 'JSON'; + try { - spec = JSON.parse(json); + if (isYaml) { + spec = yaml.load(content) as TopLevelSpec; + } else { + spec = JSON.parse(content); + } } catch (e) { flaggableSpec = { spec: null, hasFlags: true, - reasons: [`malformed JSON`], + reasons: [`malformed ${formatName}`], }; } if (spec) { @@ -44,9 +55,9 @@ export const vegaLitePlugin: Plugin = { } } if (flaggableSpec) { - json = JSON.stringify(flaggableSpec); + content = JSON.stringify(flaggableSpec); } - return sanitizedHTML('div', { class: pluginClassName(vegaPlugin.name), id: `${pluginName}-${index}` }, json, true); + return sanitizedHTML('div', { class: pluginClassName(vegaPlugin.name), id: `${pluginName}-${index}` }, content, true); }, hydratesBefore: vegaPlugin.name, }; diff --git a/packages/markdown/src/plugins/vega.ts b/packages/markdown/src/plugins/vega.ts index 5bf7ce02..66458956 100644 --- a/packages/markdown/src/plugins/vega.ts +++ b/packages/markdown/src/plugins/vega.ts @@ -10,7 +10,7 @@ import { ErrorHandler, Renderer } from '../renderer.js'; import { LogLevel, SignalBus } from '../signalbus.js'; import { pluginClassName } from './util.js'; import { defaultCommonOptions } from 'common'; -import { flaggableJsonPlugin, } from './config.js'; +import { flaggablePlugin, } from './config.js'; import { PluginNames } from './interfaces.js'; const ignoredSignals = ['width', 'height', 'padding', 'autosize', 'background', 'style', 'parent', 'datum', 'item', 'event', 'cursor']; @@ -42,7 +42,7 @@ export function inspectVegaSpec(spec: Spec) { } export const vegaPlugin: Plugin = { - ...flaggableJsonPlugin(pluginName, className, inspectVegaSpec), + ...flaggablePlugin(pluginName, className, inspectVegaSpec), hydrateComponent: async (renderer, errorHandler, specs) => { const { signalBus } = renderer; //initialize the expressionFunction only once diff --git a/packages/markdown/test/index.html b/packages/markdown/test/index.html index 86cfcd12..2240abeb 100644 --- a/packages/markdown/test/index.html +++ b/packages/markdown/test/index.html @@ -9,6 +9,7 @@ + diff --git a/packages/markdown/vite.bundle.config.js b/packages/markdown/vite.bundle.config.js index da1ec352..2c541bef 100644 --- a/packages/markdown/vite.bundle.config.js +++ b/packages/markdown/vite.bundle.config.js @@ -11,6 +11,7 @@ const commonOutputConfig = { 'vega': 'vega', 'vega-lite': 'vegaLite', 'css-tree': 'csstree', + 'js-yaml': 'jsyaml', }, entryFileNames: 'chartifact.markdown.umd.js', }; @@ -23,7 +24,7 @@ export default defineConfig({ minify: false, rollupOptions: { // External dependencies that the library expects consumers to provide - external: ['markdown-it', 'vega', 'vega-lite', 'tabulator-tables', 'css-tree', 'mermaid'], + external: ['markdown-it', 'vega', 'vega-lite', 'tabulator-tables', 'css-tree', 'mermaid', 'js-yaml'], output: [ { ...commonOutputConfig, diff --git a/packages/sandbox/dev/index.ts b/packages/sandbox/dev/index.ts index fed7321d..ece32731 100644 --- a/packages/sandbox/dev/index.ts +++ b/packages/sandbox/dev/index.ts @@ -18,6 +18,7 @@ class LocalSandbox extends Sandbox { + diff --git a/packages/sandbox/src/sandbox.ts b/packages/sandbox/src/sandbox.ts index 37015ab7..de6f9df0 100644 --- a/packages/sandbox/src/sandbox.ts +++ b/packages/sandbox/src/sandbox.ts @@ -109,6 +109,7 @@ export class Sandbox { + diff --git a/packages/sandbox/vite.bundle.config.js b/packages/sandbox/vite.bundle.config.js index e5ef5003..f5b86e91 100644 --- a/packages/sandbox/vite.bundle.config.js +++ b/packages/sandbox/vite.bundle.config.js @@ -11,6 +11,7 @@ const commonOutputConfig = { 'vega': 'vega', 'vega-lite': 'vegaLite', 'css-tree': 'csstree', + 'js-yaml': 'jsyaml', }, entryFileNames: 'chartifact.sandbox.umd.js', }; @@ -24,7 +25,7 @@ export default defineConfig({ emptyOutDir: false, rollupOptions: { // External dependencies that the library expects consumers to provide - external: ['vega', 'vega-lite', 'css-tree'], + external: ['vega', 'vega-lite', 'css-tree', 'js-yaml'], output: [ { ...commonOutputConfig, diff --git a/packages/schema-doc/src/common.ts b/packages/schema-doc/src/common.ts index 539ad304..d7796bc9 100644 --- a/packages/schema-doc/src/common.ts +++ b/packages/schema-doc/src/common.ts @@ -60,3 +60,7 @@ export interface VariableControl extends ElementBase { /** optional label if the variableId is not descriptive enough */ label?: string; } + +export interface OptionalVariableControl extends ElementBase { + variableId?: VariableID; +} diff --git a/packages/schema-doc/src/interactive.ts b/packages/schema-doc/src/interactive.ts index 099f0363..847dfcf0 100644 --- a/packages/schema-doc/src/interactive.ts +++ b/packages/schema-doc/src/interactive.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ -import { VariableID, VariableControl, ElementBase, TemplatedUrl } from './common.js'; +import { VariableID, VariableControl, ElementBase, TemplatedUrl, OptionalVariableControl } from './common.js'; /** * Interactive Elements @@ -98,15 +98,14 @@ export interface MermaidTemplate { dataSourceName?: string; } -export interface MermaidElementProps extends ElementBase { +export interface MermaidElementProps extends OptionalVariableControl { /** Static option: the raw Mermaid diagram text */ diagramText?: string; - /** Dynamic option: data-driven template */ + /** Dynamic option 1: data-driven template */ template?: MermaidTemplate; - /** Dynamic option: input from signal bus, or output to signal bus from rendered data-driven template */ - variableId?: string; + /** Dynamic option 2: input as variableId from signal bus, or output to signal bus from rendered data-driven template */ } /** @@ -141,13 +140,13 @@ export interface Preset { } /** - * Table + * Tabulator * use for tabular data */ -export interface TableElement extends TableElementProps { - type: 'table'; +export interface TabulatorElement extends TabulatorElementProps { + type: 'tabulator'; } -export interface TableElementProps extends VariableControl { +export 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; @@ -211,6 +210,6 @@ export type InteractiveElement = | MermaidElement | PresetsElement | SliderElement - | TableElement + | TabulatorElement | TextboxElement ; diff --git a/packages/vscode/scripts/resources.mjs b/packages/vscode/scripts/resources.mjs index 10efe4fd..12f7354d 100644 --- a/packages/vscode/scripts/resources.mjs +++ b/packages/vscode/scripts/resources.mjs @@ -10,6 +10,7 @@ const resources = [ '../../node_modules/vega-lite/build/vega-lite.min.js', '../../node_modules/markdown-it/dist/markdown-it.min.js', '../../node_modules/css-tree/dist/csstree.js', + '../../node_modules/js-yaml/dist/js-yaml.min.js', '../../node_modules/tabulator-tables/dist/js/tabulator.min.js', '../../node_modules/tabulator-tables/dist/css/tabulator.min.css', '../../node_modules/mermaid/dist/mermaid.min.js', diff --git a/packages/vscode/src/web/command-edit.ts b/packages/vscode/src/web/command-edit.ts index 0bdad009..111f0e3b 100644 --- a/packages/vscode/src/web/command-edit.ts +++ b/packages/vscode/src/web/command-edit.ts @@ -71,6 +71,7 @@ export class EditManager { style(getResourceContent('tabulator.min.css')) + script(getResourceContent('markdown-it.min.js')) + script(getResourceContent('csstree.js')) + + script(getResourceContent('js-yaml.min.js')) + script(getResourceContent('vega.min.js')) + script(getResourceContent('vega-lite.min.js')) + script(getResourceContent('tabulator.min.js')) + diff --git a/packages/vscode/src/web/command-preview.ts b/packages/vscode/src/web/command-preview.ts index 0d880e96..9467ba38 100644 --- a/packages/vscode/src/web/command-preview.ts +++ b/packages/vscode/src/web/command-preview.ts @@ -96,6 +96,7 @@ export class PreviewManager { style(getResourceContent('tabulator.min.css')) + script(getResourceContent('markdown-it.min.js')) + script(getResourceContent('csstree.js')) + + script(getResourceContent('js-yaml.min.js')) + script(getResourceContent('vega.min.js')) + script(getResourceContent('vega-lite.min.js')) + script(getResourceContent('tabulator.min.js')) + diff --git a/packages/vscode/src/web/resources.ts b/packages/vscode/src/web/resources.ts index d7775e80..5bd20254 100644 --- a/packages/vscode/src/web/resources.ts +++ b/packages/vscode/src/web/resources.ts @@ -21,6 +21,7 @@ export const initializeResources = async (context: vscode.ExtensionContext): Pro 'tabulator.min.css', 'markdown-it.min.js', 'csstree.js', + 'js-yaml.min.js', 'vega.min.js', 'vega-lite.min.js', 'tabulator.min.js',