From bd95fdc5d741a67def77b225f9f33488c8e9584e Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Tue, 11 Jul 2023 19:45:27 +0800
Subject: [PATCH] :recycle: Refactor av data structure
---
kernel/api/av.go | 18 +-
kernel/av/{av.go => attribute_view.go} | 80 ++--
kernel/av/column.go | 470 ---------------------
kernel/av/column_calc.go | 48 +++
kernel/av/filter.go | 42 +-
kernel/av/sort.go | 47 +--
kernel/av/table.go | 556 +++++++++++++++++++++++++
kernel/model/attribute_view.go | 45 +-
8 files changed, 724 insertions(+), 582 deletions(-)
rename kernel/av/{av.go => attribute_view.go} (74%)
create mode 100644 kernel/av/column_calc.go
create mode 100644 kernel/av/table.go
diff --git a/kernel/api/av.go b/kernel/api/av.go
index 80ba40b50..a3c8e3761 100644
--- a/kernel/api/av.go
+++ b/kernel/api/av.go
@@ -35,13 +35,27 @@ func renderAttributeView(c *gin.Context) {
}
id := arg["id"].(string)
- av, err := model.RenderAttributeView(id)
+ view, attrView, err := model.RenderAttributeView(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
+
+ var views []map[string]interface{}
+ for _, v := range attrView.Views {
+ view := map[string]interface{}{
+ "id": v.ID,
+ "name": v.Name,
+ }
+
+ views = append(views, view)
+ }
+
ret.Data = map[string]interface{}{
- "av": av,
+ "name": attrView.Name,
+ "views": views,
+ "viewType": view.Type(),
+ "view": view,
}
}
diff --git a/kernel/av/av.go b/kernel/av/attribute_view.go
similarity index 74%
rename from kernel/av/av.go
rename to kernel/av/attribute_view.go
index fbf581e07..28e73d238 100644
--- a/kernel/av/av.go
+++ b/kernel/av/attribute_view.go
@@ -33,43 +33,60 @@ import (
// AttributeView 描述了属性视图的结构。
type AttributeView struct {
- Spec int `json:"spec"`
+ Spec int `json:"spec"` // 格式版本
ID string `json:"id"` // 属性视图 ID
Name string `json:"name"` // 属性视图名称
- Columns []*Column `json:"columns"` // 表格列名
- Rows []*Row `json:"rows"` // 表格行记录
+ Columns []*Column `json:"columns"` // 列
+ Rows []*Row `json:"rows"` // 行
- Type AttributeViewType `json:"type"` // 属性视图类型
- Filters []*AttributeViewFilter `json:"filters"` // 过滤规则
- Sorts []*AttributeViewSort `json:"sorts"` // 排序规则
+ CurrentViewID string `json:"currentViewId"` // 当前视图 ID
+ Views []*View `json:"views"` // 视图
}
-// AttributeViewType 描述了属性视图的类型。
-type AttributeViewType string
+// View 描述了视图的结构。
+type View struct {
+ ID string `json:"id"` // 视图 ID
+ Name string `json:"name"` // 视图名称
+ Type ViewType `json:"type"` // 视图类型
+
+ Filters []*ViewFilter `json:"filters"` // 过滤规则
+ Sorts []*ViewSort `json:"sorts"` // 排序规则
+}
+
+// ViewType 描述了视图的类型。
+type ViewType string
const (
- AttributeViewTypeTable AttributeViewType = "table" // 属性视图类型 - 表格
+ ViewTypeTable ViewType = "table" // 属性视图类型 - 表格
+ ViewTypeKanban ViewType = "kanban" // 属性视图类型 - 看板
)
-func NewAttributeView(id string) *AttributeView {
- return &AttributeView{
- Spec: 0,
- ID: id,
- Name: "Table",
- Columns: []*Column{{ID: ast.NewNodeID(), Name: "Block", Type: ColumnTypeBlock}},
- Rows: []*Row{},
- Type: AttributeViewTypeTable,
- Filters: []*AttributeViewFilter{},
- Sorts: []*AttributeViewSort{},
- }
+// Viewable 描述了视图的接口。
+type Viewable interface {
+ Filterable
+ Sortable
+ Calculable
+
+ Type() ViewType
}
-func (av *AttributeView) GetColumnNames() (ret []string) {
- ret = []string{}
- for _, column := range av.Columns {
- ret = append(ret, column.Name)
+func NewAttributeView(id string) *AttributeView {
+ view := &View{
+ ID: ast.NewNodeID(),
+ Name: "Table",
+ Type: ViewTypeTable,
+ Filters: []*ViewFilter{},
+ Sorts: []*ViewSort{},
+ }
+
+ return &AttributeView{
+ Spec: 0,
+ ID: id,
+ Columns: []*Column{{ID: ast.NewNodeID(), Name: "Block", Type: ColumnTypeBlock}},
+ Rows: []*Row{},
+ CurrentViewID: view.ID,
+ Views: []*View{view},
}
- return
}
func ParseAttributeView(avID string) (ret *AttributeView, err error) {
@@ -90,6 +107,19 @@ func ParseAttributeView(avID string) (ret *AttributeView, err error) {
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
return
}
+
+ if 1 > len(ret.Views) {
+ view := &View{
+ ID: ast.NewNodeID(),
+ Name: "Table",
+ Type: ViewTypeTable,
+ Filters: []*ViewFilter{},
+ Sorts: []*ViewSort{},
+ }
+
+ ret.CurrentViewID = view.ID
+ ret.Views = []*View{view}
+ }
return
}
diff --git a/kernel/av/column.go b/kernel/av/column.go
index 46bf529dd..5ed989f2e 100644
--- a/kernel/av/column.go
+++ b/kernel/av/column.go
@@ -18,8 +18,6 @@ package av
import (
"github.com/88250/lute/ast"
- "math"
- "sort"
)
type ColumnType string
@@ -65,471 +63,3 @@ func NewColumn(name string, columnType ColumnType) *Column {
Type: columnType,
}
}
-
-type ColumnCalc struct {
- Column string `json:"column"`
- Operator CalcOperator `json:"operator"`
- Result *Value `json:"result"`
-}
-
-type CalcOperator string
-
-const (
- CalcOperatorNone CalcOperator = ""
- CalcOperatorCountAll CalcOperator = "Count all"
- CalcOperatorCountValues CalcOperator = "Count values"
- CalcOperatorCountUniqueValues CalcOperator = "Count unique values"
- CalcOperatorCountEmpty CalcOperator = "Count empty"
- CalcOperatorCountNotEmpty CalcOperator = "Count not empty"
- CalcOperatorPercentEmpty CalcOperator = "Percent empty"
- CalcOperatorPercentNotEmpty CalcOperator = "Percent not empty"
- CalcOperatorSum CalcOperator = "Sum"
- CalcOperatorAverage CalcOperator = "Average"
- CalcOperatorMedian CalcOperator = "Median"
- CalcOperatorMin CalcOperator = "Min"
- CalcOperatorMax CalcOperator = "Max"
- CalcOperatorRange CalcOperator = "Range"
- CalcOperatorEarliest CalcOperator = "Earliest"
- CalcOperatorLatest CalcOperator = "Latest"
-)
-
-func (av *AttributeView) CalcCols() {
- for i, col := range av.Columns {
- if nil == col.Calc {
- continue
- }
-
- if CalcOperatorNone == col.Calc.Operator {
- continue
- }
-
- switch col.Type {
- case ColumnTypeText:
- av.calcColText(col, i)
- case ColumnTypeNumber:
- av.calcColNumber(col, i)
- case ColumnTypeDate:
- av.calcColDate(col, i)
- case ColumnTypeSelect:
- av.calcColSelect(col, i)
- case ColumnTypeMSelect:
- av.calcColMSelect(col, i)
- }
- }
-}
-
-func (av *AttributeView) calcColMSelect(col *Column, colIndex int) {
- switch col.Calc.Operator {
- case CalcOperatorCountAll:
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(av.Rows))}}
- case CalcOperatorCountValues:
- countValues := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
- countValues += len(row.Cells[colIndex].Value.MSelect)
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
- case CalcOperatorCountUniqueValues:
- countUniqueValues := 0
- uniqueValues := map[string]bool{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
- for _, sel := range row.Cells[colIndex].Value.MSelect {
- if _, ok := uniqueValues[sel.Content]; !ok {
- uniqueValues[sel.Content] = true
- countUniqueValues++
- }
- }
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
- case CalcOperatorCountEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 0 == len(row.Cells[colIndex].Value.MSelect) {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
- case CalcOperatorCountNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
- case CalcOperatorPercentEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 0 == len(row.Cells[colIndex].Value.MSelect) {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(av.Rows))}}
- case CalcOperatorPercentNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(av.Rows))}}
- }
-}
-
-func (av *AttributeView) calcColSelect(col *Column, colIndex int) {
- switch col.Calc.Operator {
- case CalcOperatorCountAll:
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(av.Rows))}}
- case CalcOperatorCountValues:
- countValues := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
- countValues++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
- case CalcOperatorCountUniqueValues:
- countUniqueValues := 0
- uniqueValues := map[string]bool{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
- uniqueValues[row.Cells[colIndex].Value.MSelect[0].Content] = true
- countUniqueValues++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
- case CalcOperatorCountEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 1 > len(row.Cells[colIndex].Value.MSelect) || nil == row.Cells[colIndex].Value.MSelect[0] || "" == row.Cells[colIndex].Value.MSelect[0].Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
- case CalcOperatorCountNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
- case CalcOperatorPercentEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 1 > len(row.Cells[colIndex].Value.MSelect) || nil == row.Cells[colIndex].Value.MSelect[0] || "" == row.Cells[colIndex].Value.MSelect[0].Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(av.Rows))}}
- case CalcOperatorPercentNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(av.Rows))}}
- }
-}
-
-func (av *AttributeView) calcColDate(col *Column, colIndex int) {
- switch col.Calc.Operator {
- case CalcOperatorCountAll:
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(av.Rows))}}
- case CalcOperatorCountValues:
- countValues := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- countValues++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
- case CalcOperatorCountUniqueValues:
- countUniqueValues := 0
- uniqueValues := map[int64]bool{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- if _, ok := uniqueValues[row.Cells[colIndex].Value.Date.Content]; !ok {
- countUniqueValues++
- uniqueValues[row.Cells[colIndex].Value.Date.Content] = true
- }
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
- case CalcOperatorCountEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Date || 0 == row.Cells[colIndex].Value.Date.Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
- case CalcOperatorCountNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
- case CalcOperatorPercentEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Date || 0 == row.Cells[colIndex].Value.Date.Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(av.Rows))}}
- case CalcOperatorPercentNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(av.Rows))}}
- case CalcOperatorEarliest:
- earliest := int64(0)
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- if 0 == earliest || earliest > row.Cells[colIndex].Value.Date.Content {
- earliest = row.Cells[colIndex].Value.Date.Content
- }
- }
- }
- if 0 != earliest {
- col.Calc.Result = &Value{Date: &ValueDate{Content: earliest}}
- }
- case CalcOperatorLatest:
- latest := int64(0)
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- if 0 == latest || latest < row.Cells[colIndex].Value.Date.Content {
- latest = row.Cells[colIndex].Value.Date.Content
- }
- }
- }
- if 0 != latest {
- col.Calc.Result = &Value{Date: &ValueDate{Content: latest}}
- }
- case CalcOperatorRange:
- earliest := int64(0)
- latest := int64(0)
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
- if 0 == earliest || earliest > row.Cells[colIndex].Value.Date.Content {
- earliest = row.Cells[colIndex].Value.Date.Content
- }
- if 0 == latest || latest < row.Cells[colIndex].Value.Date.Content {
- latest = row.Cells[colIndex].Value.Date.Content
- }
- }
- }
- if 0 != earliest && 0 != latest {
- col.Calc.Result = &Value{Date: &ValueDate{Content: latest - earliest}}
- }
- }
-}
-
-func (av *AttributeView) calcColNumber(col *Column, colIndex int) {
- switch col.Calc.Operator {
- case CalcOperatorCountAll:
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(av.Rows))}}
- case CalcOperatorCountValues:
- countValues := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number {
- countValues++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
- case CalcOperatorCountUniqueValues:
- countUniqueValues := 0
- uniqueValues := map[float64]bool{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- if !uniqueValues[row.Cells[colIndex].Value.Number.Content] {
- uniqueValues[row.Cells[colIndex].Value.Number.Content] = true
- countUniqueValues++
- }
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
- case CalcOperatorCountEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Number && !row.Cells[colIndex].Value.Number.IsNotEmpty {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
- case CalcOperatorCountNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
- case CalcOperatorPercentEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Number && !row.Cells[colIndex].Value.Number.IsNotEmpty {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(av.Rows))}}
- case CalcOperatorPercentNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(av.Rows))}}
- case CalcOperatorSum:
- sum := 0.0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- sum += row.Cells[colIndex].Value.Number.Content
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: sum}}
- case CalcOperatorAverage:
- sum := 0.0
- count := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- sum += row.Cells[colIndex].Value.Number.Content
- count++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: sum / float64(count)}}
- case CalcOperatorMedian:
- values := []float64{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- values = append(values, row.Cells[colIndex].Value.Number.Content)
- }
- }
- sort.Float64s(values)
- if len(values) > 0 {
- if len(values)%2 == 0 {
- col.Calc.Result = &Value{Number: &ValueNumber{Content: (values[len(values)/2-1] + values[len(values)/2]) / 2}}
- } else {
- col.Calc.Result = &Value{Number: &ValueNumber{Content: values[len(values)/2]}}
- }
- } else {
- col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
- }
- case CalcOperatorMin:
- min := math.MaxFloat64
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- if row.Cells[colIndex].Value.Number.Content < min {
- min = row.Cells[colIndex].Value.Number.Content
- }
- }
- }
- if math.MaxFloat64 != min {
- col.Calc.Result = &Value{Number: &ValueNumber{Content: min}}
- } else {
- col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
- }
- case CalcOperatorMax:
- max := -math.MaxFloat64
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- if row.Cells[colIndex].Value.Number.Content > max {
- max = row.Cells[colIndex].Value.Number.Content
- }
- }
- }
- if -math.MaxFloat64 != max {
- col.Calc.Result = &Value{Number: &ValueNumber{Content: max}}
- } else {
- col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
- }
- case CalcOperatorRange:
- min := math.MaxFloat64
- max := -math.MaxFloat64
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
- if row.Cells[colIndex].Value.Number.Content < min {
- min = row.Cells[colIndex].Value.Number.Content
- }
- if row.Cells[colIndex].Value.Number.Content > max {
- max = row.Cells[colIndex].Value.Number.Content
- }
- }
- }
- if math.MaxFloat64 != min && -math.MaxFloat64 != max {
- col.Calc.Result = &Value{Number: &ValueNumber{Content: max - min}}
- } else {
- col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
- }
- }
-}
-
-func (av *AttributeView) calcColText(col *Column, colIndex int) {
- switch col.Calc.Operator {
- case CalcOperatorCountAll:
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(av.Rows))}}
- case CalcOperatorCountValues:
- countValues := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
- countValues++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
- case CalcOperatorCountUniqueValues:
- countUniqueValues := 0
- uniqueValues := map[string]bool{}
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
- if !uniqueValues[row.Cells[colIndex].Value.Text.Content] {
- uniqueValues[row.Cells[colIndex].Value.Text.Content] = true
- countUniqueValues++
- }
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
- case CalcOperatorCountEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Text || "" == row.Cells[colIndex].Value.Text.Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
- case CalcOperatorCountNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
- case CalcOperatorPercentEmpty:
- countEmpty := 0
- for _, row := range av.Rows {
- if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Text || "" == row.Cells[colIndex].Value.Text.Content {
- countEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(av.Rows))}}
- case CalcOperatorPercentNotEmpty:
- countNotEmpty := 0
- for _, row := range av.Rows {
- if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
- countNotEmpty++
- }
- }
- col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(av.Rows))}}
- }
-}
diff --git a/kernel/av/column_calc.go b/kernel/av/column_calc.go
new file mode 100644
index 000000000..e2df83e57
--- /dev/null
+++ b/kernel/av/column_calc.go
@@ -0,0 +1,48 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+type Calculable interface {
+ CalcCols()
+}
+
+type ColumnCalc struct {
+ Column string `json:"column"`
+ Operator CalcOperator `json:"operator"`
+ Result *Value `json:"result"`
+}
+
+type CalcOperator string
+
+const (
+ CalcOperatorNone CalcOperator = ""
+ CalcOperatorCountAll CalcOperator = "Count all"
+ CalcOperatorCountValues CalcOperator = "Count values"
+ CalcOperatorCountUniqueValues CalcOperator = "Count unique values"
+ CalcOperatorCountEmpty CalcOperator = "Count empty"
+ CalcOperatorCountNotEmpty CalcOperator = "Count not empty"
+ CalcOperatorPercentEmpty CalcOperator = "Percent empty"
+ CalcOperatorPercentNotEmpty CalcOperator = "Percent not empty"
+ CalcOperatorSum CalcOperator = "Sum"
+ CalcOperatorAverage CalcOperator = "Average"
+ CalcOperatorMedian CalcOperator = "Median"
+ CalcOperatorMin CalcOperator = "Min"
+ CalcOperatorMax CalcOperator = "Max"
+ CalcOperatorRange CalcOperator = "Range"
+ CalcOperatorEarliest CalcOperator = "Earliest"
+ CalcOperatorLatest CalcOperator = "Latest"
+)
diff --git a/kernel/av/filter.go b/kernel/av/filter.go
index 89525bc17..9760ccf26 100644
--- a/kernel/av/filter.go
+++ b/kernel/av/filter.go
@@ -16,7 +16,11 @@
package av
-type AttributeViewFilter struct {
+type Filterable interface {
+ FilterRows()
+}
+
+type ViewFilter struct {
Column string `json:"column"`
Operator FilterOperator `json:"operator"`
Value *Value `json:"value"`
@@ -40,39 +44,3 @@ const (
FilterOperatorIsBetween FilterOperator = "Is between"
FilterOperatorIsRelativeToToday FilterOperator = "Is relative to today"
)
-
-func (av *AttributeView) FilterRows() {
- if 1 > len(av.Filters) {
- return
- }
-
- var colIndexes []int
- for _, f := range av.Filters {
- for i, c := range av.Columns {
- if c.ID == f.Column {
- colIndexes = append(colIndexes, i)
- break
- }
- }
- }
-
- rows := []*Row{}
- for _, row := range av.Rows {
- pass := true
- for j, index := range colIndexes {
- c := av.Columns[index]
- if c.Type == ColumnTypeBlock {
- continue
- }
-
- if !row.Cells[index].Value.CompareOperator(av.Filters[j].Value, av.Filters[j].Operator) {
- pass = false
- break
- }
- }
- if pass {
- rows = append(rows, row)
- }
- }
- av.Rows = rows
-}
diff --git a/kernel/av/sort.go b/kernel/av/sort.go
index fe0061a89..d95877a05 100644
--- a/kernel/av/sort.go
+++ b/kernel/av/sort.go
@@ -16,9 +16,11 @@
package av
-import "sort"
+type Sortable interface {
+ SortRows()
+}
-type AttributeViewSort struct {
+type ViewSort struct {
Column string `json:"column"` // 列 ID
Order SortOrder `json:"order"` // 排序顺序
}
@@ -29,44 +31,3 @@ const (
SortOrderAsc SortOrder = "ASC"
SortOrderDesc SortOrder = "DESC"
)
-
-func (av *AttributeView) SortRows() {
- if 1 > len(av.Sorts) {
- return
- }
-
- type ColIndexSort struct {
- Index int
- Order SortOrder
- }
-
- var colIndexSorts []*ColIndexSort
- for _, s := range av.Sorts {
- for i, c := range av.Columns {
- if c.ID == s.Column {
- colIndexSorts = append(colIndexSorts, &ColIndexSort{Index: i, Order: s.Order})
- break
- }
- }
- }
-
- sort.Slice(av.Rows, func(i, j int) bool {
- for _, colIndexSort := range colIndexSorts {
- c := av.Columns[colIndexSort.Index]
- if c.Type == ColumnTypeBlock {
- continue
- }
-
- result := av.Rows[i].Cells[colIndexSort.Index].Value.Compare(av.Rows[j].Cells[colIndexSort.Index].Value)
- if 0 == result {
- continue
- }
-
- if colIndexSort.Order == SortOrderAsc {
- return 0 > result
- }
- return 0 < result
- }
- return false
- })
-}
diff --git a/kernel/av/table.go b/kernel/av/table.go
new file mode 100644
index 000000000..bb104fe13
--- /dev/null
+++ b/kernel/av/table.go
@@ -0,0 +1,556 @@
+// SiYuan - Refactor your thinking
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package av
+
+import (
+ "math"
+ "sort"
+)
+
+// Table 描述了表格视图的结构。
+type Table struct {
+ Spec int `json:"spec"` // 视图格式版本
+
+ ID string `json:"id"` // 表格 ID
+ Name string `json:"name"` // 表格名称
+ Columns []*Column `json:"columns"` // 表格列
+ Rows []*Row `json:"rows"` // 表格行
+ Filters []*ViewFilter `json:"filters"` // 过滤规则
+ Sorts []*ViewSort `json:"sorts"` // 排序规则
+}
+
+func (table *Table) Type() ViewType {
+ return ViewTypeTable
+}
+
+func (table *Table) SortRows() {
+ if 1 > len(table.Sorts) {
+ return
+ }
+
+ type ColIndexSort struct {
+ Index int
+ Order SortOrder
+ }
+
+ var colIndexSorts []*ColIndexSort
+ for _, s := range table.Sorts {
+ for i, c := range table.Columns {
+ if c.ID == s.Column {
+ colIndexSorts = append(colIndexSorts, &ColIndexSort{Index: i, Order: s.Order})
+ break
+ }
+ }
+ }
+
+ sort.Slice(table.Rows, func(i, j int) bool {
+ for _, colIndexSort := range colIndexSorts {
+ c := table.Columns[colIndexSort.Index]
+ if c.Type == ColumnTypeBlock {
+ continue
+ }
+
+ result := table.Rows[i].Cells[colIndexSort.Index].Value.Compare(table.Rows[j].Cells[colIndexSort.Index].Value)
+ if 0 == result {
+ continue
+ }
+
+ if colIndexSort.Order == SortOrderAsc {
+ return 0 > result
+ }
+ return 0 < result
+ }
+ return false
+ })
+}
+
+func (table *Table) FilterRows() {
+ if 1 > len(table.Filters) {
+ return
+ }
+
+ var colIndexes []int
+ for _, f := range table.Filters {
+ for i, c := range table.Columns {
+ if c.ID == f.Column {
+ colIndexes = append(colIndexes, i)
+ break
+ }
+ }
+ }
+
+ rows := []*Row{}
+ for _, row := range table.Rows {
+ pass := true
+ for j, index := range colIndexes {
+ c := table.Columns[index]
+ if c.Type == ColumnTypeBlock {
+ continue
+ }
+
+ if !row.Cells[index].Value.CompareOperator(table.Filters[j].Value, table.Filters[j].Operator) {
+ pass = false
+ break
+ }
+ }
+ if pass {
+ rows = append(rows, row)
+ }
+ }
+ table.Rows = rows
+}
+
+func (table *Table) CalcCols() {
+ for i, col := range table.Columns {
+ if nil == col.Calc {
+ continue
+ }
+
+ if CalcOperatorNone == col.Calc.Operator {
+ continue
+ }
+
+ switch col.Type {
+ case ColumnTypeText:
+ table.calcColText(col, i)
+ case ColumnTypeNumber:
+ table.calcColNumber(col, i)
+ case ColumnTypeDate:
+ table.calcColDate(col, i)
+ case ColumnTypeSelect:
+ table.calcColSelect(col, i)
+ case ColumnTypeMSelect:
+ table.calcColMSelect(col, i)
+ }
+ }
+}
+
+func (table *Table) calcColMSelect(col *Column, colIndex int) {
+ switch col.Calc.Operator {
+ case CalcOperatorCountAll:
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(table.Rows))}}
+ case CalcOperatorCountValues:
+ countValues := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
+ countValues += len(row.Cells[colIndex].Value.MSelect)
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
+ case CalcOperatorCountUniqueValues:
+ countUniqueValues := 0
+ uniqueValues := map[string]bool{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
+ for _, sel := range row.Cells[colIndex].Value.MSelect {
+ if _, ok := uniqueValues[sel.Content]; !ok {
+ uniqueValues[sel.Content] = true
+ countUniqueValues++
+ }
+ }
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
+ case CalcOperatorCountEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 0 == len(row.Cells[colIndex].Value.MSelect) {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
+ case CalcOperatorCountNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
+ case CalcOperatorPercentEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 0 == len(row.Cells[colIndex].Value.MSelect) {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorPercentNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(table.Rows))}}
+ }
+}
+
+func (table *Table) calcColSelect(col *Column, colIndex int) {
+ switch col.Calc.Operator {
+ case CalcOperatorCountAll:
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(table.Rows))}}
+ case CalcOperatorCountValues:
+ countValues := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
+ countValues++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
+ case CalcOperatorCountUniqueValues:
+ countUniqueValues := 0
+ uniqueValues := map[string]bool{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
+ uniqueValues[row.Cells[colIndex].Value.MSelect[0].Content] = true
+ countUniqueValues++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
+ case CalcOperatorCountEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 1 > len(row.Cells[colIndex].Value.MSelect) || nil == row.Cells[colIndex].Value.MSelect[0] || "" == row.Cells[colIndex].Value.MSelect[0].Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
+ case CalcOperatorCountNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
+ case CalcOperatorPercentEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.MSelect || 1 > len(row.Cells[colIndex].Value.MSelect) || nil == row.Cells[colIndex].Value.MSelect[0] || "" == row.Cells[colIndex].Value.MSelect[0].Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorPercentNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.MSelect && 0 < len(row.Cells[colIndex].Value.MSelect) && nil != row.Cells[colIndex].Value.MSelect[0] && "" != row.Cells[colIndex].Value.MSelect[0].Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(table.Rows))}}
+ }
+}
+
+func (table *Table) calcColDate(col *Column, colIndex int) {
+ switch col.Calc.Operator {
+ case CalcOperatorCountAll:
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(table.Rows))}}
+ case CalcOperatorCountValues:
+ countValues := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ countValues++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
+ case CalcOperatorCountUniqueValues:
+ countUniqueValues := 0
+ uniqueValues := map[int64]bool{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ if _, ok := uniqueValues[row.Cells[colIndex].Value.Date.Content]; !ok {
+ countUniqueValues++
+ uniqueValues[row.Cells[colIndex].Value.Date.Content] = true
+ }
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
+ case CalcOperatorCountEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Date || 0 == row.Cells[colIndex].Value.Date.Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
+ case CalcOperatorCountNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
+ case CalcOperatorPercentEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Date || 0 == row.Cells[colIndex].Value.Date.Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorPercentNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorEarliest:
+ earliest := int64(0)
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ if 0 == earliest || earliest > row.Cells[colIndex].Value.Date.Content {
+ earliest = row.Cells[colIndex].Value.Date.Content
+ }
+ }
+ }
+ if 0 != earliest {
+ col.Calc.Result = &Value{Date: &ValueDate{Content: earliest}}
+ }
+ case CalcOperatorLatest:
+ latest := int64(0)
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ if 0 == latest || latest < row.Cells[colIndex].Value.Date.Content {
+ latest = row.Cells[colIndex].Value.Date.Content
+ }
+ }
+ }
+ if 0 != latest {
+ col.Calc.Result = &Value{Date: &ValueDate{Content: latest}}
+ }
+ case CalcOperatorRange:
+ earliest := int64(0)
+ latest := int64(0)
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Date && 0 != row.Cells[colIndex].Value.Date.Content {
+ if 0 == earliest || earliest > row.Cells[colIndex].Value.Date.Content {
+ earliest = row.Cells[colIndex].Value.Date.Content
+ }
+ if 0 == latest || latest < row.Cells[colIndex].Value.Date.Content {
+ latest = row.Cells[colIndex].Value.Date.Content
+ }
+ }
+ }
+ if 0 != earliest && 0 != latest {
+ col.Calc.Result = &Value{Date: &ValueDate{Content: latest - earliest}}
+ }
+ }
+}
+
+func (table *Table) calcColNumber(col *Column, colIndex int) {
+ switch col.Calc.Operator {
+ case CalcOperatorCountAll:
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(table.Rows))}}
+ case CalcOperatorCountValues:
+ countValues := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number {
+ countValues++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
+ case CalcOperatorCountUniqueValues:
+ countUniqueValues := 0
+ uniqueValues := map[float64]bool{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ if !uniqueValues[row.Cells[colIndex].Value.Number.Content] {
+ uniqueValues[row.Cells[colIndex].Value.Number.Content] = true
+ countUniqueValues++
+ }
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
+ case CalcOperatorCountEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Number && !row.Cells[colIndex].Value.Number.IsNotEmpty {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
+ case CalcOperatorCountNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
+ case CalcOperatorPercentEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Number && !row.Cells[colIndex].Value.Number.IsNotEmpty {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorPercentNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorSum:
+ sum := 0.0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ sum += row.Cells[colIndex].Value.Number.Content
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: sum}}
+ case CalcOperatorAverage:
+ sum := 0.0
+ count := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ sum += row.Cells[colIndex].Value.Number.Content
+ count++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: sum / float64(count)}}
+ case CalcOperatorMedian:
+ values := []float64{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ values = append(values, row.Cells[colIndex].Value.Number.Content)
+ }
+ }
+ sort.Float64s(values)
+ if len(values) > 0 {
+ if len(values)%2 == 0 {
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: (values[len(values)/2-1] + values[len(values)/2]) / 2}}
+ } else {
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: values[len(values)/2]}}
+ }
+ } else {
+ col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
+ }
+ case CalcOperatorMin:
+ min := math.MaxFloat64
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ if row.Cells[colIndex].Value.Number.Content < min {
+ min = row.Cells[colIndex].Value.Number.Content
+ }
+ }
+ }
+ if math.MaxFloat64 != min {
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: min}}
+ } else {
+ col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
+ }
+ case CalcOperatorMax:
+ max := -math.MaxFloat64
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ if row.Cells[colIndex].Value.Number.Content > max {
+ max = row.Cells[colIndex].Value.Number.Content
+ }
+ }
+ }
+ if -math.MaxFloat64 != max {
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: max}}
+ } else {
+ col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
+ }
+ case CalcOperatorRange:
+ min := math.MaxFloat64
+ max := -math.MaxFloat64
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Number && row.Cells[colIndex].Value.Number.IsNotEmpty {
+ if row.Cells[colIndex].Value.Number.Content < min {
+ min = row.Cells[colIndex].Value.Number.Content
+ }
+ if row.Cells[colIndex].Value.Number.Content > max {
+ max = row.Cells[colIndex].Value.Number.Content
+ }
+ }
+ }
+ if math.MaxFloat64 != min && -math.MaxFloat64 != max {
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: max - min}}
+ } else {
+ col.Calc.Result = &Value{Number: &ValueNumber{IsNotEmpty: false}}
+ }
+ }
+}
+
+func (table *Table) calcColText(col *Column, colIndex int) {
+ switch col.Calc.Operator {
+ case CalcOperatorCountAll:
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(len(table.Rows))}}
+ case CalcOperatorCountValues:
+ countValues := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
+ countValues++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countValues)}}
+ case CalcOperatorCountUniqueValues:
+ countUniqueValues := 0
+ uniqueValues := map[string]bool{}
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
+ if !uniqueValues[row.Cells[colIndex].Value.Text.Content] {
+ uniqueValues[row.Cells[colIndex].Value.Text.Content] = true
+ countUniqueValues++
+ }
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}}
+ case CalcOperatorCountEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Text || "" == row.Cells[colIndex].Value.Text.Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty)}}
+ case CalcOperatorCountNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty)}}
+ case CalcOperatorPercentEmpty:
+ countEmpty := 0
+ for _, row := range table.Rows {
+ if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Text || "" == row.Cells[colIndex].Value.Text.Content {
+ countEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countEmpty) / float64(len(table.Rows))}}
+ case CalcOperatorPercentNotEmpty:
+ countNotEmpty := 0
+ for _, row := range table.Rows {
+ if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Text && "" != row.Cells[colIndex].Value.Text.Content {
+ countNotEmpty++
+ }
+ }
+ col.Calc.Result = &Value{Number: &ValueNumber{Content: float64(countNotEmpty) / float64(len(table.Rows))}}
+ }
+}
diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go
index eb1569707..2d9e6ec36 100644
--- a/kernel/model/attribute_view.go
+++ b/kernel/model/attribute_view.go
@@ -32,18 +32,53 @@ import (
"github.com/siyuan-note/siyuan/kernel/util"
)
-func RenderAttributeView(avID string) (ret *av.AttributeView, err error) {
+func RenderAttributeView(avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
waitForSyncingStorages()
- ret, err = av.ParseAttributeView(avID)
+ attrView, err = av.ParseAttributeView(avID)
if nil != err {
logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
return
}
- ret.FilterRows()
- ret.SortRows()
- ret.CalcCols()
+ if 1 > len(attrView.Views) {
+ err = errors.New("no view")
+ return
+ }
+
+ var view *av.View
+ if "" != attrView.CurrentViewID {
+ for _, v := range attrView.Views {
+ if v.ID == attrView.CurrentViewID {
+ view = v
+ break
+ }
+ }
+ } else {
+ view = attrView.Views[0]
+ }
+
+ switch view.Type {
+ case av.ViewTypeTable:
+ viewable, err = renderAttributeViewTable(attrView, view)
+ }
+
+ viewable.FilterRows()
+ viewable.SortRows()
+ viewable.CalcCols()
+ return
+}
+
+func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *av.Table, err error) {
+ ret = &av.Table{
+ Spec: attrView.Spec,
+ ID: view.ID,
+ Name: view.Name,
+ Columns: attrView.Columns,
+ Rows: attrView.Rows,
+ Filters: view.Filters,
+ Sorts: view.Sorts,
+ }
return
}