...
This commit is contained in:
@@ -11,9 +11,9 @@ buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
|
||||
mut model := bizmodel.generate('test', playbook_path)!
|
||||
|
||||
println(model.sheet)
|
||||
println(model.sheet.export()!)
|
||||
// println(model.sheet.export()!)
|
||||
|
||||
model.sheet.export(path: '~/Downloads/test.csv')!
|
||||
model.sheet.pprint()!
|
||||
// model.sheet.export(path: '~/code/github/freeflowuniverse/starlight_template/src/content/test.csv')!
|
||||
// model.sheet
|
||||
|
||||
model.sheet
|
||||
|
||||
28
examples/biztools/bizmodel1.vsh
Executable file
28
examples/biztools/bizmodel1.vsh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import os
|
||||
|
||||
heroscript:="
|
||||
|
||||
Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percent which is 20% at start but goes to 10% after 20 months.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem1'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_percent: '1:20%,20:10%'
|
||||
|
||||
|
||||
This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem2'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs: '10:100000,15:1000,20:120000'
|
||||
"
|
||||
|
||||
bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint()!
|
||||
@@ -1 +0,0 @@
|
||||
output dir of example
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "${script_dir}"
|
||||
|
||||
echo "Docs directory: $script_dir"
|
||||
|
||||
cd "${HOME}/hero/var/docusaurus"
|
||||
|
||||
export PATH=/tmp/docusaurus_build/node_modules/.bin:${HOME}/.bun/bin/:$PATH
|
||||
|
||||
rm -rf /Users/despiegk/hero/var/docusaurus/build/
|
||||
|
||||
. ${HOME}/.zprofile
|
||||
|
||||
bun docusaurus build
|
||||
|
||||
mkdir -p /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus
|
||||
echo SYNC TO /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus
|
||||
rsync -rv --delete /Users/despiegk/hero/var/docusaurus/build/ /Users/despiegk/code/github/freeflowuniverse/herolib/examples/biztools/bizmodel/example/docusaurus/
|
||||
@@ -1 +0,0 @@
|
||||
{"style":"dark","links":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"name":"","title":"Docusaurus","tagline":"","favicon":"img/favicon.png","url":"http://localhost","url_home":"docs/introduction","baseUrl":"/","image":"img/tf_graph.png","metadata":{"description":"Docusaurus","image":"Docusaurus","title":"Docusaurus"},"buildDest":[],"buildDestDev":[]}
|
||||
@@ -1 +0,0 @@
|
||||
{"title":"Business Model","items":[{"href":"https://threefold.info/kristof/","label":"ThreeFold Technology","position":"right"},{"href":"https://threefold.io","label":"Operational Plan","position":"left"}]}
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "${script_dir}"
|
||||
|
||||
echo "Docs directory: $script_dir"
|
||||
|
||||
cd "${HOME}/hero/var/docusaurus"
|
||||
|
||||
export PATH=/tmp/docusaurus_build/node_modules/.bin:${HOME}/.bun/bin/:$PATH
|
||||
|
||||
. ${HOME}/.zprofile
|
||||
|
||||
bun run start -p 3100
|
||||
28
examples/biztools/test_revenue_model.v
Normal file
28
examples/biztools/test_revenue_model.v
Normal file
@@ -0,0 +1,28 @@
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import os
|
||||
|
||||
fn main() ! {
|
||||
heroscript:="
|
||||
|
||||
Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percent which is 20% at start but goes to 10% after 20 months.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem1'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_percent: '1:20%,20:10%'
|
||||
|
||||
|
||||
This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned.
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test' name:'oem2'
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs: '10:100000,15:1000,20:120000'
|
||||
"
|
||||
|
||||
bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint()!
|
||||
}
|
||||
@@ -50,7 +50,10 @@ follow rows in sheets
|
||||
|
||||
```v
|
||||
|
||||
heroscript:='
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import os
|
||||
|
||||
heroscript:="
|
||||
|
||||
Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percent which is 20% at start but goes to 10% after 20 months.
|
||||
|
||||
@@ -58,7 +61,7 @@ Next will define an OEM product in month 10, 1 Million EUR, ... cogs is a percen
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_percent: '1:20%,20:10%'
|
||||
'
|
||||
|
||||
|
||||
This time we have the cogs defined in fixed manner, the default currency is USD doesn't have to be mentioned.
|
||||
|
||||
@@ -66,8 +69,12 @@ This time we have the cogs defined in fixed manner, the default currency is USD
|
||||
descr:'OEM Deals'
|
||||
revenue:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs: '10:100000,15:1000,20:120000'
|
||||
'
|
||||
"
|
||||
|
||||
bizmodel.play(heroscript:heroscript)!
|
||||
|
||||
mut bm:=bizmodel.get("test")!
|
||||
|
||||
bm.sheet.pprint()!
|
||||
|
||||
```
|
||||
@@ -1,12 +1,8 @@
|
||||
module bizmodel
|
||||
|
||||
import arrays
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.playbook { Action, PlayBook }
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook, Action }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.texttools
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
const action_priorities = {
|
||||
0: ['revenue_define', 'costcenter_define', 'funding_define']
|
||||
@@ -14,7 +10,20 @@ const action_priorities = {
|
||||
2: ['sheet_wiki', 'graph_bar_row', 'graph_pie_row', 'graph_line_row', 'row_overview']
|
||||
}
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
heroscript string
|
||||
heroscript_path string
|
||||
plbook ?PlayBook
|
||||
reset bool
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut args := args_
|
||||
mut plbook := args.plbook or {
|
||||
playbook.new(text: args.heroscript, path: args.heroscript_path)!
|
||||
}
|
||||
// group actions by which bizmodel they belong to
|
||||
actions_by_biz := arrays.group_by[string, &Action](plbook.actions_find(actor: 'bizmodel')!,
|
||||
fn (a &Action) string {
|
||||
|
||||
94
lib/biz/spreadsheet/docu/charting_capabilities.md
Normal file
94
lib/biz/spreadsheet/docu/charting_capabilities.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Charting Capabilities
|
||||
|
||||
The spreadsheet module integrates with ECharts to provide powerful data visualization capabilities, allowing you to generate various types of charts directly from your sheet data.
|
||||
|
||||
## Common Charting Parameters (`RowGetArgs`)
|
||||
|
||||
Most charting functions utilize a common set of arguments defined in `RowGetArgs` to specify which data to chart and how to present it.
|
||||
|
||||
**Key `RowGetArgs` Parameters:**
|
||||
- `rowname` (string, optional): The exact name of a single row to chart.
|
||||
- `namefilter` ([]string, optional): A list of exact row names to include.
|
||||
- `includefilter` ([]string, optional): A list of tags to include. Rows must match at least one of these tags.
|
||||
- `excludefilter` ([]string, optional): A list of tags to exclude.
|
||||
- `period_type` (`PeriodType`, optional): Specifies the period type for the chart's X-axis (e.g., `.month`, `.quarter`, `.year`).
|
||||
- `aggregate` (bool, optional, default: `true`): If `true` and multiple rows match the filters, their values will be aggregated.
|
||||
- `aggregatetype` (`RowAggregateType`, optional, default: `.sum`): The type of aggregation to perform if `aggregate` is `true`.
|
||||
- `unit` (`UnitType`, optional): The unit of the data (e.g., currency, percentage).
|
||||
- `title` (string, optional): The main title of the chart.
|
||||
- `title_sub` (string, optional): A subtitle for the chart.
|
||||
- `size` (string, optional): For pie charts, this can define the radius (e.g., "50%").
|
||||
- `rowname_show` (bool, optional, default: `true`): Whether to show the row name in the chart legend/labels.
|
||||
- `descr_show` (bool, optional, default: `false`): Whether to show the row description. If `true`, `rowname_show` will be set to `false`.
|
||||
- `description` (string, optional): A general description for the chart.
|
||||
|
||||
## Chart Types
|
||||
|
||||
### Line Chart (`line_chart`)
|
||||
|
||||
Generates a line chart, ideal for visualizing trends over time. Multiple rows can be plotted on the same chart.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import freeflowuniverse.herolib.web.echarts
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
// ... populate my_sheet with data
|
||||
|
||||
// Generate a line chart for 'revenue_row' and 'expenses_row' over months
|
||||
line_chart_option := my_sheet.line_chart(
|
||||
rowname: 'revenue_row,expenses_row', // Comma-separated row names
|
||||
period_type: .month,
|
||||
title: 'Revenue vs. Expenses Over Time',
|
||||
title_sub: 'Monthly Data'
|
||||
)!
|
||||
|
||||
// The 'line_chart_option' can then be used to render the chart using ECharts.
|
||||
```
|
||||
|
||||
### Bar Chart (`bar_chart`)
|
||||
|
||||
Generates a bar chart, suitable for comparing discrete categories or values. Typically used for a single row's data.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import freeflowuniverse.herolib.web.echarts
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
// ... populate my_sheet with data
|
||||
|
||||
// Generate a bar chart for 'profit_row' aggregated by quarter
|
||||
bar_chart_option := my_sheet.bar_chart(
|
||||
rowname: 'profit_row',
|
||||
period_type: .quarter,
|
||||
title: 'Quarterly Profit',
|
||||
title_sub: 'Aggregated Data'
|
||||
)!
|
||||
```
|
||||
|
||||
### Pie Chart (`pie_chart`)
|
||||
|
||||
Generates a pie chart, useful for showing the proportion of different categories within a single data set. Typically used for a single row's data.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import freeflowuniverse.herolib.web.echarts
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
// ... populate my_sheet with data
|
||||
|
||||
// Generate a pie chart for 'budget_allocation_row' showing yearly distribution
|
||||
pie_chart_option := my_sheet.pie_chart(
|
||||
rowname: 'budget_allocation_row',
|
||||
period_type: .year,
|
||||
title: 'Annual Budget Allocation',
|
||||
size: '70%' // Set the radius of the pie chart
|
||||
)!
|
||||
```
|
||||
|
||||
## Integration with ECharts
|
||||
|
||||
The charting functions return an `echarts.EChartsOption` object, which is a JSON-serializable structure compatible with the ECharts JavaScript library. This allows for flexible rendering of these charts in web interfaces or other environments that support ECharts.
|
||||
93
lib/biz/spreadsheet/docu/data_aggregation_transformation.md
Normal file
93
lib/biz/spreadsheet/docu/data_aggregation_transformation.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Data Aggregation and Transformation
|
||||
|
||||
The spreadsheet module provides powerful functionalities to aggregate and transform data within sheets, allowing for different views and summarized insights.
|
||||
|
||||
## Grouping Rows into a New Row
|
||||
|
||||
The `group2row` method allows you to select rows based on their tags and aggregate their values into a new, single row. This is particularly useful for creating summary rows (e.g., total salaries for a specific department).
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object with various rows
|
||||
// and some rows have tags like 'department:dev' or 'department:engineering'.
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
// ... add rows to my_sheet with relevant tags and data
|
||||
|
||||
// Aggregate all rows tagged with 'department:dev' or 'department:engineering'
|
||||
// into a new row named 'total_dev_engineering_salaries'.
|
||||
// The aggregation type defaults to 'sum'.
|
||||
mut total_salaries_row := my_sheet.group2row(
|
||||
name: 'total_dev_engineering_salaries',
|
||||
include: ['department:dev', 'department:engineering'],
|
||||
tags: 'summary:dev_eng', // Optional: tags for the new aggregated row
|
||||
descr: 'Total salaries for Development and Engineering departments'
|
||||
)!
|
||||
|
||||
// You can also specify an aggregation type other than sum, e.g., .avg, .max, .min
|
||||
// mut avg_salaries_row := my_sheet.group2row(
|
||||
// name: 'average_salaries',
|
||||
// include: ['department:dev'],
|
||||
// aggregatetype: .avg
|
||||
// )!
|
||||
```
|
||||
|
||||
**`Group2RowArgs` Parameters:**
|
||||
- `name` (string, required): The name of the new aggregated row.
|
||||
- `include` ([]string, optional): A list of tags to include. Rows must match at least one of these tags to be included in the aggregation. Supports wildcard matching (e.g., `location:belgium_*`).
|
||||
- `exclude` ([]string, optional): A list of tags to exclude. Rows matching any of these tags will be excluded from the aggregation.
|
||||
- `tags` (string, optional): Tags to assign to the newly created aggregated row.
|
||||
- `descr` (string, optional): Description for the new aggregated row.
|
||||
- `subgroup` (string, optional): Subgroup for the new aggregated row.
|
||||
- `aggregatetype` (`RowAggregateType`, optional, default: `.sum`): The type of aggregation to perform.
|
||||
- `.sum`: Sums the values of matching cells.
|
||||
- `.avg`: Calculates the average of matching cells.
|
||||
- `.max`: Finds the maximum value among matching cells.
|
||||
- `.min`: Finds the minimum value among matching cells.
|
||||
|
||||
## Transforming Sheet Periodicity (Yearly/Quarterly Aggregation)
|
||||
|
||||
The module allows you to create new sheets where the data is aggregated into larger time periods, such as years or quarters, from a monthly-based sheet.
|
||||
|
||||
### Aggregating to Yearly Data
|
||||
|
||||
The `toyear` method creates a new sheet where monthly data is aggregated into yearly columns.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
// Assuming 'monthly_sheet' is a sheet with 60 columns (5 years of monthly data)
|
||||
mut monthly_sheet := spreadsheet.sheet_new(name: 'monthly_data', nrcol: 60)!
|
||||
// ... populate monthly_sheet with data
|
||||
|
||||
// Create a new sheet 'yearly_data' with data aggregated by year
|
||||
mut yearly_sheet := monthly_sheet.toyear(
|
||||
name: 'yearly_data',
|
||||
namefilter: ['revenue_row', 'expenses_row'], // Optional: only include specific rows
|
||||
includefilter: ['category:income'], // Optional: filter rows by tags
|
||||
excludefilter: ['status:draft'] // Optional: exclude rows by tags
|
||||
)!
|
||||
```
|
||||
|
||||
### Aggregating to Quarterly Data
|
||||
|
||||
Similarly, the `toquarter` method creates a new sheet with data aggregated into quarterly columns.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
// Assuming 'monthly_sheet' is a sheet with 60 columns
|
||||
mut monthly_sheet := spreadsheet.sheet_new(name: 'monthly_data', nrcol: 60)!
|
||||
// ... populate monthly_sheet with data
|
||||
|
||||
// Create a new sheet 'quarterly_data' with data aggregated by quarter
|
||||
mut quarterly_sheet := monthly_sheet.toquarter(
|
||||
name: 'quarterly_data'
|
||||
)!
|
||||
```
|
||||
|
||||
**`ToYearQuarterArgs` Parameters:**
|
||||
- `name` (string, optional): The name of the new aggregated sheet. If empty, a default name based on the original sheet and period (e.g., `original_sheet_name_year`) is used.
|
||||
- `namefilter` ([]string, optional): A list of exact row names to include in the new sheet. If provided, only these rows will be processed.
|
||||
- `includefilter` ([]string, optional): A list of tags to include. Rows must match at least one of these tags to be included.
|
||||
- `excludefilter` ([]string, optional): A list of tags to exclude. Rows matching any of these tags will be excluded.
|
||||
44
lib/biz/spreadsheet/docu/exporting_data.md
Normal file
44
lib/biz/spreadsheet/docu/exporting_data.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Exporting Data
|
||||
|
||||
The spreadsheet module provides functionality to export sheet data into various formats, making it easy to integrate with other tools or for reporting purposes.
|
||||
|
||||
## Exporting to CSV
|
||||
|
||||
The `export_csv` method allows you to export the sheet's data to a CSV (Comma Separated Values) file or directly as a string. It offers flexibility in terms of separators and handling of empty cells.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import os
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
// ... populate my_sheet with data
|
||||
|
||||
// Export to a CSV file with the default pipe '|' separator
|
||||
// The file will be created at '~/output.csv'
|
||||
my_sheet.export_csv(path: '~/output.csv')!
|
||||
|
||||
// Export to a CSV file with a custom comma ',' separator and include empty cells
|
||||
csv_content_with_empty := my_sheet.export_csv(
|
||||
path: '~/output_with_empty.csv',
|
||||
separator: ',',
|
||||
include_empty: true
|
||||
)!
|
||||
|
||||
// Export to a string only (no file will be created)
|
||||
csv_string := my_sheet.export_csv(path: '')!
|
||||
println(csv_string)
|
||||
```
|
||||
|
||||
**`ExportCSVArgs` Parameters:**
|
||||
- `path` (string, optional): The file path where the CSV should be saved. If an empty string is provided, the CSV content will be returned as a string instead of being written to a file. The `~` character is expanded to the user's home directory.
|
||||
- `include_empty` (bool, optional, default: `false`): If `true`, empty cells will be included in the CSV output (represented as '0' for numeric values, or empty string for others). If `false`, empty cells will be represented as an empty string.
|
||||
- `separator` (string, optional, default: `'|'`): The character used to separate values in the CSV. Common separators include ',' (comma), ';' (semicolon), or '|' (pipe).
|
||||
|
||||
**CSV Export Features:**
|
||||
- **Configurable Separator:** Easily change the delimiter to suit different CSV parsing requirements.
|
||||
- **Special Character Handling:** Values containing the separator, double quotes, or newlines are automatically enclosed in double quotes, and internal double quotes are escaped (e.g., `"` becomes `""`).
|
||||
- **Empty Cell Inclusion:** Control whether empty cells are explicitly represented in the output.
|
||||
- **Numeric Formatting:** Numeric values are formatted appropriately, with small numbers showing up to 3 decimal places and larger numbers as integers. Trailing zeros and decimal points are removed if unnecessary.
|
||||
|
||||
The CSV output includes a header row with "Name", "Description", "AggregateType", "Tags", "Subgroup", followed by the sheet's column headers (e.g., "M1", "M2", "Y1", etc.). Each subsequent row represents a sheet row, with its metadata followed by its cell values.
|
||||
51
lib/biz/spreadsheet/docu/overview.md
Normal file
51
lib/biz/spreadsheet/docu/overview.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Spreadsheet Module Overview
|
||||
|
||||
The `spreadsheet` module in `herolib` provides a software representation of a spreadsheet, designed for business modeling and data analysis. It supports multi-currency behavior, powerful extrapolation/interpolation, and various data manipulation and visualization capabilities.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Sheet
|
||||
A `Sheet` is the primary container, representing the entire spreadsheet. It holds a collection of `Row` objects and defines global properties such as the number of columns (`nrcol`), visualization parameters (`SheetParams`), and the associated currency.
|
||||
|
||||
**Key Properties:**
|
||||
- `name`: A unique identifier for the sheet.
|
||||
- `rows`: A map of `Row` objects, indexed by their names.
|
||||
- `nrcol`: The number of columns in the sheet, typically representing periods (e.g., 60 months for 5 years).
|
||||
- `params`: Configuration parameters for the sheet, such as `visualize_cur` (whether to display currency symbols).
|
||||
- `currency`: The default currency for the sheet, used for currency exchange operations.
|
||||
|
||||
### Row
|
||||
A `Row` represents a single horizontal line of data within a `Sheet`. Each row has a name, an optional alias, a description, and can be associated with tags for grouping and filtering. It contains a series of `Cell` objects, each holding a value for a specific column.
|
||||
|
||||
**Key Properties:**
|
||||
- `name`: A unique identifier for the row within the sheet.
|
||||
- `alias`: An optional alternative name for the row.
|
||||
- `description`: A textual description of the row's purpose.
|
||||
- `tags`: A string of space-separated tags used for categorization and filtering (e.g., "location:belgium_*, department:finance").
|
||||
- `cells`: A list of `Cell` objects, representing the data points across columns.
|
||||
- `aggregatetype`: Defines how values in this row should be aggregated (e.g., sum, average, min, max).
|
||||
|
||||
### Cell
|
||||
A `Cell` is the fundamental unit of data in the spreadsheet, residing at the intersection of a row and a column. It stores a floating-point value (`f64`) and a flag indicating if it's empty.
|
||||
|
||||
**Key Properties:**
|
||||
- `val`: The numeric value stored in the cell.
|
||||
- `empty`: A boolean flag, `true` if the cell is empty, `false` otherwise.
|
||||
|
||||
**Key Operations:**
|
||||
- `set(v string)`: Sets the cell's value. This method intelligently handles currency inputs, converting them to the sheet's base currency if necessary.
|
||||
- `add(v f64)`: Adds a numeric value to the existing cell value.
|
||||
- `repr()`: Returns a string representation of the cell's value, displaying '-' for empty cells and formatting numbers appropriately.
|
||||
|
||||
## Multi-Currency Support
|
||||
|
||||
The spreadsheet module inherently supports multi-currency operations. When a value is set in a cell using a currency string (e.g., "100 EUR"), the system automatically converts it to the sheet's defined currency based on exchange rates.
|
||||
|
||||
## Period Representation
|
||||
|
||||
Sheets can represent data over various periods:
|
||||
- **Months:** Typically 60 columns for a 5-year period.
|
||||
- **Years:** Data can be aggregated into yearly columns.
|
||||
- **Quarters:** Data can be aggregated into quarterly columns.
|
||||
|
||||
This flexibility allows for different levels of granularity in financial and business modeling.
|
||||
66
lib/biz/spreadsheet/docu/row_cell_operations.md
Normal file
66
lib/biz/spreadsheet/docu/row_cell_operations.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Row and Cell Operations
|
||||
|
||||
Rows and Cells are fundamental building blocks of the spreadsheet, allowing for granular data management and manipulation.
|
||||
|
||||
## Row Operations
|
||||
|
||||
### Creating a New Row
|
||||
|
||||
Rows are created within the context of a `Sheet` object.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
mut my_sheet := spreadsheet.sheet_new(name: 'my_sheet', nrcol: 60)!
|
||||
|
||||
// Create a new row named 'salaries' with specific tags and description
|
||||
mut salaries_row := my_sheet.row_new(
|
||||
name: 'salaries',
|
||||
tags: 'department:hr location:belgium',
|
||||
descr: 'Monthly salaries for HR department in Belgium',
|
||||
aggregatetype: .sum // Optional: define default aggregation for this row
|
||||
)!
|
||||
```
|
||||
|
||||
### Setting Cell Values in a Row
|
||||
|
||||
Once a row is created, you can set values for its individual cells. The `set` method of a `Cell` handles currency conversion automatically if a currency string is provided.
|
||||
|
||||
```v
|
||||
// Set the value of the first cell (column 0) in 'salaries_row'
|
||||
// The value "1000 USD" will be converted to the sheet's currency if different.
|
||||
salaries_row.cells[0].set('1000 USD')!
|
||||
|
||||
// Set a plain numeric value for the second cell (column 1)
|
||||
salaries_row.cells[1].set('1200.50')!
|
||||
```
|
||||
|
||||
### Adding Values to Cells
|
||||
|
||||
You can add a numeric value to an existing cell's value.
|
||||
|
||||
```v
|
||||
// Add 500 to the value of the first cell
|
||||
salaries_row.cells[0].add(500.0)
|
||||
```
|
||||
|
||||
### Retrieving Row Values
|
||||
|
||||
You can get all the values of a row as a list of `f64`.
|
||||
|
||||
```v
|
||||
// Get all values from 'salaries_row'
|
||||
values := salaries_row.values_get()
|
||||
// values will be a []f64
|
||||
```
|
||||
|
||||
### String Representation of a Cell
|
||||
|
||||
The `repr()` and `str()` methods of a `Cell` provide a formatted string representation of its value.
|
||||
|
||||
```v
|
||||
// Assuming 'my_cell' is a Cell object
|
||||
cell_string := my_cell.repr()
|
||||
// If the cell is empty, cell_string will be "-"
|
||||
// Otherwise, it will be the formatted numeric value.
|
||||
88
lib/biz/spreadsheet/docu/sheet_operations.md
Normal file
88
lib/biz/spreadsheet/docu/sheet_operations.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Sheet Operations
|
||||
|
||||
The `Sheet` object is the central component of the spreadsheet module, providing methods to manage and manipulate the overall spreadsheet data.
|
||||
|
||||
## Creating a New Sheet
|
||||
|
||||
A new sheet can be initialized using the `sheet_new` function.
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
// Create a new sheet named 'my_financial_sheet' with 60 columns
|
||||
mut my_sheet := spreadsheet.sheet_new(
|
||||
name: 'my_financial_sheet',
|
||||
nrcol: 60,
|
||||
visualize_cur: true, // Optional: display currency symbols in cells
|
||||
curr: 'USD' // Optional: set the default currency for the sheet
|
||||
)!
|
||||
```
|
||||
|
||||
## Retrieving Rows and Cells
|
||||
|
||||
You can access specific rows and cells within a sheet using their names and column numbers.
|
||||
|
||||
### Get a Row by Name
|
||||
|
||||
```v
|
||||
// Assuming 'my_sheet' is an existing Sheet object
|
||||
// and 'revenue_row' is the name of a row within it.
|
||||
mut revenue_row := my_sheet.row_get('revenue_row')!
|
||||
// Now you can work with the 'revenue_row' object
|
||||
```
|
||||
|
||||
### Get a Cell by Row Name and Column Number
|
||||
|
||||
```v
|
||||
// Get the cell at column 5 of 'revenue_row'
|
||||
mut cell_data := my_sheet.cell_get('revenue_row', 5)!
|
||||
// Access the value: cell_data.val
|
||||
```
|
||||
|
||||
## Deleting Rows
|
||||
|
||||
Rows can be removed from a sheet using their name.
|
||||
|
||||
```v
|
||||
// Delete the row named 'expense_row'
|
||||
my_sheet.row_delete('expense_row')
|
||||
// Alternatively, using the 'delete' alias
|
||||
my_sheet.delete('expense_row')
|
||||
```
|
||||
|
||||
## Calculating Widths for Display
|
||||
|
||||
The sheet provides utility functions to determine the maximum string length of cell values, row names, and row descriptions, which can be useful for formatting output.
|
||||
|
||||
### Maximum Cell Width for a Column
|
||||
|
||||
```v
|
||||
// Get the maximum width of cells in the first column (column index 0)
|
||||
max_width_col0 := my_sheet.cells_width(0)!
|
||||
```
|
||||
|
||||
### Maximum Row Name/Alias Width
|
||||
|
||||
```v
|
||||
// Get the maximum width among all row names and aliases in the sheet
|
||||
max_row_name_width := my_sheet.rows_names_width_max()
|
||||
```
|
||||
|
||||
### Maximum Row Description Width
|
||||
|
||||
```v
|
||||
// Get the maximum width among all row descriptions in the sheet
|
||||
max_row_descr_width := my_sheet.rows_description_width_max()
|
||||
```
|
||||
|
||||
## Generating Headers
|
||||
|
||||
The `header()` function generates a list of strings representing the column headers based on the number of columns (`nrcol`). It automatically determines if the sheet represents months, quarters, or years.
|
||||
|
||||
```v
|
||||
// For a sheet with 60 columns (months)
|
||||
// header_labels will be ["M1", "M2", ..., "M60"]
|
||||
header_labels := my_sheet.header()!
|
||||
|
||||
// For a sheet with 5 columns (years)
|
||||
// header_labels will be ["Y1", "Y2", ..., "Y5"]
|
||||
@@ -9,6 +9,9 @@ pub enum ReprType {
|
||||
|
||||
// represent a
|
||||
pub fn float_repr(nr_ f64, reprtype ReprType) string {
|
||||
if nr_ == 0.0 {
|
||||
return ''
|
||||
}
|
||||
mut out := ''
|
||||
mut nr := nr_
|
||||
mut nr_pos := math.abs(nr)
|
||||
|
||||
137
lib/biz/spreadsheet/sheet_pprint.v
Normal file
137
lib/biz/spreadsheet/sheet_pprint.v
Normal file
@@ -0,0 +1,137 @@
|
||||
module spreadsheet
|
||||
|
||||
import os
|
||||
import math
|
||||
import strconv
|
||||
|
||||
// pad_right pads a string on the right with spaces to a specified length
|
||||
fn pad_right(s string, length int) string {
|
||||
if s.len >= length {
|
||||
return s
|
||||
}
|
||||
mut res := s
|
||||
for _ in 0 .. length - s.len {
|
||||
res += ' '
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PPrintArgs {
|
||||
pub mut:
|
||||
group_months int = 1 //e.g. if 2 then will group by 2 months
|
||||
nr_columns int = 0 //number of columns to show in the table, 0 is all
|
||||
description bool //show description in the table
|
||||
aggrtype bool = true //show aggregate type in the table
|
||||
tags bool //show tags in the table
|
||||
subgroup bool //show subgroup in the table
|
||||
}
|
||||
// calculate_column_widths calculates the maximum width for each column
|
||||
fn calculate_column_widths(rows [][]string) []int {
|
||||
if rows.len == 0 {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
mut widths := []int{len: rows[0].len, init: 0}
|
||||
for _, row in rows {
|
||||
for i, cell in row {
|
||||
if cell.len > widths[i] {
|
||||
widths[i] = cell.len
|
||||
}
|
||||
}
|
||||
}
|
||||
return widths
|
||||
}
|
||||
|
||||
// format_row formats a single row with padding based on column widths
|
||||
fn format_row(row []string, widths []int) string {
|
||||
mut formatted_cells := []string{}
|
||||
for i, cell in row {
|
||||
formatted_cells << pad_right(cell, widths[i])
|
||||
}
|
||||
return formatted_cells.join(' ')
|
||||
}
|
||||
|
||||
pub fn (mut s Sheet) pprint(args PPrintArgs) ! {
|
||||
mut all_rows := [][]string{}
|
||||
|
||||
// Prepare header row
|
||||
mut header_row := ['Name']
|
||||
if args.description {
|
||||
header_row << 'Description'
|
||||
}
|
||||
if args.aggrtype {
|
||||
header_row << 'AggregateType'
|
||||
}
|
||||
if args.tags {
|
||||
header_row << 'Tags'
|
||||
}
|
||||
if args.subgroup {
|
||||
header_row << 'Subgroup'
|
||||
}
|
||||
|
||||
header_row << s.header()!
|
||||
all_rows << header_row
|
||||
|
||||
// Prepare data rows
|
||||
for _, row in s.rows {
|
||||
mut row_data := []string{} // Initialize row_data for each row
|
||||
row_data << row.name // Add the name of the row
|
||||
if args.description {
|
||||
row_data << row.description
|
||||
}
|
||||
if args.aggrtype {
|
||||
row_data << row.aggregatetype.str()
|
||||
}
|
||||
if args.tags {
|
||||
row_data << row.tags
|
||||
}
|
||||
if args.subgroup {
|
||||
row_data << row.subgroup
|
||||
}
|
||||
|
||||
for cell in row.cells {
|
||||
if cell.empty {
|
||||
row_data << '-'
|
||||
} else {
|
||||
row_data << float_repr(cell.val, row.reprtype)
|
||||
}
|
||||
}
|
||||
mut is_empty_row := true
|
||||
mut data_start_index := 1 // for row.name
|
||||
if args.description {
|
||||
data_start_index++
|
||||
}
|
||||
if args.aggrtype {
|
||||
data_start_index++
|
||||
}
|
||||
if args.tags {
|
||||
data_start_index++
|
||||
}
|
||||
if args.subgroup {
|
||||
data_start_index++
|
||||
}
|
||||
|
||||
for i := data_start_index; i < row_data.len; i++ {
|
||||
cell_val := row_data[i]
|
||||
if cell_val.trim_space() != '' && cell_val.trim_space() != '-' {
|
||||
is_empty_row = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !is_empty_row {
|
||||
all_rows << row_data
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate column widths
|
||||
widths := calculate_column_widths(all_rows)
|
||||
|
||||
// Format all rows
|
||||
mut formatted_result := []string{}
|
||||
for _, row in all_rows {
|
||||
formatted_result << '| ' + format_row(row, widths) + ' |'
|
||||
}
|
||||
|
||||
println(formatted_result.join('\n'))
|
||||
}
|
||||
Reference in New Issue
Block a user