♻️ Refactor av data structure

This commit is contained in:
Daniel 2023-07-11 19:45:27 +08:00
parent 2f99364071
commit bd95fdc5d7
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
8 changed files with 724 additions and 582 deletions

View file

@ -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,
}
}

View file

@ -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
}

View file

@ -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))}}
}
}

48
kernel/av/column_calc.go Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
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"
)

View file

@ -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
}

View file

@ -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
})
}

556
kernel/av/table.go Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
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))}}
}
}

View file

@ -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
}