From 93e98e5b138914339809157eeefd488341bfc552 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 7 Jul 2023 19:55:46 +0800 Subject: [PATCH] :art: Attribute View columns calculate https://github.com/siyuan-note/siyuan/issues/8699 --- app/appearance/langs/en_US.json | 16 ++ app/appearance/langs/es_ES.json | 16 ++ app/appearance/langs/fr_FR.json | 16 ++ app/appearance/langs/zh_CHT.json | 16 ++ app/appearance/langs/zh_CN.json | 16 ++ kernel/av/av.go | 308 +++++++++++++++++++------------ kernel/model/attribute_view.go | 1 + 7 files changed, 273 insertions(+), 116 deletions(-) diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 48a45ec66..90c249880 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,20 @@ { + "calcOperatorNone": "None", + "calcOperatorCountAll": "Count all", + "calcOperatorCountValues": "Count Values", + "calcOperatorCountUniqueValues": "Count unique values", + "calcOperatorCountEmpty": "Count empty", + "calcOperatorCountNotEmpty": "Count not empty", + "calcOperatorPercentEmpty": "Percent empty", + "calcOperatorPercentNotEmpty": "Percent not empty", + "calcOperatorSum": "Sum", + "calcOperatorAverage": "Average", + "calcOperatorMedian": "Median", + "calcOperatorMin": "Min", + "calcOperatorMax": "Max", + "calcOperatorRange": "Range", + "calcOperatorEarliest": "Earliest", + "calcOperatorLatest": "Latest", "filterOperatorIs": "Is", "filterOperatorIsNot": "Is not", "filterOperatorContains": "Contains", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index f8e89a1c3..5ed34c923 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,4 +1,20 @@ { + "calcOperatorNone": "Ninguno", + "calcOperatorCountAll": "Contar todo", + "calcOperatorCountValues": "Valores de conteo", + "calcOperatorCountUniqueValues": "Contar valores únicos", + "calcOperatorCountEmpty": "Cuenta vacía", + "calcOperatorCountNotEmpty": "Cuenta no vacía", + "calcOperatorPercentEmpty": "Porcentaje vacío", + "calcOperatorPercentNotEmpty": "Porcentaje no vacío", + "calcOperatorSum": "Suma", + "calcOperatorAverage": "Promedio", + "calcOperatorMedian": "Mediana", + "calcOperatorMin": "Min", + "calcOperatorMax": "Máx.", + "calcOperatorRange": "Rango", + "calcOperatorEarliest": "Primero", + "calcOperatorLatest": "Último", "filterOperatorIs": "Es", "filterOperatorIsNot": "No es", "filterOperatorContains": "Contiene", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index c0c445e24..ec51a9701 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,20 @@ { + "calcOperatorNone": "Aucun", + "calcOperatorCountAll": "Compter tout", + "calcOperatorCountValues": "Compter les valeurs", + "calcOperatorCountUniqueValues": "Compter les valeurs uniques", + "calcOperatorCountEmpty": "Compter vide", + "calcOperatorCountNotEmpty": "Compter non vide", + "calcOperatorPercentEmpty": "Pourcentage vide", + "calcOperatorPercentNotEmpty": "Pourcentage non vide", + "calcOperatorSum": "Somme", + "calcOperatorAverage": "Moyenne", + "calcOperatorMedian": "Médiane", + "calcOperatorMin": "Min", + "calcOperatorMax": "Max", + "calcOperatorRange": "Plage", + "calcOperatorEarliest": "Le plus tôt", + "calcOperatorLatest": "Dernier", "filterOperatorIs": "Est", "filterOperatorIsNot": "N'est pas", "filterOperatorContains": "Contient", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 51bba422a..20770bd2e 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,4 +1,20 @@ { + "calcOperatorNone": "無", + "calcOperatorCountAll": "行計數", + "calcOperatorCountValues": "值計數", + "calcOperatorCountUniqueValues": "唯一值計數", + "calcOperatorCountEmpty": "空值計數", + "calcOperatorCountNotEmpty": "非空值計數", + "calcOperatorPercentEmpty": "空值佔比", + "calcOperatorPercentNotEmpty": "非空值佔比", + "calcOperatorSum": "求和", + "calcOperatorAverage": "平均值", + "calcOperatorMedian": "中位數", + "calcOperatorMin": "最小值", + "calcOperatorMax": "最大值", + "calcOperatorRange": "極差", + "calcOperatorEarliest": "最早", + "calcOperatorLatest": "最晚", "filterOperatorIs": "等於", "filterOperatorIsNot": "不等於", "filterOperatorContains": "包含", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index bc97c7f02..2e7642748 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,20 @@ { + "calcOperatorNone": "无", + "calcOperatorCountAll": "行计数", + "calcOperatorCountValues": "值计数", + "calcOperatorCountUniqueValues": "唯一值计数", + "calcOperatorCountEmpty": "空值计数", + "calcOperatorCountNotEmpty": "非空值计数", + "calcOperatorPercentEmpty": "空值占比", + "calcOperatorPercentNotEmpty": "非空值占比", + "calcOperatorSum": "求和", + "calcOperatorAverage": "平均值", + "calcOperatorMedian": "中位数", + "calcOperatorMin": "最小值", + "calcOperatorMax": "最大值", + "calcOperatorRange": "极差", + "calcOperatorEarliest": "最早", + "calcOperatorLatest": "最晚", "filterOperatorIs": "等于", "filterOperatorIsNot": "不等于", "filterOperatorContains": "包含", diff --git a/kernel/av/av.go b/kernel/av/av.go index fee3661d1..7f3f2f0e9 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -74,55 +74,6 @@ func (av *AttributeView) GetColumnNames() (ret []string) { return } -type AttributeViewFilter struct { - Column string `json:"column"` - Operator FilterOperator `json:"operator"` - Value *Value `json:"value"` -} - -type FilterOperator string - -const ( - FilterOperatorIsEqual FilterOperator = "=" - FilterOperatorIsNotEqual FilterOperator = "!=" - FilterOperatorIsGreater FilterOperator = ">" - FilterOperatorIsGreaterOrEqual FilterOperator = ">=" - FilterOperatorIsLess FilterOperator = "<" - FilterOperatorIsLessOrEqual FilterOperator = "<=" - FilterOperatorContains FilterOperator = "Contains" - FilterOperatorDoesNotContain FilterOperator = "Does not contains" - FilterOperatorIsEmpty FilterOperator = "Is empty" - FilterOperatorIsNotEmpty FilterOperator = "Is not empty" - FilterOperatorStartsWith FilterOperator = "Starts with" - FilterOperatorEndsWith FilterOperator = "Ends with" - FilterOperatorIsBetween FilterOperator = "Is between" - FilterOperatorIsRelativeToToday FilterOperator = "Is relative to today" -) - -type AttributeViewCalc struct { - Column string `json:"column"` - Operator CalcOperator `json:"operator"` -} - -type CalcOperator string - -const ( - CalcOperatorNone FilterOperator = "" - CalcOperatorCountAll FilterOperator = "Count all" -) - -type AttributeViewSort struct { - Column string `json:"column"` // 列 ID - Order SortOrder `json:"order"` // 排序顺序 -} - -type SortOrder string - -const ( - SortOrderAsc SortOrder = "ASC" - SortOrderDesc SortOrder = "DESC" -) - func ParseAttributeView(avID string) (ret *AttributeView, err error) { avJSONPath := getAttributeViewDataPath(avID) if !gulu.File.IsExist(avJSONPath) { @@ -241,79 +192,204 @@ func RebuildAttributeViewTable(tx *sql.Tx, av *AttributeView) (err error) { return } -func (av *AttributeView) SortRows() { - if 0 < len(av.Sorts) { - 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 - }) - } +type AttributeViewSort struct { + Column string `json:"column"` // 列 ID + Order SortOrder `json:"order"` // 排序顺序 } +type SortOrder string + +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 + }) +} + +type AttributeViewFilter struct { + Column string `json:"column"` + Operator FilterOperator `json:"operator"` + Value *Value `json:"value"` +} + +type FilterOperator string + +const ( + FilterOperatorIsEqual FilterOperator = "=" + FilterOperatorIsNotEqual FilterOperator = "!=" + FilterOperatorIsGreater FilterOperator = ">" + FilterOperatorIsGreaterOrEqual FilterOperator = ">=" + FilterOperatorIsLess FilterOperator = "<" + FilterOperatorIsLessOrEqual FilterOperator = "<=" + FilterOperatorContains FilterOperator = "Contains" + FilterOperatorDoesNotContain FilterOperator = "Does not contains" + FilterOperatorIsEmpty FilterOperator = "Is empty" + FilterOperatorIsNotEmpty FilterOperator = "Is not empty" + FilterOperatorStartsWith FilterOperator = "Starts with" + FilterOperatorEndsWith FilterOperator = "Ends with" + FilterOperatorIsBetween FilterOperator = "Is between" + FilterOperatorIsRelativeToToday FilterOperator = "Is relative to today" +) + func (av *AttributeView) FilterRows() { - if 0 < len(av.Filters) { - 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 + 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 } +type AttributeViewCalc struct { + Column string `json:"column"` + Operator CalcOperator `json:"operator"` + Value *Value `json:"value"` +} + +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() { + if 1 > len(av.Calculates) { + return + } + var colIndexes []int + for _, c := range av.Calculates { + for i, col := range av.Columns { + if col.ID == c.Column { + colIndexes = append(colIndexes, i) + break + } + } + } + + var colValues []*Value + for _, row := range av.Rows { + for _, index := range colIndexes { + colValues = append(colValues, row.Cells[index].Value) + } + } + + for i, c := range av.Calculates { + switch c.Operator { + case CalcOperatorCountAll: + av.Calculates[i].Value = &Value{Number: &ValueNumber{Content: float64(len(colValues))}} + case CalcOperatorCountValues: + var countValues int + for _, v := range colValues { + if v.Number.IsNotEmpty { + countValues++ + } + } + + av.Calculates[i].Value = &Value{Number: &ValueNumber{Content: float64(countValues)}} + case CalcOperatorCountUniqueValues: + var countUniqueValues int + uniqueValues := map[float64]bool{} + for _, v := range colValues { + if v.Number.IsNotEmpty { + if !uniqueValues[v.Number.Content] { + countUniqueValues++ + uniqueValues[v.Number.Content] = true + } + } + } + + av.Calculates[i].Value = &Value{Number: &ValueNumber{Content: float64(countUniqueValues)}} + case CalcOperatorCountEmpty: + var countEmpty int + for _, v := range colValues { + if !v.Number.IsNotEmpty { + countEmpty++ + } + } + + av.Calculates[i].Value = &Value{Number: &ValueNumber{Content: float64(countEmpty)}} + } + } } diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 9f6a6cc2b..db3de6c54 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -43,6 +43,7 @@ func RenderAttributeView(avID string) (ret *av.AttributeView, err error) { ret.FilterRows() ret.SortRows() + ret.CalcCols() return }