diff --git a/lib/biz/spreadsheet/playmacro.v b/lib/biz/spreadsheet/playmacro.v index cf349844..f94eefc0 100644 --- a/lib/biz/spreadsheet/playmacro.v +++ b/lib/biz/spreadsheet/playmacro.v @@ -92,7 +92,7 @@ pub fn playmacro(action Action) !string { content = sh.wiki(args) or { panic(err) } } 'graph_title_row' { - content = sh.wiki_title_chart(args) + content = sh.wiki_title_chart(args)! } 'graph_line_row' { content = sh.wiki_line_chart(args)! diff --git a/lib/biz/spreadsheet/sheet.v b/lib/biz/spreadsheet/sheet.v index 8ae81695..724e3320 100644 --- a/lib/biz/spreadsheet/sheet.v +++ b/lib/biz/spreadsheet/sheet.v @@ -260,7 +260,9 @@ pub fn (mut s Sheet) json() string { // find row, report error if not found pub fn (s Sheet) row_get(name string) !&Row { - row := s.rows[name] or { return error('could not find row with name: ${name}, available rows: ${s.rows.keys()}') } + row := s.rows[name] or { + return error('could not find row with name: ${name}, available rows: ${s.rows.keys()}') + } return row } diff --git a/lib/biz/spreadsheet/wiki.v b/lib/biz/spreadsheet/wiki.v index bc119778..114be7ed 100644 --- a/lib/biz/spreadsheet/wiki.v +++ b/lib/biz/spreadsheet/wiki.v @@ -4,7 +4,7 @@ import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.ui.console // format a sheet properly in wiki format -pub fn (mut s Sheet) wiki(args_ RowGetArgs) !string { +pub fn (s Sheet) wiki(args_ RowGetArgs) !string { mut args := args_ _ := match args.period_type { diff --git a/lib/biz/spreadsheet/wiki_charts.v b/lib/biz/spreadsheet/wiki_charts.v index b064e9c0..389587cc 100644 --- a/lib/biz/spreadsheet/wiki_charts.v +++ b/lib/biz/spreadsheet/wiki_charts.v @@ -3,22 +3,12 @@ module spreadsheet import freeflowuniverse.herolib.data.markdownparser.elements import freeflowuniverse.herolib.ui.console -pub fn (mut s Sheet) wiki_title_chart(args RowGetArgs) string { - if args.title.len > 0 { - titletxt := " - title: { - text: '${args.title}', - subtext: '${args.title_sub}', - left: 'center' - }, - " - return titletxt - } - return '' +pub fn (s Sheet) wiki_title_chart(args RowGetArgs) !string { + return s.title_chart(args).markdown() } -pub fn (mut s_ Sheet) wiki_row_overview(args RowGetArgs) !string { - mut s := s_.filter(args)! +pub fn (s_ Sheet) wiki_row_overview(args RowGetArgs) !string { + s := s_.filter(args)! rows_values := s.rows.values().map([it.name, it.description, it.tags]) mut rows := []elements.Row{} @@ -43,146 +33,18 @@ pub fn (mut s_ Sheet) wiki_row_overview(args RowGetArgs) !string { // produce a nice looking bar chart see // https://echarts.apache.org/examples/en/editor.html?c=line-stack -pub fn (mut s Sheet) wiki_line_chart(args_ RowGetArgs) !string { - mut args := args_ - - rownames := s.rownames_get(args)! - header := s.header_get_as_string(args.period_type)! - mut series_lines := []string{} - - for rowname in rownames { - data := s.data_get_as_string(RowGetArgs{ - ...args - rowname: rowname - })! - series_lines << '{ - name: \'${rowname}\', - type: \'line\', - stack: \'Total\', - data: [${data}] - }' - } - - // TODO: need to implement the multiple results which can come back from the args, can be more than 1 - - // header := s.header_get_as_string(args.period_type)! - // data := s.data_get_as_string(args)! - // console.print_debug('HERE! ${header}') - // console.print_debug('HERE!! ${data}') - - template := " - ${s.wiki_title_chart(args)} - tooltip: { - trigger: 'axis' - }, - legend: { - data: ${rownames} - }, - grid: { - left: '3%', - right: '4%', - bottom: '3%', - containLabel: true - }, - toolbox: { - feature: { - saveAsImage: {} - } - }, - xAxis: { - type: 'category', - boundaryGap: false, - data: [${header}] - }, - yAxis: { - type: 'value' - }, - series: [${series_lines.join(',')}] - " - out := remove_empty_line('```echarts\n{${template}\n};\n```\n') - return out +pub fn (s Sheet) wiki_line_chart(args_ RowGetArgs) !string { + return s.line_chart(args_)!.markdown() } // produce a nice looking bar chart see // https://echarts.apache.org/examples/en/index.html#chart-type-bar -pub fn (mut s Sheet) wiki_bar_chart(args_ RowGetArgs) !string { - mut args := args_ - args.rowname = s.rowname_get(args)! - header := s.header_get_as_string(args.period_type)! - data := s.data_get_as_string(args)! - bar1 := " - ${s.wiki_title_chart(args)} - xAxis: { - type: 'category', - data: [${header}] - }, - yAxis: { - type: 'value' - }, - series: [ - { - data: [${data}], - type: 'bar', - showBackground: true, - backgroundStyle: { - color: 'rgba(180, 180, 180, 0.2)' - } - } - ] - " - out := remove_empty_line('```echarts\n{${bar1}\n};\n```\n') - return out +pub fn (s Sheet) wiki_bar_chart(args_ RowGetArgs) !string { + return s.bar_chart(args_)!.markdown() } // produce a nice looking bar chart see // https://echarts.apache.org/examples/en/index.html#chart-type-bar -pub fn (mut s Sheet) wiki_pie_chart(args_ RowGetArgs) !string { - mut args := args_ - args.rowname = s.rowname_get(args)! - header := s.header_get_as_list(args.period_type)! - data := s.data_get_as_list(args)! - - mut radius := '' - if args.size.len > 0 { - radius = "radius: '${args.size}'," - } - - if header.len != data.len { - return error('data and header lengths must match.\n${header}\n${data}') - } - - mut data_lines := []string{} - for i, _ in data { - data_lines << '{ value: ${data[i]}, name: ${header[i]}}' - } - data_str := '[${data_lines.join(',')}]' - - bar1 := " - ${s.wiki_title_chart(args)} - tooltip: { - trigger: 'item' - }, - legend: { - orient: 'vertical', - left: 'left' - }, - series: [ - { - name: 'Access From', - type: 'pie', - ${radius} - data: ${data_str}, - emphasis: { - itemStyle: { - shadowBlur: 10, - shadowOffsetX: 0, - shadowColor: 'rgba(0, 0, 0, 0.5)' - } - } - } - ] - - " - out := remove_empty_line('```echarts\n{${bar1}\n};\n```\n') - return out +pub fn (s Sheet) wiki_pie_chart(args_ RowGetArgs) !string { + return s.pie_chart(args_)!.markdown() } diff --git a/lib/web/echarts/echarts.v b/lib/web/echarts/echarts.v new file mode 100644 index 00000000..07ae945b --- /dev/null +++ b/lib/web/echarts/echarts.v @@ -0,0 +1,87 @@ +module echarts + +import json +import x.json2 + +pub struct Title { +pub: + text string @[json: 'text'; omitempty] + subtext string @[json: 'subtext'; omitempty] + left string @[json: 'left'; omitempty] +} + +pub struct Tooltip { +pub: + trigger string @[json: 'trigger'; omitempty] +} + +pub struct Legend { +pub: + data []string @[json: 'data'; omitempty] + orient string @[omitempty] + left string @[omitempty] +} + +pub struct Grid { +pub: + left string @[json: 'left'; omitempty] + right string @[json: 'right'; omitempty] + bottom string @[json: 'bottom'; omitempty] + contain_label bool @[json: 'containLabel'; omitempty] +} + +pub struct ToolboxFeature { +pub: + save_as_image map[string]string @[json: 'saveAsImage'; omitempty] +} + +pub struct Toolbox { +pub: + feature ToolboxFeature @[json: 'feature'; omitempty] +} + +pub struct XAxis { +pub: + type_ string @[json: 'type'; omitempty] + boundary_gap bool @[json: 'boundaryGap'; omitempty] + data []string @[json: 'data'; omitempty] +} + +pub struct YAxis { +pub: + type_ string @[json: 'type'; omitempty] +} + +pub struct Series { +pub: + name string @[json: 'name'; omitempty] + type_ string @[json: 'type'; omitempty] + stack string @[json: 'stack'; omitempty] + data []string @[json: 'data'; omitempty] + radius int @[omitempty] + emphasis Emphasis @[omitempty] +} + +pub struct Emphasis { +pub: + item_style ItemStyle @[json: 'itemStyle'; omitempty] +} + +pub struct ItemStyle { +pub: + shadow_blur int @[json: 'shadowBlur'; omitempty] + shadow_offset_x int @[json: 'shadowOffsetX'; omitempty] + shadow_color string @[json: 'shadowColor'; omitempty] +} + +pub struct EChartsOption { +pub: + title Title @[json: 'title'; omitempty] + tooltip Tooltip @[json: 'tooltip'; omitempty] + legend Legend @[json: 'legend'; omitempty] + grid Grid @[json: 'grid'; omitempty] + toolbox Toolbox @[json: 'toolbox'; omitempty] + x_axis XAxis @[json: 'xAxis'; omitempty] + y_axis YAxis @[json: 'yAxis'; omitempty] + series []Series @[json: 'series'; omitempty] +} diff --git a/lib/web/echarts/echarts_test.v b/lib/web/echarts/echarts_test.v new file mode 100644 index 00000000..7a894120 --- /dev/null +++ b/lib/web/echarts/echarts_test.v @@ -0,0 +1,55 @@ +module echarts + +import json + +const option_json = '{"title":{"text":"Main Title","subtext":"Subtitle","left":"center"},"tooltip":{"trigger":"axis"},"legend":{"data":["Example1","Example2"]},"grid":{"left":"3%","right":"4%","bottom":"3%","containLabel":true},"xAxis":{"type":"category","data":["Jan","Feb","Mar"]},"yAxis":{"type":"value"},"series":[{"name":"Example1","type":"line","stack":"Total","data":["10","20","30"]},{"name":"Example2","type":"line","stack":"Total","data":["15","25","35"]}]}' + +fn test_echarts() { + option := EChartsOption{ + title: Title{ + text: 'Main Title' + subtext: 'Subtitle' + left: 'center' + } + tooltip: Tooltip{ + trigger: 'axis' + } + legend: Legend{ + data: ['Example1', 'Example2'] + } + grid: Grid{ + left: '3%' + right: '4%' + bottom: '3%' + contain_label: true + } + toolbox: Toolbox{ + feature: ToolboxFeature{ + save_as_image: {} + } + } + x_axis: XAxis{ + type_: 'category' + boundary_gap: false + data: ['Jan', 'Feb', 'Mar'] + } + y_axis: YAxis{ + type_: 'value' + } + series: [ + Series{ + name: 'Example1' + type_: 'line' + stack: 'Total' + data: ['10', '20', '30'] + }, + Series{ + name: 'Example2' + type_: 'line' + stack: 'Total' + data: ['15', '25', '35'] + }, + ] + } + assert json.encode(option) == option_json +} diff --git a/lib/web/echarts/encode.v b/lib/web/echarts/encode.v new file mode 100644 index 00000000..3eae7a18 --- /dev/null +++ b/lib/web/echarts/encode.v @@ -0,0 +1,120 @@ +module echarts + +import x.json2 as json + +pub fn (o EChartsOption) json() string { + return json.encode(o) +} + +pub fn (o EChartsOption) mdx() string { + option := format_js_object(o, true) + return '' +} + +pub fn (o EChartsOption) markdown() string { + option := format_js_object(o, true) + return '```echarts\n{${option}\n};\n```\n' +} + +// Generic function to format JavaScript-like objects +fn format_js_object[T](obj T, omitempty bool) string { + mut result := '' + result += '{' + + $for field in T.fields { + field_name := if field.attrs.any(it.starts_with('json:')) { + field.attrs.filter(it.starts_with('json'))[0].all_after('json:').trim_space() + } else { + field.name + } + value := obj.$(field.name) + formatted_value := format_js_value(value, field.attrs.contains('omitempty')) + if formatted_value.trim_space() != '' || !omitempty { + result += '${field_name}: ${formatted_value.trim_space()}, ' + } + } + result += '}' + if result == '{}' && omitempty { + return '' + } + return result.str().replace(', }', '}') // Remove trailing comma +} + +// Fully generic function to format any JS value +// TODO: improve code below, far from cleanest implementation +// currently is sufficient since only used in echart mdx export +fn format_js_value[T](value T, omitempty bool) string { + return $if T is string { + // is actually map + if value.str().starts_with('{') && value.str().ends_with('}') { + value + // map_any := json2.raw_decode(value.str()) or {'{}'}.as_map() + // println('debugzo21 ${map_any}') + // mut val := '{' + // for k, v in map_any { + // val += '${k}: ${format_js_value(v.str(), false)}' + // } + // val += '}' + // if val == '{}' && omitempty { + // return '' + // } + // val + } else { + val := '"${value}"' + if val == '""' && omitempty { + return '' + } + val + } + } $else $if T is int { + if '${value}' == '0' && omitempty { + '' + } else { + '${value}' + } + } $else $if T is f64 { + if '${value}' == '0.0' && omitempty { + '' + } else { + '${value}' + } + } $else $if T is bool { + if '${value}' == 'false' && omitempty { + '' + } else { + '${value}' + } + } $else $if T is $struct { + val := format_js_object(value, omitempty) + if val == '' && omitempty { + return '' + } + val + } $else $if T is $array { + mut arr := '[' + for i in 0 .. value.len { + if i != 0 { + arr += ', ' + } + val := format_js_value(value[i], omitempty) + if val.starts_with('"{') && val.ends_with('}"') { + arr += val.trim('"') + } else if val.starts_with('"\'') && val.ends_with('\'"') { + arr += val.trim('"') + } else if val.trim('"').trim_space().f64() != 0 { + arr += val.trim('"').trim_space() + } else if val.trim('"').trim_space() == '0' || val.trim('"').trim_space() == '0.0' { + arr += '0' + } else { + arr += val + } + } + arr += ']' + if omitempty && arr == '[]' { + return '' + } + arr + } $else { + 'null' + } +}