This commit is contained in:
Daniel 2023-07-07 19:55:46 +08:00
parent ebe130a029
commit 93e98e5b13
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
7 changed files with 273 additions and 116 deletions

View file

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

View file

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

View file

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

View file

@ -1,4 +1,20 @@
{
"calcOperatorNone": "無",
"calcOperatorCountAll": "行計數",
"calcOperatorCountValues": "值計數",
"calcOperatorCountUniqueValues": "唯一值計數",
"calcOperatorCountEmpty": "空值計數",
"calcOperatorCountNotEmpty": "非空值計數",
"calcOperatorPercentEmpty": "空值佔比",
"calcOperatorPercentNotEmpty": "非空值佔比",
"calcOperatorSum": "求和",
"calcOperatorAverage": "平均值",
"calcOperatorMedian": "中位數",
"calcOperatorMin": "最小值",
"calcOperatorMax": "最大值",
"calcOperatorRange": "極差",
"calcOperatorEarliest": "最早",
"calcOperatorLatest": "最晚",
"filterOperatorIs": "等於",
"filterOperatorIsNot": "不等於",
"filterOperatorContains": "包含",

View file

@ -1,4 +1,20 @@
{
"calcOperatorNone": "无",
"calcOperatorCountAll": "行计数",
"calcOperatorCountValues": "值计数",
"calcOperatorCountUniqueValues": "唯一值计数",
"calcOperatorCountEmpty": "空值计数",
"calcOperatorCountNotEmpty": "非空值计数",
"calcOperatorPercentEmpty": "空值占比",
"calcOperatorPercentNotEmpty": "非空值占比",
"calcOperatorSum": "求和",
"calcOperatorAverage": "平均值",
"calcOperatorMedian": "中位数",
"calcOperatorMin": "最小值",
"calcOperatorMax": "最大值",
"calcOperatorRange": "极差",
"calcOperatorEarliest": "最早",
"calcOperatorLatest": "最晚",
"filterOperatorIs": "等于",
"filterOperatorIsNot": "不等于",
"filterOperatorContains": "包含",

View file

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

View file

@ -43,6 +43,7 @@ func RenderAttributeView(avID string) (ret *av.AttributeView, err error) {
ret.FilterRows()
ret.SortRows()
ret.CalcCols()
return
}