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 }